/* 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 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) }