package scpt import ( "errors" "fmt" "github.com/alecthomas/participle" "github.com/antonmedv/expr" "io" "reflect" "strings" ) // Vars stores any variables set during script runtime var Vars = map[string]interface{}{} // FuncMap is a map of strings mapped to suitable script functions type FuncMap map[string]func(map[string]interface{}) (interface{}, error) // Funcs stores the functions allowed for use in a script var Funcs = FuncMap{ "display-dialog": displayDialog, "do-shell-script": doShellScript, "print": print, } // AddFuncs adds all functions from the provided FuncMap into // the Funcs variable func AddFuncs(fnMap FuncMap) { for name, fn := range fnMap { Funcs[name] = fn } } // AddVars adds all functions from the provided map into // the Vars variable func AddVars(varMap map[string]interface{}) { for name, val := range varMap { Vars[name] = val } } // Parse uses participle to parse a script from r into a new AST func Parse(r io.Reader) (*AST, error) { parser, err := participle.Build(&AST{}) if err != nil { return nil, err } ast := &AST{} err = parser.Parse(r, ast) if err != nil { return nil, err } return ast, nil } // ParseValue parses a Value struct into a go value func ParseValue(val *Value) (interface{}, error) { if val.String != nil { return strings.Trim(*val.String, `"`), nil } else if val.Bool != nil { return *val.Bool, nil } else if val.Float != nil { return *val.Float, nil } else if val.Integer != nil { return *val.Integer, nil } else if val.SubCmd != nil { return val.SubCmd, nil } else if val.VarVal != nil { return Vars[*val.VarVal], nil } else if val.Expr != nil { left, _ := ParseValue(val.Expr.Left) if isStr(left) { left = requoteStr(left.(string)) } right, _ := ParseValue(val.Expr.Right) if isStr(right) { right = requoteStr(right.(string)) } exp := fmt.Sprintf( "%v %s %v", left, val.Expr.Op, right, ) fmt.Println(exp) program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**")) if err != nil { return nil, err } out, err := expr.Run(program, Vars) if err != nil { return nil, err } return out, nil } return nil, nil } // Add quotes to an unquoted string func requoteStr(s string) string { return `"` + s + `"` } // Check if i is a string func isStr(i interface{}) bool { if reflect.TypeOf(i).String() == "string" { return true } return false } // UnwrapArgs takes a slice of Arg structs and returns a map // storing the argument name and its value. If the argument has // no name, its key will be an empty string func UnwrapArgs(args []*Arg) (map[string]interface{}, error) { argMap := map[string]interface{}{} for _, arg := range args { val, err := ParseValue(arg.Value) if err != nil { return nil, err } if IsFuncCall(val) { argMap[arg.Key], err = CallFunction(val.(*FuncCall)) if err != nil { return nil, err } continue } argMap[arg.Key] = val } return argMap, nil } // IsFuncCall checks if val is a FuncCall struct func IsFuncCall(val interface{}) bool { if strings.Contains(reflect.TypeOf(val).String(), ".FuncCall") { return true } return false } // CallFunction executes a given function call in the form of // a FuncCall struct func CallFunction(call *FuncCall) (interface{}, error) { argMap, err := UnwrapArgs(call.Args) if err != nil { return nil, err } fn, ok := Funcs[call.Name] if !ok { return nil, errors.New("no such function: " + call.Name) } return fn(argMap) }