package scpt import ( "bytes" "encoding/json" "errors" "fmt" "github.com/alecthomas/participle/lexer" ) var loopRunning bool var breakLoop bool var funcRunning bool var retValue interface{} = nil // AST stores the root of the Abstract Syntax Tree for scpt type AST struct { Pos lexer.Position Commands []*Command `@@*` } // Execute traverses the AST and executes any commands, it returns an error // containing the position at which the error was encountered and the error // itself 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 } func (ast *AST) Dump() ([]byte, error) { return json.Marshal(ast) } func (ast *AST) DumpPretty() ([]byte, error) { buf := bytes.NewBuffer([]byte{}) enc := json.NewEncoder(buf) enc.SetIndent("", " ") err := enc.Encode(ast) if err != nil { return buf.Bytes(), err } return buf.Bytes(), nil } func LoadAST(data []byte) (*AST, error) { var ast AST err := json.Unmarshal(data, &ast) if err != nil { return nil, err } return &ast, nil } // Execute a variable declaration func executeVarCmd(Var *Var) error { // 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) } } else if Var.Index != nil { // If variable definition has an associated index, get index value index, err := callIfFunc(ParseValue(Var.Index)) if err != nil { return fmt.Errorf("%s: %s", Var.Index.Pos, err) } // Attempt to get the variable from Vars and assert it as a []interface{} slc, ok := Vars[Var.Key].([]interface{}) // If assertion successful if ok { // Assert index value as a 64-bit float indexInt, ok := index.(float64) if !ok { return fmt.Errorf("%s: %s", Var.Pos, "variable "+Var.Key+" does not exist or is not an array") } // Set integer index of interface{} slice to value slc[int64(indexInt)] = val } else { // If slice assertion unsuccessful, attempt to assert as map[interface{}]interface{} iMap, ok := Vars[Var.Key].(map[interface{}]interface{}) if !ok { return fmt.Errorf("%s: %s", Var.Pos, "variable "+Var.Key+" does not exist or is not a map") } // Set index of interface{} to interface{} map to value iMap[index] = val } } else { // If value is not a function call, set variable to parsed value Vars[Var.Key] = val } return nil } // Execute an if statement func executeIfCmd(If *If) error { // 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", InnerCmd.Pos, err) } } } return nil } // Execute a repeat loop func executeRptLoop(rptLoop *RptLoop) error { // Set loopRunning to true to allow break loopRunning = true // Run for loop with correct amount of iterations rpt: for i := 0; i < *rptLoop.Times; i++ { // If user requested index variable via "{ var in ... }" if rptLoop.IndexVar != nil { // Set requested variable name to index Vars[*rptLoop.IndexVar] = i } // For each command within the loop for _, InnerCmd := range rptLoop.InnerCmds { // Execute command recursively err := executeCmd(InnerCmd) if err != nil { return fmt.Errorf("%s: %s", InnerCmd.Pos, err) } // If breakLoop set to true if breakLoop { // Reset breakLoop breakLoop = false break rpt } } } // Remove index variable if existent delete(Vars, *rptLoop.IndexVar) // Reset loopRunning loopRunning = false return nil } // Execute a while loop func executeWhlLoop(whlLoop *WhlLoop) error { loopRunning = true // Get condition value condVal, err := callIfFunc(ParseValue(whlLoop.Condition)) if err != nil { return fmt.Errorf("%s: %s", whlLoop.Condition.Pos, err) } // Attempt to assert condition type as bool condBool, ok := condVal.(bool) if !ok { return errors.New("condition must be a boolean") } // Run for loop if condition is true whl: for condBool { // For each inner command for _, InnerCmd := range whlLoop.InnerCmds { // Execute command recursively err := executeCmd(InnerCmd) if err != nil { return fmt.Errorf("%s: %s", InnerCmd.Pos, err) } // If breakLoop set to true if breakLoop { // Reset breakLoop breakLoop = false break whl } // Get condition value condVal, err = callIfFunc(ParseValue(whlLoop.Condition)) if err != nil { return fmt.Errorf("%s: %s", whlLoop.Condition.Pos, err) } // Attempt to assert condition type as bool and update its value condBool, ok = condVal.(bool) if !ok { return errors.New("condition must be a boolean") } } } loopRunning = false return nil } // Execute a function definition func executeFuncDef(def *FuncDef) error { // Set requested function name in Funcs Funcs[*def.Name] = func(args map[string]interface{}) (interface{}, error) { funcRunning = true // Create new empty map[interface{}]interface{} argIMap := map[interface{}]interface{}{} // Convert args map[string]interface{} to map[interface{}]interface{} for key, value := range args { argIMap[key] = value } // Set variable _args to the args map[interface{}]interface{} Vars["_args"] = argIMap // For each command within the definition for _, InnerCmd := range def.InnerCmds { // Execute command recursively err := executeCmd(InnerCmd) if err != nil { return nil, fmt.Errorf("%s: %s", InnerCmd.Pos, err) } if retValue != nil { ret := retValue retValue = nil funcRunning = false return ret, nil } } // Remove args variable from Vars delete(Vars, "_args") funcRunning = false return nil, nil } 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 { // Attempt to execute the variable command err := executeVarCmd(Var) if err != nil { return err } } } 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 { // Attempt to execute the if command err := executeIfCmd(If) if err != nil { return err } } } else if cmd.RptLoops != nil { // For each repeat loop for _, RptLoop := range cmd.RptLoops { // Attempt to execute the repeat loop err := executeRptLoop(RptLoop) if err != nil { return err } } } else if cmd.WhlLoops != nil { // For each while loop for _, WhlLoop := range cmd.WhlLoops { // Attempt to execute the while loop err := executeWhlLoop(WhlLoop) if err != nil { return err } } } else if cmd.Defs != nil { // For each function definition for _, Def := range cmd.Defs { // Attempt to execute the function definition err := executeFuncDef(Def) if err != nil { return err } } } return nil } // Command stores any commands encountered while parsing a script type Command struct { Pos lexer.Position Vars []*Var `( @@` Ifs []*If `| @@` RptLoops []*RptLoop `| @@` WhlLoops []*WhlLoop `| @@` Defs []*FuncDef `| @@` Calls []*FuncCall `| @@)` } // Value stores any literal values encountered while parsing a script type Value struct { Pos lexer.Position String *string ` @String` Number *float64 `| @Number` Bool *Bool `| @("true" | "false")` SubCmd *FuncCall `| "(" @@ ")"` VarVal *VarVal `| @@` Expr *Expression `| "{" @@ "}"` Map []*MapKVPair `| "[" (@@ ("," @@)* )? "]"` Array []*Value `| "[" (@@ ("," @@)* )? "]"` Opposite *Value `| "!" @@` } // Bool stores boolean values encountered while parsing a script. // It is required for the Capture method type Bool bool // Capture parses a boolean literal encountered in the script into // a Go boolean value func (b *Bool) Capture(values []string) error { // Convert string to boolean *b = values[0] == "true" return nil } // FuncCall stores any function calls encountered while parsing a script type FuncCall struct { Pos lexer.Position Name string `@Ident @("-" Ident)*` Args []*Arg `@@*` } // Arg stores arguments for function calls type Arg struct { Pos lexer.Position Key string `("with" @Ident)?` Value *Value `@@` } // VarVal stores any references to a variable encountered while parsing a script type VarVal struct { Name *string `"$" @Ident` Index *Value `("[" @@ "]")?` } // Expression stores any expressions encountered while parsing a // script for later evaluation type Expression struct { Pos lexer.Position Left *Value `@@` RightSegs []*ExprRightSeg `@@*` } // ExprRightSeg stores segments of the right side of an expression type ExprRightSeg struct { Op string `@Operator` Right *Value `@@` } // MapKVPair stores any key/value pairs encountered while parsing map literals type MapKVPair struct { Key *Value `@@` Value *Value `":" @@` } // FuncDef stores any function definitions encountered while parsing a script type FuncDef struct { Pos lexer.Position Name *string `"define" @Ident "{"` InnerCmds []*Command `@@* "}"` } // Var stores any variables encountered while parsing a script type Var struct { Pos lexer.Position Key string `"set" @Ident` Index *Value `("[" @@ "]")?` Value *Value `"to" @@` } // If stores any if statements encountered while parsing a script type If struct { Pos lexer.Position Condition *Value `"if" @@ "{"` InnerCmds []*Command `@@* "}"` } // RptLoop stores any repeat loops encountered while parsing a script type RptLoop struct { Pos lexer.Position Times *int `"repeat" @Number "times" "{"` IndexVar *string `(@Ident "in")?` InnerCmds []*Command `@@* "}"` } // WhlLoop stores any while loops encountered while parsing a script type WhlLoop struct { Pos lexer.Position Condition *Value `"loop" "while" @@ "{"` InnerCmds []*Command `@@* "}"` }