scpt/ast.go

206 lines
5.4 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 (
"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)
}
}
}
delete(Vars, *RptLoop.IndexVar)
}
}
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 `@@* "}"`
}
// 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 `@@* "}"`
}
// 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 `@@`
}