303 lines
8.0 KiB
Go
303 lines
8.0 KiB
Go
// 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)
|
|
}
|