Add comments and remove debug code

This commit is contained in:
Elara 2021-03-01 15:01:21 -08:00
parent 5fed3e3ce4
commit a95d054189
3 changed files with 69 additions and 7 deletions

12
ast.go
View File

@ -15,25 +15,36 @@ type AST struct {
// containing the position at which the error was encountered and the error // containing the position at which the error was encountered and the error
// itself // itself
func (ast *AST) Execute() error { func (ast *AST) Execute() error {
// For each command in AST
for _, cmd := range ast.Commands { for _, cmd := range ast.Commands {
// If parsing variables
if cmd.Vars != nil { if cmd.Vars != nil {
// For each variable
for _, Var := range cmd.Vars { for _, Var := range cmd.Vars {
// Parse value of variable
val, err := ParseValue(Var.Value) val, err := ParseValue(Var.Value)
if err != nil { if err != nil {
return fmt.Errorf("%s: %s", Var.Value.Pos, err) return fmt.Errorf("%s: %s", Var.Value.Pos, err)
} }
// If value of variable is a function call
if IsFuncCall(val) { if IsFuncCall(val) {
// Assert type of val as *FuncCall
Call := val.(*FuncCall) Call := val.(*FuncCall)
// Set variable value to function return value
Vars[Var.Key], err = CallFunction(Call) Vars[Var.Key], err = CallFunction(Call)
if err != nil { if err != nil {
return fmt.Errorf("%s: %s", Var.Value.Pos, err) return fmt.Errorf("%s: %s", Var.Value.Pos, err)
} }
} else { } else {
// If value is not a function call, set variable value to parsed value
Vars[Var.Key] = val Vars[Var.Key] = val
} }
} }
// If parsing function calls
} else if cmd.Calls != nil { } else if cmd.Calls != nil {
// For each function call
for _, Call := range cmd.Calls { for _, Call := range cmd.Calls {
// Attempt to call function
_, err := CallFunction(Call) _, err := CallFunction(Call)
if err != nil { if err != nil {
return fmt.Errorf("%s: %s", Call.Pos, err) return fmt.Errorf("%s: %s", Call.Pos, err)
@ -91,6 +102,7 @@ type Bool bool
// Capture parses a boolean literal encountered in the script into // Capture parses a boolean literal encountered in the script into
// a Go boolean value // a Go boolean value
func (b *Bool) Capture(values []string) error { func (b *Bool) Capture(values []string) error {
// Convert string to boolean
*b = values[0] == "true" *b = values[0] == "true"
return nil return nil
} }

View File

@ -8,47 +8,62 @@ import (
"os/exec" "os/exec"
) )
// Default function to display a dialog
func displayDialog(args map[string]interface{}) (interface{}, error) { func displayDialog(args map[string]interface{}) (interface{}, error) {
// Get title
title, ok := args["title"] title, ok := args["title"]
if !ok { if !ok {
return nil, errors.New("title not provided") return nil, errors.New("title not provided")
} }
// Get unnamed argument as text
text, ok := args[""] text, ok := args[""]
if !ok { if !ok {
return nil, errors.New("text not provided") return nil, errors.New("text not provided")
} }
// Display correct dialog based on given type
switch args["type"] { switch args["type"] {
case "yesno": case "yesno":
// Display yes or no dialog, returning bool based on user input
return dlgs.Question(fmt.Sprint(title), fmt.Sprint(text), true) return dlgs.Question(fmt.Sprint(title), fmt.Sprint(text), true)
case "info": case "info":
// Display info dialog, returning bool based on success
return dlgs.Info(fmt.Sprint(title), fmt.Sprint(text)) return dlgs.Info(fmt.Sprint(title), fmt.Sprint(text))
case "error": case "error":
// Display error dialog, returning bool based on success
return dlgs.Error(fmt.Sprint(title), fmt.Sprint(text)) return dlgs.Error(fmt.Sprint(title), fmt.Sprint(text))
case "entry": case "entry":
// Check if default text given
defaultText, ok := args["default"] defaultText, ok := args["default"]
if !ok { if !ok {
// Set to empty if not given
defaultText = "" defaultText = ""
} }
// Display entry dialog
input, _, err := dlgs.Entry(fmt.Sprint(title), fmt.Sprint(text), fmt.Sprint(defaultText)) input, _, err := dlgs.Entry(fmt.Sprint(title), fmt.Sprint(text), fmt.Sprint(defaultText))
// Return user input
return input, err return input, err
default: default:
// If type unknown, return error
return nil, fmt.Errorf("unknown dialog type: %v", args["type"]) return nil, fmt.Errorf("unknown dialog type: %v", args["type"])
} }
} }
// Default function to run a shell script using `sh -c`
func doShellScript(args map[string]interface{}) (interface{}, error) { func doShellScript(args map[string]interface{}) (interface{}, error) {
// Get unnamed argument and assert its type as string
script, ok := args[""].(string) script, ok := args[""].(string)
// If assertion successful
if ok { if ok {
// Create new exec.Cmd containing `sh -c <script>`
cmd := exec.Command("sh", "-c", script) cmd := exec.Command("sh", "-c", script)
// Set command I/O
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
// Run command
_ = cmd.Run() _ = cmd.Run()
return "", nil return "", nil
} else { } else {
return nil, errors.New("script not provided") return nil, errors.New("script not provided")
} }
}
func print(args map[string]interface{}) (interface{}, error) {
fmt.Println(args)
return nil, nil
} }

39
scpt.go
View File

@ -1,3 +1,5 @@
// Package scpt provides an interpreter for my simple applescript-like
// scripting language
package scpt package scpt
import ( import (
@ -20,12 +22,12 @@ type FuncMap map[string]func(map[string]interface{}) (interface{}, error)
var Funcs = FuncMap{ var Funcs = FuncMap{
"display-dialog": displayDialog, "display-dialog": displayDialog,
"do-shell-script": doShellScript, "do-shell-script": doShellScript,
"print": print,
} }
// AddFuncs adds all functions from the provided FuncMap into // AddFuncs adds all functions from the provided FuncMap into
// the Funcs variable // the Funcs variable
func AddFuncs(fnMap FuncMap) { func AddFuncs(fnMap FuncMap) {
// Add each function to Funcs
for name, fn := range fnMap { for name, fn := range fnMap {
Funcs[name] = fn Funcs[name] = fn
} }
@ -35,62 +37,81 @@ func AddFuncs(fnMap FuncMap) {
// the Vars variable // the Vars variable
func AddVars(varMap map[string]interface{}) { func AddVars(varMap map[string]interface{}) {
for name, val := range varMap { for name, val := range varMap {
// Add each variable to Vars
Vars[name] = val Vars[name] = val
} }
} }
// Parse uses participle to parse a script from r into a new AST // Parse uses participle to parse a script from r into a new AST
func Parse(r io.Reader) (*AST, error) { func Parse(r io.Reader) (*AST, error) {
// Build parser from empty AST struct
parser, err := participle.Build(&AST{}) parser, err := participle.Build(&AST{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Create new empty AST struct
ast := &AST{} ast := &AST{}
// Parse script from provided reader into ast
err = parser.Parse(r, ast) err = parser.Parse(r, ast)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Return filled AST struct
return ast, nil return ast, nil
} }
// ParseValue parses a Value struct into a go value // ParseValue parses a Value struct into a go value
func ParseValue(val *Value) (interface{}, error) { func ParseValue(val *Value) (interface{}, error) {
// Determine which value was provided and return it
if val.String != nil { if val.String != nil {
// Return unquoted string
return strings.Trim(*val.String, `"`), nil return strings.Trim(*val.String, `"`), nil
} else if val.Bool != nil { } else if val.Bool != nil {
// Return dereferenced boolean
return *val.Bool, nil return *val.Bool, nil
} else if val.Float != nil { } else if val.Float != nil {
// Return dereferenced float
return *val.Float, nil return *val.Float, nil
} else if val.Integer != nil { } else if val.Integer != nil {
// Return dereferenced integer
return *val.Integer, nil return *val.Integer, nil
} else if val.SubCmd != nil { } else if val.SubCmd != nil {
// Return reference to subcommand
return val.SubCmd, nil return val.SubCmd, nil
} else if val.VarVal != nil { } else if val.VarVal != nil {
// Return value of provided key
return Vars[*val.VarVal], nil return Vars[*val.VarVal], nil
} else if val.Expr != nil { } else if val.Expr != nil {
// Parse value of left side of expression
left, _ := ParseValue(val.Expr.Left) left, _ := ParseValue(val.Expr.Left)
// If value is string, requote
if isStr(left) { if isStr(left) {
left = requoteStr(left.(string)) left = requoteStr(left.(string))
} }
// Parse value of right side of expression
right, _ := ParseValue(val.Expr.Right) right, _ := ParseValue(val.Expr.Right)
// If value is string, requote
if isStr(right) { if isStr(right) {
right = requoteStr(right.(string)) right = requoteStr(right.(string))
} }
// Create string expression from halves and operator
exp := fmt.Sprintf( exp := fmt.Sprintf(
"%v %s %v", "%v %s %v",
left, left,
val.Expr.Op, val.Expr.Op,
right, right,
) )
fmt.Println(exp) // Compile string expression
program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**")) program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Run expression
out, err := expr.Run(program, Vars) out, err := expr.Run(program, Vars)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Return expression output value
return out, nil return out, nil
} }
return nil, nil return nil, nil
@ -98,11 +119,13 @@ func ParseValue(val *Value) (interface{}, error) {
// Add quotes to an unquoted string // Add quotes to an unquoted string
func requoteStr(s string) string { func requoteStr(s string) string {
// Return quoted string
return `"` + s + `"` return `"` + s + `"`
} }
// Check if i is a string // Check if i is a string
func isStr(i interface{}) bool { func isStr(i interface{}) bool {
// if type of input is string, return true
if reflect.TypeOf(i).String() == "string" { if reflect.TypeOf(i).String() == "string" {
return true return true
} }
@ -113,26 +136,35 @@ func isStr(i interface{}) bool {
// storing the argument name and its value. If the argument has // storing the argument name and its value. If the argument has
// no name, its key will be an empty string // no name, its key will be an empty string
func UnwrapArgs(args []*Arg) (map[string]interface{}, error) { func UnwrapArgs(args []*Arg) (map[string]interface{}, error) {
// Create new empty map of strings to any type
argMap := map[string]interface{}{} argMap := map[string]interface{}{}
// For each argument
for _, arg := range args { for _, arg := range args {
// Parse value into interface{}
val, err := ParseValue(arg.Value) val, err := ParseValue(arg.Value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If value is function call
if IsFuncCall(val) { if IsFuncCall(val) {
// Call function, setting its value as the argument's value
argMap[arg.Key], err = CallFunction(val.(*FuncCall)) argMap[arg.Key], err = CallFunction(val.(*FuncCall))
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Skip further code and start next loop
continue continue
} }
// Set argument value to parsed value
argMap[arg.Key] = val argMap[arg.Key] = val
} }
// Return map of arguments
return argMap, nil return argMap, nil
} }
// IsFuncCall checks if val is a FuncCall struct // IsFuncCall checks if val is a FuncCall struct
func IsFuncCall(val interface{}) bool { func IsFuncCall(val interface{}) bool {
// If type of val contains .FuncCall, return true
if strings.Contains(reflect.TypeOf(val).String(), ".FuncCall") { if strings.Contains(reflect.TypeOf(val).String(), ".FuncCall") {
return true return true
} }
@ -142,13 +174,16 @@ func IsFuncCall(val interface{}) bool {
// CallFunction executes a given function call in the form of // CallFunction executes a given function call in the form of
// a FuncCall struct // a FuncCall struct
func CallFunction(call *FuncCall) (interface{}, error) { func CallFunction(call *FuncCall) (interface{}, error) {
// Unwrap provided arguments
argMap, err := UnwrapArgs(call.Args) argMap, err := UnwrapArgs(call.Args)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Attempt to get function from Funcs map
fn, ok := Funcs[call.Name] fn, ok := Funcs[call.Name]
if !ok { if !ok {
return nil, errors.New("no such function: " + call.Name) return nil, errors.New("no such function: " + call.Name)
} }
// Return value received from function
return fn(argMap) return fn(argMap)
} }