Implement if statements and regex lexer
This commit is contained in:
parent
d552836b2c
commit
745920b139
115
ast.go
115
ast.go
@ -32,52 +32,71 @@ type AST struct {
|
|||||||
func (ast *AST) Execute() error {
|
func (ast *AST) Execute() error {
|
||||||
// For each command in AST
|
// For each command in AST
|
||||||
for _, cmd := range ast.Commands {
|
for _, cmd := range ast.Commands {
|
||||||
// If parsing variables
|
// Execute current command
|
||||||
if cmd.Vars != nil {
|
err := executeCmd(cmd)
|
||||||
// For each variable
|
if err != nil {
|
||||||
for _, Var := range cmd.Vars {
|
return err
|
||||||
// Parse value of variable
|
}
|
||||||
val, err := ParseValue(Var.Value)
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and execute command
|
||||||
|
func executeCmd(cmd *Command) error {
|
||||||
|
// If parsing variables
|
||||||
|
if cmd.Vars != nil {
|
||||||
|
// For each variable
|
||||||
|
for _, Var := range cmd.Vars {
|
||||||
|
// Parse value of variable
|
||||||
|
val, err := ParseValue(Var.Value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
||||||
|
}
|
||||||
|
// If value of variable is a function call
|
||||||
|
if IsFuncCall(val) {
|
||||||
|
// Assert type of val as *FuncCall
|
||||||
|
Call := val.(*FuncCall)
|
||||||
|
// Set variable value to function return value
|
||||||
|
Vars[Var.Key], err = CallFunction(Call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
||||||
}
|
}
|
||||||
// If value of variable is a function call
|
} else {
|
||||||
if IsFuncCall(val) {
|
// If value is not a function call, set variable value to parsed value
|
||||||
// Assert type of val as *FuncCall
|
Vars[Var.Key] = val
|
||||||
Call := val.(*FuncCall)
|
}
|
||||||
// Set variable value to function return value
|
}
|
||||||
Vars[Var.Key], err = CallFunction(Call)
|
} else if cmd.Calls != nil {
|
||||||
|
// For each function call
|
||||||
|
for _, Call := range cmd.Calls {
|
||||||
|
// Attempt to call function
|
||||||
|
_, err := CallFunction(Call)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", Call.Pos, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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 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 {
|
||||||
|
// For each inner command
|
||||||
|
for _, InnerCmd := range If.InnerCmds {
|
||||||
|
// Execute command recursively
|
||||||
|
err := executeCmd(InnerCmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// If value is not a function call, set variable value to parsed value
|
|
||||||
Vars[Var.Key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If parsing function calls
|
|
||||||
} else if cmd.Calls != nil {
|
|
||||||
// For each function call
|
|
||||||
for _, Call := range cmd.Calls {
|
|
||||||
// Attempt to call function
|
|
||||||
_, err := CallFunction(Call)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s: %s", Call.Pos, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if cmd.Ifs != nil {
|
|
||||||
for _, If := range cmd.Ifs {
|
|
||||||
condVal, err := callIfFunc(ParseValue(If.Condition))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
condBool, ok := condVal.(bool)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("condition must be a boolean")
|
|
||||||
}
|
|
||||||
if condBool {
|
|
||||||
_, err := CallFunction(If.Action)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,15 +107,17 @@ func (ast *AST) Execute() error {
|
|||||||
// Command stores any commands encountered while parsing a script
|
// Command stores any commands encountered while parsing a script
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
|
Tokens []lexer.Token
|
||||||
Vars []*Var `( @@`
|
Vars []*Var `( @@`
|
||||||
Ifs []*If `| @@`
|
Ifs []*If `| @@`
|
||||||
Calls []*FuncCall `| @@ )`
|
Calls []*FuncCall `| @@)`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If stores any if statements encountered while parsing a script
|
||||||
type If struct {
|
type If struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
Condition *Value `"if" @@`
|
Condition *Value `"if" @@ "{"`
|
||||||
Action *FuncCall `"then" @@`
|
InnerCmds []*Command `@@*"}"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FuncCall stores any function calls encountered while parsing a script
|
// FuncCall stores any function calls encountered while parsing a script
|
||||||
@ -124,8 +145,7 @@ type Var struct {
|
|||||||
type Value struct {
|
type Value struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
String *string ` @String`
|
String *string ` @String`
|
||||||
Float *float64 `| @Float`
|
Number *float64 `| @Number`
|
||||||
Integer *int64 `| @Int`
|
|
||||||
Bool *Bool `| @("true" | "false")`
|
Bool *Bool `| @("true" | "false")`
|
||||||
SubCmd *FuncCall `| "(" @@ ")"`
|
SubCmd *FuncCall `| "(" @@ ")"`
|
||||||
VarVal *string `| "$" @Ident`
|
VarVal *string `| "$" @Ident`
|
||||||
@ -152,7 +172,8 @@ type Expression struct {
|
|||||||
RightSegs []*ExprRightSeg `@@*`
|
RightSegs []*ExprRightSeg `@@*`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExprRightSeg stores segments of the right side of an expression
|
||||||
type ExprRightSeg struct {
|
type ExprRightSeg struct {
|
||||||
Op string `@( ">" | ">" "=" | "<" | "<" "=" | "!" "=" | "=" "=" | "+" | "-" | "*" | "/" | "^" | "%")`
|
Op string `@Operator`
|
||||||
Right *Value `@@`
|
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
|
// Parse uses participle to parse a script from r into a new AST
|
||||||
func Parse(r io.Reader) (*AST, error) {
|
func Parse(r io.Reader) (*AST, error) {
|
||||||
// Build parser from empty AST struct
|
// Build parser from empty AST struct with custom lexer
|
||||||
parser, err := participle.Build(&AST{})
|
parser, err := participle.Build(
|
||||||
|
&AST{},
|
||||||
|
participle.Lexer(scptLexer),
|
||||||
|
participle.Elide("Whitespace", "Comment"),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -83,12 +87,9 @@ func ParseValue(val *Value) (interface{}, error) {
|
|||||||
} else if val.Bool != nil {
|
} else if val.Bool != nil {
|
||||||
// Return dereferenced Bool converted to bool
|
// Return dereferenced Bool converted to bool
|
||||||
return bool(*val.Bool), nil
|
return bool(*val.Bool), nil
|
||||||
} else if val.Float != nil {
|
} else if val.Number != nil {
|
||||||
// Return dereferenced float
|
// Return dereferenced float
|
||||||
return *val.Float, nil
|
return *val.Number, nil
|
||||||
} else if val.Integer != nil {
|
|
||||||
// Return dereferenced integer
|
|
||||||
return *val.Integer, nil
|
|
||||||
} else if val.SubCmd != nil {
|
} else if val.SubCmd != nil {
|
||||||
// Return reference to subcommand
|
// Return reference to subcommand
|
||||||
return val.SubCmd, nil
|
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 "notify-send Test Notification"
|
||||||
do-shell-script {"echo " + (display-dialog {"Test " + "2"} with title 30 with type "entry" with default "text")}
|
do-shell-script {"echo " + (display-dialog {"Test " + "2"} with title 30 with type "entry" with default "text")}
|
||||||
set x to $y
|
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