You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
8.0 KiB
302 lines
8.0 KiB
// 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) |
|
}
|
|
|