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

39
ast.go
View File

@ -32,6 +32,17 @@ 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 {
// 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 parsing variables
if cmd.Vars != nil { if cmd.Vars != nil {
// For each variable // For each variable
@ -55,7 +66,6 @@ func (ast *AST) Execute() error {
Vars[Var.Key] = val Vars[Var.Key] = val
} }
} }
// If parsing function calls
} else if cmd.Calls != nil { } else if cmd.Calls != nil {
// For each function call // For each function call
for _, Call := range cmd.Calls { for _, Call := range cmd.Calls {
@ -66,18 +76,27 @@ func (ast *AST) Execute() error {
} }
} }
} else if cmd.Ifs != nil { } else if cmd.Ifs != nil {
// For each if statement
for _, If := range cmd.Ifs { for _, If := range cmd.Ifs {
// Get condition value
condVal, err := callIfFunc(ParseValue(If.Condition)) condVal, err := callIfFunc(ParseValue(If.Condition))
if err != nil { 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) condBool, ok := condVal.(bool)
if !ok { if !ok {
return errors.New("condition must be a boolean") return errors.New("condition must be a boolean")
} }
// If condition is true
if condBool { if condBool {
_, err := CallFunction(If.Action) // For each inner command
return err 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 // 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
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 // 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

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