Implement if statements and regex lexer
This commit is contained in:
parent
d552836b2c
commit
745920b139
41
ast.go
41
ast.go
@ -32,6 +32,17 @@ type AST struct {
|
||||
func (ast *AST) Execute() error {
|
||||
// For each command in AST
|
||||
for _, cmd := range ast.Commands {
|
||||
// Execute current command
|
||||
err := executeCmd(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse and execute command
|
||||
func executeCmd(cmd *Command) error {
|
||||
// If parsing variables
|
||||
if cmd.Vars != nil {
|
||||
// For each variable
|
||||
@ -55,7 +66,6 @@ func (ast *AST) Execute() error {
|
||||
Vars[Var.Key] = val
|
||||
}
|
||||
}
|
||||
// If parsing function calls
|
||||
} else if cmd.Calls != nil {
|
||||
// For each function call
|
||||
for _, Call := range cmd.Calls {
|
||||
@ -66,18 +76,27 @@ func (ast *AST) Execute() error {
|
||||
}
|
||||
}
|
||||
} else if cmd.Ifs != nil {
|
||||
// For each if statement
|
||||
for _, If := range cmd.Ifs {
|
||||
// Get condition value
|
||||
condVal, err := callIfFunc(ParseValue(If.Condition))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%s: %s", If.Condition.Pos, err)
|
||||
}
|
||||
// Attempt to assert condition type as bool
|
||||
condBool, ok := condVal.(bool)
|
||||
if !ok {
|
||||
return errors.New("condition must be a boolean")
|
||||
}
|
||||
// If condition is true
|
||||
if condBool {
|
||||
_, err := CallFunction(If.Action)
|
||||
return err
|
||||
// For each inner command
|
||||
for _, InnerCmd := range If.InnerCmds {
|
||||
// Execute command recursively
|
||||
err := executeCmd(InnerCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -88,15 +107,17 @@ func (ast *AST) Execute() error {
|
||||
// Command stores any commands encountered while parsing a script
|
||||
type Command struct {
|
||||
Pos lexer.Position
|
||||
Tokens []lexer.Token
|
||||
Vars []*Var `( @@`
|
||||
Ifs []*If `| @@`
|
||||
Calls []*FuncCall `| @@ )`
|
||||
Calls []*FuncCall `| @@)`
|
||||
}
|
||||
|
||||
// If stores any if statements encountered while parsing a script
|
||||
type If struct {
|
||||
Pos lexer.Position
|
||||
Condition *Value `"if" @@`
|
||||
Action *FuncCall `"then" @@`
|
||||
Condition *Value `"if" @@ "{"`
|
||||
InnerCmds []*Command `@@*"}"`
|
||||
}
|
||||
|
||||
// FuncCall stores any function calls encountered while parsing a script
|
||||
@ -124,8 +145,7 @@ type Var struct {
|
||||
type Value struct {
|
||||
Pos lexer.Position
|
||||
String *string ` @String`
|
||||
Float *float64 `| @Float`
|
||||
Integer *int64 `| @Int`
|
||||
Number *float64 `| @Number`
|
||||
Bool *Bool `| @("true" | "false")`
|
||||
SubCmd *FuncCall `| "(" @@ ")"`
|
||||
VarVal *string `| "$" @Ident`
|
||||
@ -152,7 +172,8 @@ type Expression struct {
|
||||
RightSegs []*ExprRightSeg `@@*`
|
||||
}
|
||||
|
||||
// ExprRightSeg stores segments of the right side of an expression
|
||||
type ExprRightSeg struct {
|
||||
Op string `@( ">" | ">" "=" | "<" | "<" "=" | "!" "=" | "=" "=" | "+" | "-" | "*" | "/" | "^" | "%")`
|
||||
Op string `@Operator`
|
||||
Right *Value `@@`
|
||||
}
|
||||
|
17
lexer.go
Normal file
17
lexer.go
Normal file
@ -0,0 +1,17 @@
|
||||
package scpt
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/participle/lexer"
|
||||
"github.com/alecthomas/participle/lexer/stateful"
|
||||
)
|
||||
|
||||
// Create custom stateful regex lexer
|
||||
var scptLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
|
||||
{"Ident", `[a-zA-Z]\w*`, nil},
|
||||
{"String", `"[^"]*"`, nil},
|
||||
{"Number", `(?:\d*\.)?\d+`, nil},
|
||||
{"Punct", `[-[!@#$&()_{}\|:;"',.?/]|]`, nil},
|
||||
{"Whitespace", `[ \t\r\n]+`, nil},
|
||||
{"Comment", `#[^\n]+`, nil},
|
||||
{"Operator", `(>=|<=|>|<|==|!=)|[-+*/^%]`, nil},
|
||||
}))
|
15
scpt.go
15
scpt.go
@ -58,8 +58,12 @@ func AddVars(varMap map[string]interface{}) {
|
||||
|
||||
// Parse uses participle to parse a script from r into a new AST
|
||||
func Parse(r io.Reader) (*AST, error) {
|
||||
// Build parser from empty AST struct
|
||||
parser, err := participle.Build(&AST{})
|
||||
// Build parser from empty AST struct with custom lexer
|
||||
parser, err := participle.Build(
|
||||
&AST{},
|
||||
participle.Lexer(scptLexer),
|
||||
participle.Elide("Whitespace", "Comment"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -83,12 +87,9 @@ func ParseValue(val *Value) (interface{}, error) {
|
||||
} else if val.Bool != nil {
|
||||
// Return dereferenced Bool converted to bool
|
||||
return bool(*val.Bool), nil
|
||||
} else if val.Float != nil {
|
||||
} else if val.Number != nil {
|
||||
// Return dereferenced float
|
||||
return *val.Float, nil
|
||||
} else if val.Integer != nil {
|
||||
// Return dereferenced integer
|
||||
return *val.Integer, nil
|
||||
return *val.Number, nil
|
||||
} else if val.SubCmd != nil {
|
||||
// Return reference to subcommand
|
||||
return val.SubCmd, nil
|
||||
|
13
test.scpt
13
test.scpt
@ -3,5 +3,14 @@ display-dialog "Goodbye" with title 21 with type "error"
|
||||
do-shell-script "notify-send Test Notification"
|
||||
do-shell-script {"echo " + (display-dialog {"Test " + "2"} with title 30 with type "entry" with default "text")}
|
||||
set x to $y
|
||||
if {3 == 4} then display-dialog "What? Why is 3 equal to 4?" with title "What?!" with type "info"
|
||||
if {3 == 3} then display-dialog "3 is equal to 3!" with title "New Discovery" with type "info"
|
||||
|
||||
if {3 == 3} {
|
||||
set x to "hi"
|
||||
do-shell-script {"echo " + $x}
|
||||
display-dialog "3 is equal to 3!" with title "New Discovery" with type "info"
|
||||
if {3 == 4} {
|
||||
display-dialog "What? Why is 3 equal to 4?" with title "What?!" with type "error"
|
||||
do-shell-script "echo hi"
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user