/* 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 ( "errors" "fmt" "github.com/alecthomas/participle/lexer" ) // 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 } // 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) 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 } } } 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 { // 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) } } } } } 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) } } } } } return nil } // Command stores any commands encountered while parsing a script type Command struct { Pos lexer.Position Tokens []lexer.Token Vars []*Var `( @@` Ifs []*If `| @@` RptLoops []*RptLoop`| @@` Calls []*FuncCall `| @@)` } // If stores any if statements encountered while parsing a script type If struct { Pos lexer.Position Condition *Value `"if" @@ "{"` InnerCmds []*Command `@@* "}"` } type RptLoop struct { Pos lexer.Position Times *int `"repeat" @Number "times" "{"` IndexVar *string `(@Ident "in")?` InnerCmds []*Command `@@* "}"` } // 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 `@@` } // 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 } // 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 `@@` }