Implement functions, arrays, maps, and while loops. Document and clean up code.
This commit is contained in:
parent
201030ed93
commit
53e0717b91
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
|||||||
scpt
|
/scpt
|
||||||
.idea/
|
.idea/
|
377
ast.go
377
ast.go
@ -20,6 +20,9 @@ import (
|
|||||||
"github.com/alecthomas/participle/lexer"
|
"github.com/alecthomas/participle/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var loopRunning bool
|
||||||
|
var breakLoop bool
|
||||||
|
|
||||||
// AST stores the root of the Abstract Syntax Tree for scpt
|
// AST stores the root of the Abstract Syntax Tree for scpt
|
||||||
type AST struct {
|
type AST struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
@ -41,29 +44,195 @@ func (ast *AST) Execute() error {
|
|||||||
return nil
|
return 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
|
||||||
|
for i := 0; i < *rptLoop.Times; i++ {
|
||||||
|
// If breakLoop set to true
|
||||||
|
if breakLoop {
|
||||||
|
// Reset breakLoop
|
||||||
|
breakLoop = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
for condBool {
|
||||||
|
// If breakLoop set to true
|
||||||
|
if breakLoop {
|
||||||
|
// Reset breakLoop
|
||||||
|
breakLoop = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
// 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) {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove args variable from Vars
|
||||||
|
delete(Vars, "_args")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Parse and execute command
|
// Parse and execute command
|
||||||
func executeCmd(cmd *Command) error {
|
func executeCmd(cmd *Command) error {
|
||||||
// If parsing variables
|
// If parsing variables
|
||||||
if cmd.Vars != nil {
|
if cmd.Vars != nil {
|
||||||
// For each variable
|
// For each variable
|
||||||
for _, Var := range cmd.Vars {
|
for _, Var := range cmd.Vars {
|
||||||
// Parse value of variable
|
// Attempt to execute the variable command
|
||||||
val, err := ParseValue(Var.Value)
|
err := executeVarCmd(Var)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
|
return 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 value is not a function call, set variable value to parsed value
|
|
||||||
Vars[Var.Key] = val
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if cmd.Calls != nil {
|
} else if cmd.Calls != nil {
|
||||||
@ -78,44 +247,38 @@ func executeCmd(cmd *Command) error {
|
|||||||
} else if cmd.Ifs != nil {
|
} else if cmd.Ifs != nil {
|
||||||
// For each if statement
|
// For each if statement
|
||||||
for _, If := range cmd.Ifs {
|
for _, If := range cmd.Ifs {
|
||||||
// Get condition value
|
// Attempt to execute the if command
|
||||||
condVal, err := callIfFunc(ParseValue(If.Condition))
|
err := executeIfCmd(If)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %s", If.Condition.Pos, err)
|
return 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if cmd.RptLoops != nil {
|
} else if cmd.RptLoops != nil {
|
||||||
// For each repeat loop
|
// For each repeat loop
|
||||||
for _, RptLoop := range cmd.RptLoops {
|
for _, RptLoop := range cmd.RptLoops {
|
||||||
for i:=0;i<*RptLoop.Times;i++ {
|
// Attempt to execute the repeat loop
|
||||||
if RptLoop.IndexVar != nil {
|
err := executeRptLoop(RptLoop)
|
||||||
Vars[*RptLoop.IndexVar] = i
|
if err != nil {
|
||||||
}
|
return err
|
||||||
for _, InnerCmd := range RptLoop.InnerCmds {
|
}
|
||||||
// Execute command recursively
|
}
|
||||||
err := executeCmd(InnerCmd)
|
} else if cmd.WhlLoops != nil {
|
||||||
if err != nil {
|
// For each while loop
|
||||||
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
|
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
|
||||||
}
|
}
|
||||||
delete(Vars, *RptLoop.IndexVar)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -123,27 +286,38 @@ func executeCmd(cmd *Command) error {
|
|||||||
|
|
||||||
// Command stores any commands encountered while parsing a script
|
// Command stores any commands encountered while parsing a script
|
||||||
type Command struct {
|
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
|
Pos lexer.Position
|
||||||
Tokens []lexer.Token
|
String *string ` @String`
|
||||||
Vars []*Var `( @@`
|
Number *float64 `| @Number`
|
||||||
Ifs []*If `| @@`
|
Bool *Bool `| @("true" | "false")`
|
||||||
RptLoops []*RptLoop`| @@`
|
SubCmd *FuncCall `| "(" @@ ")"`
|
||||||
Calls []*FuncCall `| @@)`
|
VarVal *VarVal `| @@`
|
||||||
|
Expr *Expression `| "{" @@ "}"`
|
||||||
|
Map []*MapKVPair `| "[" (@@ ("," @@)* )? "]"`
|
||||||
|
Array []*Value `| "[" (@@ ("," @@)* )? "]"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// If stores any if statements encountered while parsing a script
|
// Bool stores boolean values encountered while parsing a script.
|
||||||
type If struct {
|
// It is required for the Capture method
|
||||||
Pos lexer.Position
|
type Bool bool
|
||||||
Condition *Value `"if" @@ "{"`
|
|
||||||
InnerCmds []*Command `@@* "}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RptLoop stores any repeat loops encountered while parsing a script
|
// Capture parses a boolean literal encountered in the script into
|
||||||
type RptLoop struct {
|
// a Go boolean value
|
||||||
Pos lexer.Position
|
func (b *Bool) Capture(values []string) error {
|
||||||
Times *int `"repeat" @Number "times" "{"`
|
// Convert string to boolean
|
||||||
IndexVar *string `(@Ident "in")?`
|
*b = values[0] == "true"
|
||||||
InnerCmds []*Command `@@* "}"`
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FuncCall stores any function calls encountered while parsing a script
|
// FuncCall stores any function calls encountered while parsing a script
|
||||||
@ -160,34 +334,10 @@ type Arg struct {
|
|||||||
Value *Value `@@`
|
Value *Value `@@`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Var stores any variables encountered while parsing a script
|
// VarVal stores any references to a variable encountered while parsing a script
|
||||||
type Var struct {
|
type VarVal struct {
|
||||||
Pos lexer.Position
|
Name *string `"$" @Ident`
|
||||||
Key string `"set" @Ident "to"`
|
Index *Value `("[" @@ "]")?`
|
||||||
Value *Value `@@`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 *string `| "$" @Ident`
|
|
||||||
Expr *Expression `| "{" @@ "}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expression stores any expressions encountered while parsing a
|
// Expression stores any expressions encountered while parsing a
|
||||||
@ -203,3 +353,46 @@ type ExprRightSeg struct {
|
|||||||
Op string `@Operator`
|
Op string `@Operator`
|
||||||
Right *Value `@@`
|
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 `@@* "}"`
|
||||||
|
}
|
@ -80,4 +80,4 @@ func doShellScript(args map[string]interface{}) (interface{}, error) {
|
|||||||
} else {
|
} else {
|
||||||
return nil, errors.New("script not provided")
|
return nil, errors.New("script not provided")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func main() {
|
|||||||
log.Fatalln("Error parsing file:", err)
|
log.Fatalln("Error parsing file:", err)
|
||||||
}
|
}
|
||||||
scpt.AddFuncs(scpt.FuncMap{
|
scpt.AddFuncs(scpt.FuncMap{
|
||||||
"print": scptPrint,
|
"print": scptPrint,
|
||||||
"display-dialog": displayDialog,
|
"display-dialog": displayDialog,
|
||||||
"do-shell-script": doShellScript,
|
"do-shell-script": doShellScript,
|
||||||
})
|
})
|
||||||
@ -43,4 +43,4 @@ func scptPrint(args map[string]interface{}) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
fmt.Println(val)
|
fmt.Println(val)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
38
defaults.go
38
defaults.go
@ -20,6 +20,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Default function to convert unnamed argument to a string using fmt.Sprint
|
||||||
func toString(args map[string]interface{}) (interface{}, error) {
|
func toString(args map[string]interface{}) (interface{}, error) {
|
||||||
val, ok := args[""]
|
val, ok := args[""]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -28,6 +29,7 @@ func toString(args map[string]interface{}) (interface{}, error) {
|
|||||||
return fmt.Sprint(val), nil
|
return fmt.Sprint(val), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default function to parse unnamed argument to a number using strconv.ParseFloat
|
||||||
func parseNumber(args map[string]interface{}) (interface{}, error) {
|
func parseNumber(args map[string]interface{}) (interface{}, error) {
|
||||||
val, ok := args[""].(string)
|
val, ok := args[""].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -36,10 +38,44 @@ func parseNumber(args map[string]interface{}) (interface{}, error) {
|
|||||||
return strconv.ParseFloat(val, 64)
|
return strconv.ParseFloat(val, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default function to parse unnamed argument to a boolean using strconv.ParseBool
|
||||||
func parseBool(args map[string]interface{}) (interface{}, error) {
|
func parseBool(args map[string]interface{}) (interface{}, error) {
|
||||||
val, ok := args[""].(string)
|
val, ok := args[""].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("no value provided")
|
return nil, errors.New("no value provided")
|
||||||
}
|
}
|
||||||
return strconv.ParseBool(val)
|
return strconv.ParseBool(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default function to set the breakLoop variable to true, breaking any loops that may be running
|
||||||
|
func setBreakLoop(_ map[string]interface{}) (interface{}, error) {
|
||||||
|
// If a loop is running
|
||||||
|
if loopRunning {
|
||||||
|
// Set breakLoop to true, breaking the loop on next cycle
|
||||||
|
breakLoop = true
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("break not inside loop")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default function that returns an array with an appended element
|
||||||
|
func appendArray(args map[string]interface{}) (interface{}, error) {
|
||||||
|
// Attempt to get unnamed argument and assert as []interface{}
|
||||||
|
val, ok := args[""].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("cannot append to non-array object")
|
||||||
|
}
|
||||||
|
// Attempt to get items argument and assert as []interface{}
|
||||||
|
items, ok := args["items"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("items argument invalid or not provided")
|
||||||
|
}
|
||||||
|
// For every item in items argument
|
||||||
|
for _, item := range items {
|
||||||
|
// Append to unnamed argument
|
||||||
|
val = append(val, item)
|
||||||
|
}
|
||||||
|
// Return appended unnamed argument
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
18
lexer.go
18
lexer.go
@ -1,3 +1,17 @@
|
|||||||
|
/*
|
||||||
|
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
|
package scpt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -7,10 +21,10 @@ import (
|
|||||||
|
|
||||||
// Create custom stateful regex lexer
|
// Create custom stateful regex lexer
|
||||||
var scptLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
|
var scptLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
|
||||||
{"Ident", `[a-zA-Z]\w*`, nil},
|
{"Ident", `[a-zA-Z_]\w*`, nil},
|
||||||
{"String", `"[^"]*"`, nil},
|
{"String", `"[^"]*"`, nil},
|
||||||
{"Number", `(?:\d*\.)?\d+`, nil},
|
{"Number", `(?:\d*\.)?\d+`, nil},
|
||||||
{"Punct", `[-[!@$&()_{}\|:;"',.?/]|]`, nil},
|
{"Punct", `[-[!@$&(){}\|:;"',.?/]|]`, nil},
|
||||||
{"Whitespace", `[ \t\r\n]+`, nil},
|
{"Whitespace", `[ \t\r\n]+`, nil},
|
||||||
{"Comment", `(###(.|\n)+###|#[^\n]+)`, nil},
|
{"Comment", `(###(.|\n)+###|#[^\n]+)`, nil},
|
||||||
{"Operator", `(>=|<=|>|<|==|!=)|[-+*/^%]`, nil},
|
{"Operator", `(>=|<=|>|<|==|!=)|[-+*/^%]`, nil},
|
||||||
|
111
scpt.go
111
scpt.go
@ -34,9 +34,11 @@ type FuncMap map[string]func(map[string]interface{}) (interface{}, error)
|
|||||||
|
|
||||||
// Funcs stores the functions allowed for use in a script
|
// Funcs stores the functions allowed for use in a script
|
||||||
var Funcs = FuncMap{
|
var Funcs = FuncMap{
|
||||||
"str": toString,
|
"str": toString,
|
||||||
"num": parseNumber,
|
"num": parseNumber,
|
||||||
"bool": parseBool,
|
"bool": parseBool,
|
||||||
|
"break": setBreakLoop,
|
||||||
|
"append": appendArray,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFuncs adds all functions from the provided FuncMap into
|
// AddFuncs adds all functions from the provided FuncMap into
|
||||||
@ -95,11 +97,89 @@ func ParseValue(val *Value) (interface{}, error) {
|
|||||||
// Return reference to subcommand
|
// 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
|
// If variable access contains index
|
||||||
return Vars[*val.VarVal], nil
|
if val.VarVal.Index != nil {
|
||||||
|
// Get index value
|
||||||
|
index, err := callIfFunc(ParseValue(val.VarVal.Index))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Get requested variable and attempt to assert as []interface{}
|
||||||
|
slc, ok := Vars[*val.VarVal.Name].([]interface{})
|
||||||
|
// If assertion successful
|
||||||
|
if ok {
|
||||||
|
// Attempt to assert index as a 64-bit float
|
||||||
|
indexFlt, ok := index.(float64)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("array index must be a number")
|
||||||
|
}
|
||||||
|
// If requested index is out of range, return error
|
||||||
|
if int64(len(slc)) <= int64(indexFlt) {
|
||||||
|
return nil, fmt.Errorf("index %d is out of range with length %d", *val.VarVal.Index, len(slc))
|
||||||
|
}
|
||||||
|
// Return value at requested index of requested variable
|
||||||
|
return slc[int64(indexFlt)], nil
|
||||||
|
} else {
|
||||||
|
// If assertion unsuccessful, attempt to assert as a map[interface{}]interface{}
|
||||||
|
iMap, ok := Vars[*val.VarVal.Name].(map[interface{}]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("variable " + *val.VarVal.Name + " does not exist or is not a map")
|
||||||
|
}
|
||||||
|
// Attempt to get value at requested key
|
||||||
|
val, ok := iMap[index]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("index %v does not exist in map", index)
|
||||||
|
}
|
||||||
|
// Return value at key with no error
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If index is absent, attempt to get variable value from Vars
|
||||||
|
value, ok := Vars[*val.VarVal.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("variable " + *val.VarVal.Name + " does not exist")
|
||||||
|
}
|
||||||
|
// Return value with no error
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
} else if val.Expr != nil {
|
} else if val.Expr != nil {
|
||||||
// Return evaluated expression
|
// If value is an expression, return evaluated expression
|
||||||
return evalExpr(*val.Expr)
|
return evalExpr(*val.Expr)
|
||||||
|
} else if val.Array != nil {
|
||||||
|
// If value is an array, create new nil []interface{}
|
||||||
|
var iSlice []interface{}
|
||||||
|
// For each value in array
|
||||||
|
for _, value := range val.Array {
|
||||||
|
// Recursively parse value
|
||||||
|
iVal, err := ParseValue(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Append value to []interface{}
|
||||||
|
iSlice = append(iSlice, iVal)
|
||||||
|
}
|
||||||
|
// Return []interface{]
|
||||||
|
return iSlice, nil
|
||||||
|
} else if val.Map != nil {
|
||||||
|
// If value is a map, create new empty map[interface{}]interface{}
|
||||||
|
iMap := map[interface{}]interface{}{}
|
||||||
|
// For each value in map
|
||||||
|
for _, value := range val.Map {
|
||||||
|
// Recursively parse value
|
||||||
|
iVal, err := ParseValue(value.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Recursively parse key
|
||||||
|
iKey, err := ParseValue(value.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set key of map to value
|
||||||
|
iMap[iKey] = iVal
|
||||||
|
}
|
||||||
|
// Return map[interface{}]interface{}
|
||||||
|
return iMap, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -110,7 +190,7 @@ func evalExpr(expression Expression) (interface{}, error) {
|
|||||||
left, _ := callIfFunc(ParseValue(expression.Left))
|
left, _ := callIfFunc(ParseValue(expression.Left))
|
||||||
// If value is string, requote
|
// If value is string, requote
|
||||||
if isStr(left) {
|
if isStr(left) {
|
||||||
left = requoteStr(left.(string))
|
left = quoteStr(left.(string))
|
||||||
}
|
}
|
||||||
// Create new nil string
|
// Create new nil string
|
||||||
var right string
|
var right string
|
||||||
@ -120,7 +200,7 @@ func evalExpr(expression Expression) (interface{}, error) {
|
|||||||
rVal, _ := callIfFunc(ParseValue(segment.Right))
|
rVal, _ := callIfFunc(ParseValue(segment.Right))
|
||||||
// If value is string, requote
|
// If value is string, requote
|
||||||
if isStr(rVal) {
|
if isStr(rVal) {
|
||||||
rVal = requoteStr(rVal.(string))
|
rVal = quoteStr(rVal)
|
||||||
}
|
}
|
||||||
// Append right segment to right string
|
// Append right segment to right string
|
||||||
right = right + fmt.Sprintf(
|
right = right + fmt.Sprintf(
|
||||||
@ -150,13 +230,22 @@ func evalExpr(expression Expression) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add quotes to an unquoted string
|
// Add quotes to an unquoted string
|
||||||
func requoteStr(s string) string {
|
func quoteStr(s interface{}) string {
|
||||||
// Return quoted string
|
// If s is nil
|
||||||
return `"` + s + `"`
|
if s == nil {
|
||||||
|
// Return empty quotes
|
||||||
|
return `""`
|
||||||
|
} else {
|
||||||
|
// Otherwise return formatted string using %v (any value)
|
||||||
|
return fmt.Sprintf(`"%v"`, s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if i is a string
|
// Check if i is a string
|
||||||
func isStr(i interface{}) bool {
|
func isStr(i interface{}) bool {
|
||||||
|
if i == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
// if type of input is string, return true
|
// if type of input is string, return true
|
||||||
if reflect.TypeOf(i).String() == "string" {
|
if reflect.TypeOf(i).String() == "string" {
|
||||||
return true
|
return true
|
||||||
|
34
test.scpt
34
test.scpt
@ -28,4 +28,36 @@ if (bool "true") {
|
|||||||
This is a multiline comment
|
This is a multiline comment
|
||||||
###
|
###
|
||||||
|
|
||||||
# This is a single line comment
|
# This is a single line comment
|
||||||
|
|
||||||
|
set hi to ["testovich", 3]
|
||||||
|
|
||||||
|
set hi[0] to "testo"
|
||||||
|
|
||||||
|
print $hi[0]
|
||||||
|
|
||||||
|
set hi to (append $hi with items [5, 4])
|
||||||
|
|
||||||
|
print {$hi[2] + $hi[3]}
|
||||||
|
|
||||||
|
set msi to [5: "hi", "hello": "world"]
|
||||||
|
set msi[5] to "hello"
|
||||||
|
print $msi[5]
|
||||||
|
print $msi["hello"]
|
||||||
|
|
||||||
|
set c to 0
|
||||||
|
set f to true
|
||||||
|
loop while $f {
|
||||||
|
set c to {$c + 1}
|
||||||
|
if {$c == 3} {
|
||||||
|
set f to false
|
||||||
|
}
|
||||||
|
print {"iter: " + (str $c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
define hi {
|
||||||
|
print {"Hello, " + $_args[""]}
|
||||||
|
}
|
||||||
|
|
||||||
|
hi "Function"
|
||||||
|
hi "World"
|
Loading…
Reference in New Issue
Block a user