/* 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 { // 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 } } // If parsing function calls } 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 _, If := range cmd.Ifs { condVal, err := callIfFunc(ParseValue(If.Condition)) if err != nil { return err } condBool, ok := condVal.(bool) if !ok { return errors.New("condition must be a boolean") } if condBool { _, err := CallFunction(If.Action) return err } } } } return nil } // Command stores any commands encountered while parsing a script type Command struct { Pos lexer.Position Vars []*Var `( @@` Ifs []*If `| @@` Calls []*FuncCall `| @@ )` } type If struct { Pos lexer.Position Condition *Value `"if" @@` Action *FuncCall `"then" @@` } // 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` Float *float64 `| @Float` Integer *int64 `| @Int` 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 `@@*` } type ExprRightSeg struct { Op string `@( ">" | ">" "=" | "<" | "<" "=" | "!" "=" | "=" "=" | "+" | "-" | "*" | "/" | "^" | "%")` Right *Value `@@` }