// Package scpt provides an interpreter for my simple applescript-like // scripting language package scpt import ( "errors" "fmt" "github.com/alecthomas/participle" "github.com/antonmedv/expr" "io" "reflect" "strings" ) // AddFuncs adds all functions from the provided FuncMap into // the Funcs variable func AddFuncs(fnMap FuncMap) { // Add each function to Funcs 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 { // Add each variable to Vars Vars[name] = val } } // Parse uses participle to parse a script from r into a new AST func Parse(r io.Reader) (*AST, error) { // Build parser from empty AST struct with custom lexer parser, err := participle.Build( &AST{}, participle.Lexer(scptLexer), participle.Elide("Whitespace", "Comment"), ) if err != nil { return nil, err } // Create new empty AST struct ast := &AST{} // Parse script from provided reader into ast err = parser.Parse(r, ast) if err != nil { return nil, err } // Return filled AST struct return ast, nil } // ParseValue parses a Value struct into a go value func ParseValue(val *Value) (interface{}, error) { // Determine which value was provided and return it if val.String != nil { // Return unquoted string return strings.Trim(*val.String, `"`), nil } else if val.Bool != nil { // Return dereferenced Bool converted to bool return bool(*val.Bool), nil } else if val.Number != nil { // Return dereferenced float return *val.Number, nil } else if val.SubCmd != nil { // Return reference to subcommand return val.SubCmd, nil } else if val.VarVal != nil { // If variable access contains index if val.VarVal.Index != nil { // Get index value index, err := callIfFunc(ParseValue(val.VarVal.Index)) if err != nil { return nil, err } // Get requested variable and attempt to assert as []interface{} slc, ok := Vars[*val.VarVal.Name].([]interface{}) // If assertion successful if ok { // Attempt to assert index as a 64-bit float indexFlt, ok := index.(float64) if !ok { return nil, errors.New("array index must be a number") } // If requested index is out of range, return error if int64(len(slc)) <= int64(indexFlt) { return nil, fmt.Errorf("index %d is out of range with length %d", *val.VarVal.Index, len(slc)) } // Return value at requested index of requested variable return slc[int64(indexFlt)], nil } else { // If assertion unsuccessful, attempt to assert as a map[interface{}]interface{} iMap, ok := Vars[*val.VarVal.Name].(map[interface{}]interface{}) if !ok { return nil, errors.New("variable " + *val.VarVal.Name + " does not exist or is not a map") } // Attempt to get value at requested key val, ok := iMap[index] if !ok { return nil, fmt.Errorf("index %v does not exist in map", index) } // Return value at key with no error return val, nil } } else { // If index is absent, attempt to get variable value from Vars value, ok := Vars[*val.VarVal.Name] if !ok { return nil, errors.New("variable " + *val.VarVal.Name + " does not exist") } // Return value with no error return value, nil } } else if val.Expr != nil { // If value is an expression, return evaluated expression return evalExpr(*val.Expr) } else if val.Array != nil { // If value is an array, create new nil []interface{} var iSlice []interface{} // For each value in array for _, value := range val.Array { // Recursively parse value iVal, err := callIfFunc(ParseValue(value)) if err != nil { return nil, err } // Append value to []interface{} iSlice = append(iSlice, iVal) } // Return []interface{] return iSlice, nil } else if val.Map != nil { // If value is a map, create new empty map[interface{}]interface{} iMap := map[interface{}]interface{}{} // For each value in map for _, value := range val.Map { // Recursively parse value iVal, err := ParseValue(value.Value) if err != nil { return nil, err } // Recursively parse key iKey, err := ParseValue(value.Key) if err != nil { return nil, err } // Set key of map to value iMap[iKey] = iVal } // Return map[interface{}]interface{} return iMap, nil } else if val.Opposite != nil { value, err := callIfFunc(ParseValue(val.Opposite)) if err != nil { return nil, err } boolean, ok := value.(bool) if !ok { return nil, errors.New("cannot take opposite of a non-boolean value") } return !boolean, nil } return nil, nil } // Evaluate given expression, returning its value and optionally an error func evalExpr(expression Expression) (interface{}, error) { // Parse value of left side of expression left, _ := callIfFunc(ParseValue(expression.Left)) // If value is string, requote if isStr(left) { left = quoteStr(left.(string)) } // Create new nil string var right string // For every right gsegment for _, segment := range expression.RightSegs { // Parse value of right segment, calling it if it is a function rVal, _ := callIfFunc(ParseValue(segment.Right)) // If value is string, requote if isStr(rVal) { rVal = quoteStr(rVal) } // Append right segment to right string right = right + fmt.Sprintf( " %s %v", segment.Op, rVal, ) } // Create string expression from segments and operator exp := fmt.Sprintf( "%v %s", left, right, ) // Compile string expression program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**")) if err != nil { return nil, err } // Run expression out, err := expr.Run(program, Vars) if err != nil { return nil, err } // Return expression output value return out, nil } // Add quotes to an unquoted string func quoteStr(s interface{}) string { // If s is nil if s == nil { // Return empty quotes return `""` } else { // Otherwise return formatted string using %v (any value) return fmt.Sprintf(`"%v"`, s) } } // Check if i is a string func isStr(i interface{}) bool { if i == nil { return true } // if type of input is string, return true if reflect.TypeOf(i).String() == "string" { return true } return false } // Call val if it is a function, otherwise pass through return values func callIfFunc(val interface{}, err error) (interface{}, error) { if err != nil { return nil, err } // If val is a pointer to a FuncCall if IsFuncCall(val) { // Pass through return values of function call return CallFunction(val.(*FuncCall)) } // Return given value return val, nil } // 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) { // Create new empty map of strings to any type argMap := map[string]interface{}{} // For each argument for _, arg := range args { // Parse value into interface{} val, err := ParseValue(arg.Value) if err != nil { return nil, err } // If value is function call if IsFuncCall(val) { // Call function, setting its value as the argument's value argMap[arg.Key], err = CallFunction(val.(*FuncCall)) if err != nil { return nil, err } // Skip further code and start next loop continue } // Set argument value to parsed value argMap[arg.Key] = val } // Return map of arguments return argMap, nil } // IsFuncCall checks if val is a FuncCall struct func IsFuncCall(val interface{}) bool { // If type of val is a pointer to FuncCall, return true return reflect.TypeOf(val) == reflect.TypeOf(&FuncCall{}) } // CallFunction executes a given function call in the form of // a FuncCall struct func CallFunction(call *FuncCall) (interface{}, error) { // Unwrap provided arguments argMap, err := UnwrapArgs(call.Args) if err != nil { return nil, err } // Attempt to get function from Funcs map fn, ok := Funcs[call.Name] if !ok { return nil, errors.New("no such function: " + call.Name) } // Return value received from function return fn(argMap) }