/* Copyright (c) 2021 Arsen Musayelyan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ 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 `@@* "}"` }