|
|
@ -1,3 +1,5 @@ |
|
|
|
// Package scpt provides an interpreter for my simple applescript-like
|
|
|
|
// scripting language
|
|
|
|
package scpt |
|
|
|
|
|
|
|
import ( |
|
|
@ -20,12 +22,12 @@ type FuncMap map[string]func(map[string]interface{}) (interface{}, error) |
|
|
|
var Funcs = FuncMap{ |
|
|
|
"display-dialog": displayDialog, |
|
|
|
"do-shell-script": doShellScript, |
|
|
|
"print": print, |
|
|
|
} |
|
|
|
|
|
|
|
// 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 |
|
|
|
} |
|
|
@ -35,62 +37,81 @@ func AddFuncs(fnMap FuncMap) { |
|
|
|
// 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
|
|
|
|
parser, err := participle.Build(&AST{}) |
|
|
|
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 boolean
|
|
|
|
return *val.Bool, nil |
|
|
|
} else if val.Float != nil { |
|
|
|
// Return dereferenced float
|
|
|
|
return *val.Float, nil |
|
|
|
} else if val.Integer != nil { |
|
|
|
// Return dereferenced integer
|
|
|
|
return *val.Integer, nil |
|
|
|
} else if val.SubCmd != nil { |
|
|
|
// Return reference to subcommand
|
|
|
|
return val.SubCmd, nil |
|
|
|
} else if val.VarVal != nil { |
|
|
|
// Return value of provided key
|
|
|
|
return Vars[*val.VarVal], nil |
|
|
|
} else if val.Expr != nil { |
|
|
|
// Parse value of left side of expression
|
|
|
|
left, _ := ParseValue(val.Expr.Left) |
|
|
|
// If value is string, requote
|
|
|
|
if isStr(left) { |
|
|
|
left = requoteStr(left.(string)) |
|
|
|
} |
|
|
|
// Parse value of right side of expression
|
|
|
|
right, _ := ParseValue(val.Expr.Right) |
|
|
|
// If value is string, requote
|
|
|
|
if isStr(right) { |
|
|
|
right = requoteStr(right.(string)) |
|
|
|
} |
|
|
|
// Create string expression from halves and operator
|
|
|
|
exp := fmt.Sprintf( |
|
|
|
"%v %s %v", |
|
|
|
left, |
|
|
|
val.Expr.Op, |
|
|
|
right, |
|
|
|
) |
|
|
|
fmt.Println(exp) |
|
|
|
// 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 |
|
|
|
} |
|
|
|
return nil, nil |
|
|
@ -98,11 +119,13 @@ func ParseValue(val *Value) (interface{}, error) { |
|
|
|
|
|
|
|
// Add quotes to an unquoted string
|
|
|
|
func requoteStr(s string) string { |
|
|
|
// Return quoted string
|
|
|
|
return `"` + s + `"` |
|
|
|
} |
|
|
|
|
|
|
|
// Check if i is a string
|
|
|
|
func isStr(i interface{}) bool { |
|
|
|
// if type of input is string, return true
|
|
|
|
if reflect.TypeOf(i).String() == "string" { |
|
|
|
return true |
|
|
|
} |
|
|
@ -113,26 +136,35 @@ func isStr(i interface{}) bool { |
|
|
|
// 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 contains .FuncCall, return true
|
|
|
|
if strings.Contains(reflect.TypeOf(val).String(), ".FuncCall") { |
|
|
|
return true |
|
|
|
} |
|
|
@ -142,13 +174,16 @@ func IsFuncCall(val interface{}) bool { |
|
|
|
// 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) |
|
|
|
} |
|
|
|