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