scpt/scpt.go

229 lines
6.0 KiB
Go
Raw Normal View History

2021-03-02 01:07:35 +00:00
/*
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.
*/
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) {
// Build parser from empty AST struct with custom lexer
parser, err := participle.Build(
&AST{},
participle.Lexer(scptLexer),
participle.Elide("Whitespace", "Comment"),
)
2021-03-01 17:11:22 +00:00
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 {
// Return dereferenced Bool converted to bool
return bool(*val.Bool), nil
} else if val.Number != nil {
2021-03-01 23:01:21 +00:00
// Return dereferenced float
return *val.Number, 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, _ := callIfFunc(ParseValue(val.Expr.Left))
2021-03-01 23:01:21 +00:00
// 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,
)
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
}
// 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) {
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 {
// If type of val is a pointer to FuncCall, return true
if reflect.TypeOf(val) == reflect.TypeOf(&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
}