Implement if statements and regex lexer

This commit is contained in:
Elara 2021-03-01 19:43:06 -08:00
parent d552836b2c
commit 745920b139
4 changed files with 104 additions and 56 deletions

115
ast.go
View File

@ -32,52 +32,71 @@ type AST struct {
func (ast *AST) Execute() error {
// For each command in AST
for _, cmd := range ast.Commands {
// If parsing variables
if cmd.Vars != nil {
// For each variable
for _, Var := range cmd.Vars {
// Parse value of variable
val, err := ParseValue(Var.Value)
// 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
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 {
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)
} else {
// If value is not a function call, set variable value to parsed value
Vars[Var.Key] = val
}
}
} 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 {
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
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
View 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
View File

@ -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

View File

@ -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"
}
}