gagent/pkg/picol/picol.go

185 lines
3.4 KiB
Go
Raw Permalink Normal View History

package picol
import (
"errors"
"fmt"
"strings"
)
/*
* Error variables
*/
var (
ErrReturn = errors.New("RETURN")
ErrBreak = errors.New("BREAK")
ErrContinue = errors.New("CONTINUE")
)
/*
* Variable type
*/
type Variable string
/*
* CommandFunc type
*/
type CommandFunc func(interp *Interpreter, argv []string, privdata interface{}) (string, error)
/*
* Command structure
*/
type Command struct {
fn CommandFunc
privdata interface{}
}
/*
* CallFrame structure
*/
type CallFrame struct {
vars map[string]Variable
parent *CallFrame
}
/*
* Interpreter structure
*/
type Interpreter struct {
level int
callframe *CallFrame
commands map[string]Command
}
/*
* NewInterpreter initializes a new Interpreter
*/
func NewInterpreter() *Interpreter {
return &Interpreter{
level: 0,
callframe: &CallFrame{vars: make(map[string]Variable)},
commands: make(map[string]Command),
}
}
/*
* Variable retrieves a variable's value
*/
func (interp *Interpreter) Variable(name string) (Variable, bool) {
for frame := interp.callframe; frame != nil; frame = frame.parent {
v, ok := frame.vars[name]
if ok {
return v, ok
}
}
return "", false
}
/*
* SetVariable sets a variable's value
*/
func (interp *Interpreter) SetVariable(name, val string) {
interp.callframe.vars[name] = Variable(val)
}
/*
* UnsetVariable removes a variable
*/
func (interp *Interpreter) UnsetVariable(name string) {
delete(interp.callframe.vars, name)
}
/*
* Command retrieves a command
*/
func (interp *Interpreter) Command(name string) *Command {
v, ok := interp.commands[name]
if !ok {
return nil
}
return &v
}
/*
* RegisterCommand registers a new command
*/
func (interp *Interpreter) RegisterCommand(name string, fn CommandFunc, privdata interface{}) error {
cmd := interp.Command(name)
if cmd != nil {
return fmt.Errorf("Command '%s' already defined", name)
}
interp.commands[name] = Command{fn, privdata}
return nil
}
/*
* Eval evaluates a script
*/
func (interp *Interpreter) Eval(script string) (string, error) {
parser := InitParser(script)
var result string
var err error
argv := []string{}
for {
prevType := parser.Type
token := parser.GetToken()
2023-04-03 19:43:35 -07:00
if parser.Type == ParserTokenEOF {
break
}
switch parser.Type {
2023-04-03 19:43:35 -07:00
case ParserTokenVAR:
v, ok := interp.Variable(token)
if !ok {
return "", fmt.Errorf("no such variable '%s'", token)
}
token = string(v)
2023-04-03 19:43:35 -07:00
case ParserTokenCMD:
result, err = interp.Eval(token)
if err != nil {
return result, err
}
token = result
2023-04-03 19:43:35 -07:00
case ParserTokenESC:
/*
* TODO: escape handling missing!
*/
2023-04-03 19:43:35 -07:00
case ParserTokenSEP:
// prevType = parser.Type
continue
}
2023-04-03 19:43:35 -07:00
if parser.Type == ParserTokenEOL {
// prevType = parser.Type
if len(argv) != 0 {
cmd := interp.Command(argv[0])
if cmd == nil {
return "", fmt.Errorf("no such command '%s'", argv[0])
}
result, err = cmd.fn(interp, argv, cmd.privdata)
if err != nil {
return result, err
}
}
/*
* Prepare for the next command
*/
argv = []string{}
continue
}
/*
* We have a new token, append to the previous or as new arg?
*/
2023-04-03 19:43:35 -07:00
if prevType == ParserTokenSEP || prevType == ParserTokenEOL {
argv = append(argv, token)
} else { // Interpolation
argv[len(argv)-1] = strings.Join([]string{argv[len(argv)-1], token}, "")
}
// prevType = parser.Type
}
return result, nil
}