scpt/ast.go

437 lines
11 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
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/alecthomas/participle/lexer"
)
var loopRunning bool
var breakLoop bool
var funcRunning bool
var retValue interface{} = nil
// AST stores the root of the Abstract Syntax Tree for scpt
type AST struct {
Pos lexer.Position
Commands []*Command `@@*`
}
// Execute traverses the AST and executes any commands, it returns an error
// containing the position at which the error was encountered and the error
// itself
func (ast *AST) Execute() error {
// For each command in AST
for _, cmd := range ast.Commands {
// Execute current command
err := executeCmd(cmd)
if err != nil {
return err
}
}
return nil
}
func (ast *AST) Dump() ([]byte, error) {
return json.Marshal(ast)
}
func (ast *AST) DumpPretty() ([]byte, error) {
buf := bytes.NewBuffer([]byte{})
enc := json.NewEncoder(buf)
enc.SetIndent("", " ")
err := enc.Encode(ast)
if err != nil {
return buf.Bytes(), err
}
return buf.Bytes(), nil
}
func LoadAST(data []byte) (*AST, error) {
var ast AST
err := json.Unmarshal(data, &ast)
if err != nil {
return nil, err
}
return &ast, nil
}
// Execute a variable declaration
func executeVarCmd(Var *Var) error {
// Parse value of variable
val, err := ParseValue(Var.Value)
if err != nil {
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
}
// If value of variable is a function call
if IsFuncCall(val) {
// Assert type of val as *FuncCall
Call := val.(*FuncCall)
// Set variable value to function return value
Vars[Var.Key], err = CallFunction(Call)
if err != nil {
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
}
} else if Var.Index != nil {
// If variable definition has an associated index, get index value
index, err := callIfFunc(ParseValue(Var.Index))
if err != nil {
return fmt.Errorf("%s: %s", Var.Index.Pos, err)
}
// Attempt to get the variable from Vars and assert it as a []interface{}
slc, ok := Vars[Var.Key].([]interface{})
// If assertion successful
if ok {
// Assert index value as a 64-bit float
indexInt, ok := index.(float64)
if !ok {
return fmt.Errorf("%s: %s", Var.Pos, "variable "+Var.Key+" does not exist or is not an array")
}
// Set integer index of interface{} slice to value
slc[int64(indexInt)] = val
} else {
// If slice assertion unsuccessful, attempt to assert as map[interface{}]interface{}
iMap, ok := Vars[Var.Key].(map[interface{}]interface{})
if !ok {
return fmt.Errorf("%s: %s", Var.Pos, "variable "+Var.Key+" does not exist or is not a map")
}
// Set index of interface{} to interface{} map to value
iMap[index] = val
}
} else {
// If value is not a function call, set variable to parsed value
Vars[Var.Key] = val
}
return nil
}
// Execute an if statement
func executeIfCmd(If *If) error {
// Get condition value
condVal, err := callIfFunc(ParseValue(If.Condition))
if err != nil {
return fmt.Errorf("%s: %s", If.Condition.Pos, err)
}
// Attempt to assert condition type as bool
condBool, ok := condVal.(bool)
if !ok {
return errors.New("condition must be a boolean")
}
// If condition is true
if condBool {
// For each inner command
for _, InnerCmd := range If.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
}
}
return nil
}
// Execute a repeat loop
func executeRptLoop(rptLoop *RptLoop) error {
// Set loopRunning to true to allow break
loopRunning = true
// Run for loop with correct amount of iterations
rpt:
for i := 0; i < *rptLoop.Times; i++ {
// If user requested index variable via "{ var in ... }"
if rptLoop.IndexVar != nil {
// Set requested variable name to index
Vars[*rptLoop.IndexVar] = i
}
// For each command within the loop
for _, InnerCmd := range rptLoop.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
// If breakLoop set to true
if breakLoop {
// Reset breakLoop
breakLoop = false
break rpt
}
}
}
// Remove index variable if existent
delete(Vars, *rptLoop.IndexVar)
// Reset loopRunning
loopRunning = false
return nil
}
// Execute a while loop
func executeWhlLoop(whlLoop *WhlLoop) error {
loopRunning = true
// Get condition value
condVal, err := callIfFunc(ParseValue(whlLoop.Condition))
if err != nil {
return fmt.Errorf("%s: %s", whlLoop.Condition.Pos, err)
}
// Attempt to assert condition type as bool
condBool, ok := condVal.(bool)
if !ok {
return errors.New("condition must be a boolean")
}
// Run for loop if condition is true
whl:
for condBool {
// For each inner command
for _, InnerCmd := range whlLoop.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
// If breakLoop set to true
if breakLoop {
// Reset breakLoop
breakLoop = false
break whl
}
// Get condition value
condVal, err = callIfFunc(ParseValue(whlLoop.Condition))
if err != nil {
return fmt.Errorf("%s: %s", whlLoop.Condition.Pos, err)
}
// Attempt to assert condition type as bool and update its value
condBool, ok = condVal.(bool)
if !ok {
return errors.New("condition must be a boolean")
}
}
}
loopRunning = false
return nil
}
// Execute a function definition
func executeFuncDef(def *FuncDef) error {
// Set requested function name in Funcs
Funcs[*def.Name] = func(args map[string]interface{}) (interface{}, error) {
funcRunning = true
// Create new empty map[interface{}]interface{}
argIMap := map[interface{}]interface{}{}
// Convert args map[string]interface{} to map[interface{}]interface{}
for key, value := range args {
argIMap[key] = value
}
// Set variable _args to the args map[interface{}]interface{}
Vars["_args"] = argIMap
// For each command within the definition
for _, InnerCmd := range def.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return nil, fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
if retValue != nil {
ret := retValue
retValue = nil
funcRunning = false
return ret, nil
}
}
// Remove args variable from Vars
delete(Vars, "_args")
funcRunning = false
return nil, nil
}
return nil
}
// Parse and execute command
func executeCmd(cmd *Command) error {
// If parsing variables
if cmd.Vars != nil {
// For each variable
for _, Var := range cmd.Vars {
// Attempt to execute the variable command
err := executeVarCmd(Var)
if err != nil {
return err
}
}
} else if cmd.Calls != nil {
// For each function call
for _, Call := range cmd.Calls {
// Attempt to call function
_, err := CallFunction(Call)
if err != nil {
return fmt.Errorf("%s: %s", Call.Pos, err)
}
}
} else if cmd.Ifs != nil {
// For each if statement
for _, If := range cmd.Ifs {
// Attempt to execute the if command
err := executeIfCmd(If)
if err != nil {
return err
}
}
} else if cmd.RptLoops != nil {
// For each repeat loop
for _, RptLoop := range cmd.RptLoops {
// Attempt to execute the repeat loop
err := executeRptLoop(RptLoop)
if err != nil {
return err
}
}
} else if cmd.WhlLoops != nil {
// For each while loop
for _, WhlLoop := range cmd.WhlLoops {
// Attempt to execute the while loop
err := executeWhlLoop(WhlLoop)
if err != nil {
return err
}
}
} else if cmd.Defs != nil {
// For each function definition
for _, Def := range cmd.Defs {
// Attempt to execute the function definition
err := executeFuncDef(Def)
if err != nil {
return err
}
}
}
return nil
}
// Command stores any commands encountered while parsing a script
type Command struct {
Pos lexer.Position
Vars []*Var `( @@`
Ifs []*If `| @@`
RptLoops []*RptLoop `| @@`
WhlLoops []*WhlLoop `| @@`
Defs []*FuncDef `| @@`
Calls []*FuncCall `| @@)`
}
// Value stores any literal values encountered while parsing a script
type Value struct {
Pos lexer.Position
String *string ` @String`
Number *float64 `| @Number`
Bool *Bool `| @("true" | "false")`
SubCmd *FuncCall `| "(" @@ ")"`
VarVal *VarVal `| @@`
Expr *Expression `| "{" @@ "}"`
Map []*MapKVPair `| "[" (@@ ("," @@)* )? "]"`
Array []*Value `| "[" (@@ ("," @@)* )? "]"`
}
// Bool stores boolean values encountered while parsing a script.
// It is required for the Capture method
type Bool bool
// Capture parses a boolean literal encountered in the script into
// a Go boolean value
func (b *Bool) Capture(values []string) error {
// Convert string to boolean
*b = values[0] == "true"
return nil
}
// FuncCall stores any function calls encountered while parsing a script
type FuncCall struct {
Pos lexer.Position
Name string `@Ident @("-" Ident)*`
Args []*Arg `@@*`
}
// Arg stores arguments for function calls
type Arg struct {
Pos lexer.Position
Key string `("with" @Ident)?`
Value *Value `@@`
}
// VarVal stores any references to a variable encountered while parsing a script
type VarVal struct {
Name *string `"$" @Ident`
Index *Value `("[" @@ "]")?`
}
// Expression stores any expressions encountered while parsing a
// script for later evaluation
type Expression struct {
Pos lexer.Position
Left *Value `@@`
RightSegs []*ExprRightSeg `@@*`
}
// ExprRightSeg stores segments of the right side of an expression
type ExprRightSeg struct {
Op string `@Operator`
Right *Value `@@`
}
// MapKVPair stores any key/value pairs encountered while parsing map literals
type MapKVPair struct {
Key *Value `@@`
Value *Value `":" @@`
}
// FuncDef stores any function definitions encountered while parsing a script
type FuncDef struct {
Pos lexer.Position
Name *string `"define" @Ident "{"`
InnerCmds []*Command `@@* "}"`
}
// Var stores any variables encountered while parsing a script
type Var struct {
Pos lexer.Position
Key string `"set" @Ident`
Index *Value `("[" @@ "]")?`
Value *Value `"to" @@`
}
// If stores any if statements encountered while parsing a script
type If struct {
Pos lexer.Position
Condition *Value `"if" @@ "{"`
InnerCmds []*Command `@@* "}"`
}
// RptLoop stores any repeat loops encountered while parsing a script
type RptLoop struct {
Pos lexer.Position
Times *int `"repeat" @Number "times" "{"`
IndexVar *string `(@Ident "in")?`
InnerCmds []*Command `@@* "}"`
}
// WhlLoop stores any while loops encountered while parsing a script
type WhlLoop struct {
Pos lexer.Position
Condition *Value `"loop" "while" @@ "{"`
InnerCmds []*Command `@@* "}"`
}