230 lines
6.0 KiB
Go
230 lines
6.0 KiB
Go
/*
|
|
Copyright (c) 2021 Arsen Musayelyan
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
*/
|
|
|
|
// 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"
|
|
)
|
|
|
|
// Vars stores any variables set during script runtime
|
|
var Vars = map[string]interface{}{}
|
|
|
|
// FuncMap is a map of strings mapped to suitable script functions
|
|
type FuncMap map[string]func(map[string]interface{}) (interface{}, error)
|
|
|
|
// Funcs stores the functions allowed for use in a script
|
|
var Funcs = FuncMap{
|
|
"display-dialog": displayDialog,
|
|
"do-shell-script": doShellScript,
|
|
"string": toString,
|
|
}
|
|
|
|
// 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 {
|
|
// Return value of provided key
|
|
return Vars[*val.VarVal], nil
|
|
} else if val.Expr != nil {
|
|
// Parse value of left side of expression
|
|
left, _ := callIfFunc(ParseValue(val.Expr.Left))
|
|
// If value is string, requote
|
|
if isStr(left) {
|
|
left = requoteStr(left.(string))
|
|
}
|
|
// Create new nil string
|
|
var right string
|
|
// For every right segment
|
|
for _, segment := range val.Expr.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 = requoteStr(rVal.(string))
|
|
}
|
|
// 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
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
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
|
|
if reflect.TypeOf(val) == reflect.TypeOf(&FuncCall{}) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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)
|
|
}
|