scpt/scpt.go

190 lines
4.8 KiB
Go
Raw Normal View History

2021-03-01 23:01:21 +00:00
// Package scpt provides an interpreter for my simple applescript-like
// scripting language
2021-03-01 17:11:22 +00:00
package scpt
import (
"errors"
"fmt"
"github.com/alecthomas/participle"
"github.com/antonmedv/expr"
2021-03-01 17:11:22 +00:00
"io"
"reflect"
2021-03-01 17:11:22 +00:00
"strings"
)
// Vars stores any variables set during script runtime
2021-03-01 17:11:22 +00:00
var Vars = map[string]interface{}{}
// FuncMap is a map of strings mapped to suitable script functions
2021-03-01 17:11:22 +00:00
type FuncMap map[string]func(map[string]interface{}) (interface{}, error)
// Funcs stores the functions allowed for use in a script
2021-03-01 17:11:22 +00:00
var Funcs = FuncMap{
"display-dialog": displayDialog,
"do-shell-script": doShellScript,
2021-03-01 17:11:22 +00:00
}
// AddFuncs adds all functions from the provided FuncMap into
// the Funcs variable
2021-03-01 17:11:22 +00:00
func AddFuncs(fnMap FuncMap) {
2021-03-01 23:01:21 +00:00
// Add each function to Funcs
2021-03-01 17:11:22 +00:00
for name, fn := range fnMap {
Funcs[name] = fn
}
}
// AddVars adds all functions from the provided map into
// the Vars variable
2021-03-01 17:11:22 +00:00
func AddVars(varMap map[string]interface{}) {
for name, val := range varMap {
2021-03-01 23:01:21 +00:00
// Add each variable to Vars
2021-03-01 17:11:22 +00:00
Vars[name] = val
}
}
// Parse uses participle to parse a script from r into a new AST
2021-03-01 17:11:22 +00:00
func Parse(r io.Reader) (*AST, error) {
2021-03-01 23:01:21 +00:00
// Build parser from empty AST struct
2021-03-01 17:11:22 +00:00
parser, err := participle.Build(&AST{})
if err != nil {
return nil, err
}
2021-03-01 23:01:21 +00:00
// Create new empty AST struct
2021-03-01 17:11:22 +00:00
ast := &AST{}
2021-03-01 23:01:21 +00:00
// Parse script from provided reader into ast
2021-03-01 17:22:00 +00:00
err = parser.Parse(r, ast)
2021-03-01 17:11:22 +00:00
if err != nil {
return nil, err
}
2021-03-01 23:01:21 +00:00
// Return filled AST struct
2021-03-01 17:11:22 +00:00
return ast, nil
}
// ParseValue parses a Value struct into a go value
func ParseValue(val *Value) (interface{}, error) {
2021-03-01 23:01:21 +00:00
// Determine which value was provided and return it
2021-03-01 17:11:22 +00:00
if val.String != nil {
2021-03-01 23:01:21 +00:00
// Return unquoted string
return strings.Trim(*val.String, `"`), nil
2021-03-01 17:11:22 +00:00
} else if val.Bool != nil {
2021-03-01 23:01:21 +00:00
// Return dereferenced boolean
return *val.Bool, nil
2021-03-01 17:11:22 +00:00
} else if val.Float != nil {
2021-03-01 23:01:21 +00:00
// Return dereferenced float
return *val.Float, nil
2021-03-01 17:11:22 +00:00
} else if val.Integer != nil {
2021-03-01 23:01:21 +00:00
// Return dereferenced integer
return *val.Integer, nil
2021-03-01 17:11:22 +00:00
} else if val.SubCmd != nil {
2021-03-01 23:01:21 +00:00
// Return reference to subcommand
return val.SubCmd, nil
2021-03-01 17:11:22 +00:00
} else if val.VarVal != nil {
2021-03-01 23:01:21 +00:00
// Return value of provided key
return Vars[*val.VarVal], nil
} else if val.Expr != nil {
2021-03-01 23:01:21 +00:00
// Parse value of left side of expression
left, _ := ParseValue(val.Expr.Left)
2021-03-01 23:01:21 +00:00
// If value is string, requote
if isStr(left) {
left = requoteStr(left.(string))
}
2021-03-01 23:01:21 +00:00
// Parse value of right side of expression
right, _ := ParseValue(val.Expr.Right)
2021-03-01 23:01:21 +00:00
// If value is string, requote
if isStr(right) {
right = requoteStr(right.(string))
}
2021-03-01 23:01:21 +00:00
// Create string expression from halves and operator
exp := fmt.Sprintf(
"%v %s %v",
left,
val.Expr.Op,
right,
)
2021-03-01 23:01:21 +00:00
// Compile string expression
program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**"))
if err != nil {
return nil, err
}
2021-03-01 23:01:21 +00:00
// Run expression
out, err := expr.Run(program, Vars)
if err != nil {
return nil, err
}
2021-03-01 23:01:21 +00:00
// Return expression output value
return out, nil
}
return nil, nil
}
// Add quotes to an unquoted string
func requoteStr(s string) string {
2021-03-01 23:01:21 +00:00
// Return quoted string
return `"` + s + `"`
}
// Check if i is a string
func isStr(i interface{}) bool {
2021-03-01 23:01:21 +00:00
// if type of input is string, return true
if reflect.TypeOf(i).String() == "string" {
return true
2021-03-01 17:11:22 +00:00
}
return false
2021-03-01 17:11:22 +00:00
}
// 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) {
2021-03-01 23:01:21 +00:00
// Create new empty map of strings to any type
2021-03-01 17:11:22 +00:00
argMap := map[string]interface{}{}
2021-03-01 23:01:21 +00:00
// For each argument
2021-03-01 17:11:22 +00:00
for _, arg := range args {
2021-03-01 23:01:21 +00:00
// Parse value into interface{}
val, err := ParseValue(arg.Value)
if err != nil {
return nil, err
}
2021-03-01 23:01:21 +00:00
// If value is function call
if IsFuncCall(val) {
2021-03-01 23:01:21 +00:00
// Call function, setting its value as the argument's value
argMap[arg.Key], err = CallFunction(val.(*FuncCall))
if err != nil {
return nil, err
}
2021-03-01 23:01:21 +00:00
// Skip further code and start next loop
continue
}
2021-03-01 23:01:21 +00:00
// Set argument value to parsed value
argMap[arg.Key] = val
2021-03-01 17:11:22 +00:00
}
2021-03-01 23:01:21 +00:00
// Return map of arguments
return argMap, nil
2021-03-01 17:11:22 +00:00
}
// IsFuncCall checks if val is a FuncCall struct
func IsFuncCall(val interface{}) bool {
2021-03-01 23:01:21 +00:00
// If type of val contains .FuncCall, return true
if strings.Contains(reflect.TypeOf(val).String(), ".FuncCall") {
return true
}
return false
2021-03-01 17:11:22 +00:00
}
// CallFunction executes a given function call in the form of
// a FuncCall struct
func CallFunction(call *FuncCall) (interface{}, error) {
2021-03-01 23:01:21 +00:00
// Unwrap provided arguments
argMap, err := UnwrapArgs(call.Args)
if err != nil {
return nil, err
2021-03-01 17:11:22 +00:00
}
2021-03-01 23:01:21 +00:00
// Attempt to get function from Funcs map
fn, ok := Funcs[call.Name]
2021-03-01 17:11:22 +00:00
if !ok {
return nil, errors.New("no such function: " + call.Name)
2021-03-01 17:11:22 +00:00
}
2021-03-01 23:01:21 +00:00
// Return value received from function
return fn(argMap)
2021-03-01 17:11:22 +00:00
}