mirror of
https://github.com/dragonheim/gagent.git
synced 2025-01-18 02:36:27 -08:00
184 lines
3.4 KiB
Go
184 lines
3.4 KiB
Go
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()
|
|
if parser.Type == ParserTokenEOF {
|
|
break
|
|
}
|
|
|
|
switch parser.Type {
|
|
case ParserTokenVAR:
|
|
v, ok := interp.Variable(token)
|
|
if !ok {
|
|
return "", fmt.Errorf("no such variable '%s'", token)
|
|
}
|
|
token = string(v)
|
|
case ParserTokenCMD:
|
|
result, err = interp.Eval(token)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
token = result
|
|
case ParserTokenESC:
|
|
/*
|
|
* TODO: escape handling missing!
|
|
*/
|
|
case ParserTokenSEP:
|
|
// prevType = parser.Type
|
|
continue
|
|
}
|
|
|
|
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?
|
|
*/
|
|
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
|
|
}
|