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/
|
377
ast.go
377
ast.go
@ -20,6 +20,9 @@ import (
|
||||
"github.com/alecthomas/participle/lexer"
|
||||
)
|
||||
|
||||
var loopRunning bool
|
||||
var breakLoop bool
|
||||
|
||||
// AST stores the root of the Abstract Syntax Tree for scpt
|
||||
type AST struct {
|
||||
Pos lexer.Position
|
||||
@ -41,29 +44,195 @@ func (ast *AST) Execute() error {
|
||||
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
|
||||
func executeCmd(cmd *Command) error {
|
||||
// If parsing variables
|
||||
if cmd.Vars != nil {
|
||||
// For each variable
|
||||
for _, Var := range cmd.Vars {
|
||||
// Parse value of variable
|
||||
val, err := ParseValue(Var.Value)
|
||||
// Attempt to execute the variable command
|
||||
err := executeVarCmd(Var)
|
||||
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 value is not a function call, set variable value to parsed value
|
||||
Vars[Var.Key] = val
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if cmd.Calls != nil {
|
||||
@ -78,44 +247,38 @@ func executeCmd(cmd *Command) error {
|
||||
} else if cmd.Ifs != nil {
|
||||
// For each if statement
|
||||
for _, If := range cmd.Ifs {
|
||||
// Get condition value
|
||||
condVal, err := callIfFunc(ParseValue(If.Condition))
|
||||
// Attempt to execute the if command
|
||||
err := executeIfCmd(If)
|
||||
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 err
|
||||
}
|
||||
}
|
||||
} else if cmd.RptLoops != nil {
|
||||
// For each repeat loop
|
||||
for _, RptLoop := range cmd.RptLoops {
|
||||
for i:=0;i<*RptLoop.Times;i++ {
|
||||
if RptLoop.IndexVar != nil {
|
||||
Vars[*RptLoop.IndexVar] = i
|
||||
}
|
||||
for _, InnerCmd := range RptLoop.InnerCmds {
|
||||
// Execute command recursively
|
||||
err := executeCmd(InnerCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
|
||||
}
|
||||
}
|
||||
// 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
|
||||
}
|
||||
delete(Vars, *RptLoop.IndexVar)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -123,27 +286,38 @@ func executeCmd(cmd *Command) error {
|
||||
|
||||
// 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
|
||||
Tokens []lexer.Token
|
||||
Vars []*Var `( @@`
|
||||
Ifs []*If `| @@`
|
||||
RptLoops []*RptLoop`| @@`
|
||||
Calls []*FuncCall `| @@)`
|
||||
String *string ` @String`
|
||||
Number *float64 `| @Number`
|
||||
Bool *Bool `| @("true" | "false")`
|
||||
SubCmd *FuncCall `| "(" @@ ")"`
|
||||
VarVal *VarVal `| @@`
|
||||
Expr *Expression `| "{" @@ "}"`
|
||||
Map []*MapKVPair `| "[" (@@ ("," @@)* )? "]"`
|
||||
Array []*Value `| "[" (@@ ("," @@)* )? "]"`
|
||||
}
|
||||
|
||||
// If stores any if statements encountered while parsing a script
|
||||
type If struct {
|
||||
Pos lexer.Position
|
||||
Condition *Value `"if" @@ "{"`
|
||||
InnerCmds []*Command `@@* "}"`
|
||||
}
|
||||
// Bool stores boolean values encountered while parsing a script.
|
||||
// It is required for the Capture method
|
||||
type Bool bool
|
||||
|
||||
// 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 `@@* "}"`
|
||||
// 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
|
||||
@ -160,34 +334,10 @@ type Arg struct {
|
||||
Value *Value `@@`
|
||||
}
|
||||
|
||||
// Var stores any variables encountered while parsing a script
|
||||
type Var struct {
|
||||
Pos lexer.Position
|
||||
Key string `"set" @Ident "to"`
|
||||
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
|
||||
// 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
|
||||
@ -203,3 +353,46 @@ 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 `@@* "}"`
|
||||
}
|
@ -26,7 +26,7 @@ func main() {
|
||||
log.Fatalln("Error parsing file:", err)
|
||||
}
|
||||
scpt.AddFuncs(scpt.FuncMap{
|
||||
"print": scptPrint,
|
||||
"print": scptPrint,
|
||||
"display-dialog": displayDialog,
|
||||
"do-shell-script": doShellScript,
|
||||
})
|
||||
|
36
defaults.go
36
defaults.go
@ -20,6 +20,7 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Default function to convert unnamed argument to a string using fmt.Sprint
|
||||
func toString(args map[string]interface{}) (interface{}, error) {
|
||||
val, ok := args[""]
|
||||
if !ok {
|
||||
@ -28,6 +29,7 @@ func toString(args map[string]interface{}) (interface{}, error) {
|
||||
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) {
|
||||
val, ok := args[""].(string)
|
||||
if !ok {
|
||||
@ -36,6 +38,7 @@ func parseNumber(args map[string]interface{}) (interface{}, error) {
|
||||
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) {
|
||||
val, ok := args[""].(string)
|
||||
if !ok {
|
||||
@ -43,3 +46,36 @@ func parseBool(args map[string]interface{}) (interface{}, error) {
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
@ -7,10 +21,10 @@ import (
|
||||
|
||||
// Create custom stateful regex lexer
|
||||
var scptLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
|
||||
{"Ident", `[a-zA-Z]\w*`, nil},
|
||||
{"Ident", `[a-zA-Z_]\w*`, nil},
|
||||
{"String", `"[^"]*"`, nil},
|
||||
{"Number", `(?:\d*\.)?\d+`, nil},
|
||||
{"Punct", `[-[!@$&()_{}\|:;"',.?/]|]`, nil},
|
||||
{"Punct", `[-[!@$&(){}\|:;"',.?/]|]`, nil},
|
||||
{"Whitespace", `[ \t\r\n]+`, nil},
|
||||
{"Comment", `(###(.|\n)+###|#[^\n]+)`, 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
|
||||
var Funcs = FuncMap{
|
||||
"str": toString,
|
||||
"num": parseNumber,
|
||||
"bool": parseBool,
|
||||
"str": toString,
|
||||
"num": parseNumber,
|
||||
"bool": parseBool,
|
||||
"break": setBreakLoop,
|
||||
"append": appendArray,
|
||||
}
|
||||
|
||||
// AddFuncs adds all functions from the provided FuncMap into
|
||||
@ -95,11 +97,89 @@ func ParseValue(val *Value) (interface{}, error) {
|
||||
// Return reference to subcommand
|
||||
return val.SubCmd, nil
|
||||
} else if val.VarVal != nil {
|
||||
// Return value of provided key
|
||||
return Vars[*val.VarVal], nil
|
||||
// If variable access contains index
|
||||
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 {
|
||||
// Return evaluated expression
|
||||
// If value is an expression, return evaluated expression
|
||||
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
|
||||
}
|
||||
@ -110,7 +190,7 @@ func evalExpr(expression Expression) (interface{}, error) {
|
||||
left, _ := callIfFunc(ParseValue(expression.Left))
|
||||
// If value is string, requote
|
||||
if isStr(left) {
|
||||
left = requoteStr(left.(string))
|
||||
left = quoteStr(left.(string))
|
||||
}
|
||||
// Create new nil string
|
||||
var right string
|
||||
@ -120,7 +200,7 @@ func evalExpr(expression Expression) (interface{}, error) {
|
||||
rVal, _ := callIfFunc(ParseValue(segment.Right))
|
||||
// If value is string, requote
|
||||
if isStr(rVal) {
|
||||
rVal = requoteStr(rVal.(string))
|
||||
rVal = quoteStr(rVal)
|
||||
}
|
||||
// Append right segment to right string
|
||||
right = right + fmt.Sprintf(
|
||||
@ -150,13 +230,22 @@ func evalExpr(expression Expression) (interface{}, error) {
|
||||
}
|
||||
|
||||
// Add quotes to an unquoted string
|
||||
func requoteStr(s string) string {
|
||||
// Return quoted string
|
||||
return `"` + s + `"`
|
||||
func quoteStr(s interface{}) string {
|
||||
// If s is nil
|
||||
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
|
||||
func isStr(i interface{}) bool {
|
||||
if i == nil {
|
||||
return true
|
||||
}
|
||||
// if type of input is string, return true
|
||||
if reflect.TypeOf(i).String() == "string" {
|
||||
return true
|
||||
|
32
test.scpt
32
test.scpt
@ -29,3 +29,35 @@ This is a multiline 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