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

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