catbase/plugins/catbasic/basic/basic.go

847 lines
19 KiB
Go

// Package basic implements a very simple, embedded BASIC interpreter.
// The interpreter is extendable by defining custom
// statements, unary and binary operators, and functions.
package basic
import (
"bufio"
"errors"
"fmt"
"io"
"math/big"
"reflect"
"strconv"
"strings"
"sync"
"unicode"
"unicode/utf8"
)
type Lang struct {
Cmds []CmdDef
BinOps [][]OpDef
UnOps []OpDef
}
type CmdDef struct {
// Name is the name of the command.
// The name is formed by space-delimited keywords
// that define all keywords in the command.
// The first keyword in the Name begins all statements for this command.
// Subsequent keywords appear between arguments of the command
// as defined by the arguments of the Exec function.
// By convention, keywords are all CAPITAL.
Name string
// Doc describes the command.
Doc string
// Eval must be a func type.
//
// The first parameter type must be *Interp.
// The following parameters must be of the following types:
// Value, indicating the argument must be an expression;
// []Value, indicating the argument must be an expression list.
//
// The number of parameters, besides the leading *Interp,
// must be equal to or one grater than
// the number of keywords in the command Name.
//
// There must be no return or the return must be an error.
Eval interface{}
}
var argTypes = map[reflect.Type]argType{
reflect.TypeOf([]Value{}).Elem(): {
Name: "Expr",
Parse: func(interp *Interp, input *string) (node, error) {
return parseExpr(interp, input)
},
},
reflect.TypeOf([]Value{}): {
Name: "ExprList",
Parse: func(interp *Interp, input *string) (node, error) {
return parseExprList(interp, input)
},
},
reflect.TypeOf(Variable{}): {
Name: "ExprList",
Parse: func(interp *Interp, input *string) (node, error) {
return parseVar(input)
},
},
}
type OpDef struct {
Op string
Doc string
// TODO: comment
// binop:
// func with 2 arguments either both Number, both String, or both Value,
// returns a Value or a Value and error.
// unop:
// func with 1 argument either Number, String, or Value,
// returns a Value or a Value and error.
Eval interface{}
}
type Interp struct {
cmds []cmd
bins [][]binop
uns []unop
mu sync.Mutex
end bool
pos Pos
immediate line
prog []line
vars map[string]*Value
stack []interface{}
}
type Pos struct {
line int
stmt int
}
type cmd struct {
kwds []string
argTypes []argType
eval reflect.Value
}
type argType struct {
Name string
Parse func(*Interp, *string) (node, error)
}
type binop struct {
op string
ltype, rtype reflect.Type
eval reflect.Value
}
type unop struct {
op string
typ reflect.Type
eval reflect.Value
}
type line struct {
num int
src string
stmts []stmt
}
type stmt struct {
cmd *cmd
nodes []node
}
type node interface {
// eval returns either Value, []Value, Variable, or []Variable.
eval(*Interp) (interface{}, error)
}
type Value interface {
String() string
eval(*Interp) (interface{}, error)
}
type String string
func (s String) String() string { return string(s) }
type Number big.Float
// MakeBool returns a Number representing the current boolean.
func MakeBool(b bool) Number {
if b {
return Number(*big.NewFloat(1))
}
return Number(*big.NewFloat(0))
}
func (n Number) String() string { return (*big.Float)(&n).String() }
func (n Number) Float() *big.Float { return (*big.Float)(&n) }
func (n Number) Bool() bool {
var zero big.Float
return n.Float().Cmp(&zero) != 0
}
type Variable struct {
Name string
}
type exprList []node
type binNode struct {
op *binop
left, right node
}
type unNode struct {
op *unop
arg node
}
type readVar struct {
Variable
}
func New(lang Lang) (*Interp, error) {
interp := Interp{
vars: make(map[string]*Value),
}
for _, cmdDef := range lang.Cmds {
cmd, err := newCmd(cmdDef)
if err != nil {
return nil, err
}
interp.cmds = append(interp.cmds, *cmd)
}
for _, prec := range lang.BinOps {
var ops []binop
for _, binDef := range prec {
binop, err := newBin(binDef)
if err != nil {
return nil, err
}
ops = append(ops, *binop)
}
interp.bins = append(interp.bins, ops)
}
for _, unDef := range lang.UnOps {
unop, err := newUn(unDef)
if err != nil {
return nil, err
}
interp.uns = append(interp.uns, *unop)
}
return &interp, nil
}
func newCmd(def CmdDef) (*cmd, error) {
var cmd cmd
cmd.eval = reflect.ValueOf(def.Eval)
if cmd.kwds = strings.Fields(def.Name); len(cmd.kwds) == 0 {
return nil, fmt.Errorf("empty command name")
}
t := reflect.TypeOf(def.Eval)
if t.Kind() != reflect.Func {
return nil, fmt.Errorf("%s: bad eval type %s, want Func", def.Name, t)
}
if t.NumIn() == 0 {
return nil, fmt.Errorf("%s: bad eval arity: 0", def.Name)
}
if n := t.NumIn(); n < len(cmd.kwds) || n > len(cmd.kwds)+1 {
return nil, fmt.Errorf("%s: bad eval arity: %d args, %d keywords",
def.Name, n, len(cmd.kwds))
}
if t.NumOut() > 1 ||
(t.NumOut() == 1 && t.Out(0) != reflect.TypeOf([]error{}).Elem()) {
return nil, fmt.Errorf("%s: bad eval return", def.Name)
}
want := reflect.TypeOf(Exec{})
if t.In(0) != want {
return nil, fmt.Errorf("%s: bad eval parm 0 type: %s, want %s",
def.Name, t.In(0), want)
}
for i := 1; i < t.NumIn(); i++ {
at, ok := argTypes[t.In(i)]
if !ok {
return nil, fmt.Errorf("%s: bad eval parm type: %s",
def.Name, t.In(i))
}
cmd.argTypes = append(cmd.argTypes, at)
}
return &cmd, nil
}
func newBin(def OpDef) (*binop, error) {
op := binop{
op: def.Op,
eval: reflect.ValueOf(def.Eval),
}
t := reflect.TypeOf(def.Eval)
if t.Kind() != reflect.Func {
return nil, fmt.Errorf("%s: bad binary op eval type %s, want Func", def.Op, t)
}
if t.NumIn() != 2 {
return nil, fmt.Errorf("%s: bad binary op eval arity: %d", def.Op, t.NumIn())
}
if t.NumOut() != 1 && t.NumOut() != 2 {
return nil, fmt.Errorf("%s: bad binary op eval return arity: %d, want 1 or 2",
def.Op, t.NumOut())
}
if t.Out(0) != reflect.TypeOf([]Value{}).Elem() {
return nil, fmt.Errorf("%s: bad binary op eval return type: %s, want Value",
def.Op, t.Out(0))
}
if t.NumOut() == 2 && t.Out(1) != reflect.TypeOf([]error{}).Elem() {
return nil, fmt.Errorf("%s: bad binary op eval return type: %s, want error",
def.Op, t.Out(1))
}
op.ltype, op.rtype = t.In(0), t.In(1)
var number Number
if op.ltype != reflect.TypeOf(number) &&
op.ltype != reflect.TypeOf(String("")) &&
op.ltype != reflect.TypeOf([]Value{}).Elem() {
return nil, fmt.Errorf("%s: bad binary op eval parm type: %s, "+
"want Number, String, or Value",
def.Op, op.ltype)
}
if op.rtype != op.ltype {
return nil, fmt.Errorf("%s: bad binary op eval parm type: %s, want %s",
def.Op, op.rtype, op.ltype)
}
return &op, nil
}
func newUn(def OpDef) (*unop, error) {
op := unop{
op: def.Op,
eval: reflect.ValueOf(def.Eval),
}
t := reflect.TypeOf(def.Eval)
if t.Kind() != reflect.Func {
return nil, fmt.Errorf("%s: bad binary op eval type %s, want Func", def.Op, t)
}
if t.NumIn() != 1 {
return nil, fmt.Errorf("%s: bad unary op eval arity: %d", def.Op, t.NumIn())
}
if t.NumOut() != 1 && t.NumOut() != 2 {
return nil, fmt.Errorf("%s: bad unary op eval return arity: %d, want 1 or 2",
def.Op, t.NumOut())
}
if t.Out(0) != reflect.TypeOf([]Value{}).Elem() {
return nil, fmt.Errorf("%s: bad unary op eval return type: %s, want Value",
def.Op, t.Out(0))
}
if t.NumOut() == 2 && t.Out(1) != reflect.TypeOf([]error{}).Elem() {
return nil, fmt.Errorf("%s: bad unary op eval return type: %s, want error",
def.Op, t.Out(1))
}
op.typ = t.In(0)
var number Number
if op.typ != reflect.TypeOf(number) &&
op.typ != reflect.TypeOf(String("")) &&
op.typ != reflect.TypeOf([]Value{}).Elem() {
return nil, fmt.Errorf("%s: bad binary op eval parm type: %s, "+
"want Number, String, or Value",
def.Op, op.typ)
}
return &op, nil
}
func (interp *Interp) Read(r io.Reader) error {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line, err := parseLine(interp, scanner.Text())
if err != nil {
return err
}
if line.num >= 0 {
addLine(interp, line)
continue
}
interp.immediate = line
interp.pos.line = -1
interp.pos.stmt = 0
if err := run(interp); err != nil {
return err
}
}
return scanner.Err()
}
func addLine(interp *Interp, line line) {
panic("unimplemented")
}
func run(interp *Interp) error {
interp.mu.Lock()
defer interp.mu.Unlock()
interp.end = false
interp.stack = interp.stack[:0]
for !interp.end {
var line *line
if i := interp.pos.line; i < 0 {
line = &interp.immediate
} else if i >= len(interp.prog) {
break
} else {
line = &interp.prog[interp.pos.line]
}
if interp.pos.stmt >= len(line.stmts) {
if interp.pos.line < 0 {
break
}
interp.pos.line++
interp.pos.stmt = 0
continue
}
stmt := line.stmts[interp.pos.stmt]
interp.pos.stmt++
if err := runStmt(interp, stmt); err != nil {
return err
}
}
return nil
}
func runStmt(interp *Interp, stmt stmt) error {
args := []reflect.Value{reflect.ValueOf(Exec{interp})}
for _, node := range stmt.nodes {
arg, err := node.eval(interp)
if err != nil {
return err
}
args = append(args, reflect.ValueOf(arg))
}
if err := stmt.cmd.eval.Call(args); len(err) > 0 {
if i := err[0].Interface(); i != nil {
return i.(error)
}
}
return nil
}
func (s String) eval(*Interp) (interface{}, error) { return s, nil }
func (n Number) eval(*Interp) (interface{}, error) { return n, nil }
func (v Variable) eval(*Interp) (interface{}, error) { return v, nil }
func (l exprList) eval(interp *Interp) (interface{}, error) {
var vals []Value
for _, n := range l {
v, err := n.eval(interp)
if err != nil {
return nil, err
}
vals = append(vals, v.(Value))
}
return vals, nil
}
func (b binNode) eval(interp *Interp) (interface{}, error) {
l, err := b.left.eval(interp)
if err != nil {
return nil, err
}
if t := reflect.TypeOf(l); !t.AssignableTo(b.op.ltype) {
return nil, fmt.Errorf("%s got type %s, want %s", b.op.op, t, b.op.ltype)
}
r, err := b.right.eval(interp)
if err != nil {
return nil, err
}
if t := reflect.TypeOf(r); !t.AssignableTo(b.op.rtype) {
return nil, fmt.Errorf("%s got type %s, want %s", b.op.op, t, b.op.rtype)
}
res := b.op.eval.Call([]reflect.Value{reflect.ValueOf(l), reflect.ValueOf(r)})
if len(res) == 2 {
return res[0].Interface().(Value), res[1].Interface().(error)
}
return res[0].Interface().(Value), nil
}
func (u unNode) eval(interp *Interp) (interface{}, error) {
a, err := u.arg.eval(interp)
if err != nil {
return nil, err
}
if t := reflect.TypeOf(a); !t.AssignableTo(u.op.typ) {
return nil, fmt.Errorf("%s got type %s, want %s", u.op.op, t, u.op.typ)
}
res := u.op.eval.Call([]reflect.Value{reflect.ValueOf(a)})
if len(res) == 2 {
return res[0].Interface().(Value), res[1].Interface().(error)
}
return res[0].Interface().(Value), nil
}
func (r readVar) eval(interp *Interp) (interface{}, error) {
return *getVar(interp, r.Variable), nil
}
func getVar(interp *Interp, v Variable) *Value {
val, ok := interp.vars[v.Name]
if !ok {
val = new(Value)
r, _ := utf8.DecodeLastRuneInString(v.Name)
if r == '$' {
*val = String("")
} else {
*val = Number(big.Float{})
}
interp.vars[v.Name] = val
}
return val
}
func parseLine(interp *Interp, input string) (line line, err error) {
line.src = input
if line.num, err = parseLineNum(&input); err != nil {
return line, err
}
line.stmts, err = parseStmts(interp, &input)
return line, err
}
// returns -1, nil if there is no number.
func parseLineNum(input *string) (int, error) {
i := digits(*input)
if i == 0 {
return -1, nil
}
n, err := strconv.Atoi((*input)[:i])
if err != nil {
return 0, errors.New("bad line number: " + err.Error())
}
*input = (*input)[:i]
return n, nil
}
func parseStmts(interp *Interp, input *string) (stmts []stmt, err error) {
for {
consumeSpace(input)
if *input == "" {
break
}
stmt, err := parseStmt(interp, input)
if err != nil {
return nil, err
}
stmts = append(stmts, stmt)
if len(*input) == 0 {
break
}
consumeSpace(input)
if r, w := utf8.DecodeRuneInString(*input); r == ':' {
*input = (*input)[w:]
continue
}
return nil, fmt.Errorf("expected end-of-statement, got [%s]", *input)
}
return stmts, nil
}
func parseStmt(interp *Interp, input *string) (stmt, error) {
k := parseKeyword(input)
if k == "" {
return stmt{}, fmt.Errorf("expected statement: [%s]", *input)
}
for i := range interp.cmds {
cmd := &interp.cmds[i]
if cmd.kwds[0] != k {
continue
}
nodes, err := parseArgs(interp, cmd, input)
if err != nil {
return stmt{}, err
}
return stmt{cmd: cmd, nodes: nodes}, nil
}
return stmt{}, errors.New(k + " command not found")
}
func parseKeyword(input *string) string {
consumeSpace(input)
var i int
for i < len(*input) {
r, w := utf8.DecodeRuneInString((*input)[i:])
if unicode.IsSpace(r) || r == ':' {
break
}
i += w
}
k := (*input)[:i]
*input = (*input)[i:]
return k
}
func parseArgs(interp *Interp, cmd *cmd, input *string) ([]node, error) {
var nodes []node
for i, at := range cmd.argTypes {
node, err := at.Parse(interp, input)
if err != nil {
return nil, err
}
nodes = append(nodes, node)
if i+1 < len(cmd.kwds) {
want := cmd.kwds[i+1]
if k := parseKeyword(input); k != want {
return nil, fmt.Errorf("expected %s, got [%s]", want, k)
}
}
}
return nodes, nil
}
func parseExprList(interp *Interp, input *string) (exprList, error) {
var vals exprList
for {
v, err := parseExpr(interp, input)
if err != nil {
return nil, err
}
vals = append(vals, v)
r, w := utf8.DecodeRuneInString(*input)
if r != ',' {
break
}
*input = (*input)[w:]
}
return vals, nil
}
func parseExpr(interp *Interp, input *string) (node, error) {
return parseBinOp(interp, 0, input)
}
func parseBinOp(interp *Interp, prec int, input *string) (n node, err error) {
if prec >= len(interp.bins) {
return parseFactor(interp, input)
}
if n, err = parseBinOp(interp, prec+1, input); err != nil {
return nil, err
}
for {
consumeSpace(input)
var op *binop
for i := range interp.bins[prec] {
if strings.HasPrefix(*input, interp.bins[prec][i].op) {
op = &interp.bins[prec][i]
break
}
}
if op == nil {
break
}
*input = (*input)[len(op.op):]
r, err := parseBinOp(interp, prec+1, input)
if err != nil {
return nil, err
}
n = binNode{op: op, left: n, right: r}
}
return n, nil
}
func parseFactor(interp *Interp, input *string) (node, error) {
consumeSpace(input)
for _, op := range interp.uns {
if !strings.HasPrefix(*input, op.op) {
continue
}
*input = (*input)[len(op.op):]
arg, err := parseFactor(interp, input)
if err != nil {
return nil, err
}
return unNode{op: &op, arg: arg}, nil
}
switch r, _ := utf8.DecodeRuneInString(*input); {
case r == '+' || r == '-' || '0' <= r && r <= '9':
return parseNumber(input)
case r == '"':
return parseString(input)
case r == '(':
return parseSubExpr(interp, input)
case unicode.IsLetter(r):
v, err := parseVar(input)
return readVar{Variable: v}, err
default:
// TODO: Var and Call
return nil, fmt.Errorf("bad expression [%s]", *input)
}
}
func parseSubExpr(interp *Interp, input *string) (node, error) {
*input = (*input)[1:] // consume (
expr, err := parseExpr(interp, input)
if err != nil {
return nil, err
}
consumeSpace(input)
r, w := utf8.DecodeRuneInString(*input)
if r != ')' {
return nil, errors.New("unterminated (")
}
*input = (*input)[w:]
return expr, nil
}
func parseVar(input *string) (Variable, error) {
consumeSpace(input)
i := 0
for {
r, w := utf8.DecodeRuneInString((*input)[i:])
if unicode.IsLetter(r) || unicode.IsNumber(r) {
i += w
continue
}
if i == 0 {
return Variable{}, fmt.Errorf("expected variable, got [%s]", *input)
}
if r == '$' {
i += w
}
break
}
v := Variable{Name: (*input)[:i]}
*input = (*input)[i:]
return v, nil
}
func parseNumber(input *string) (Number, error) {
consumeSpace(input)
var i int
if r, w := utf8.DecodeRuneInString((*input)[i:]); r == '+' || r == '-' {
i += w
}
n := digits((*input)[i:])
if n == 0 {
return Number{}, fmt.Errorf("expected digits, got [%s]", *input)
}
i += n
if r, w := utf8.DecodeRuneInString((*input)[i:]); r == '.' {
i += w
n := digits((*input)[i:])
if n == 0 {
return Number{}, errors.New("expected digits")
}
i += n
}
if r, w := utf8.DecodeRuneInString((*input)[i:]); r == 'e' || r == 'E' {
i += w
n := digits((*input)[i:])
if n == 0 {
return Number{}, errors.New("expected digits")
}
i += n
}
var f big.Float
if _, _, err := f.Parse((*input)[:i], 10); err != nil {
panic(err.Error())
}
*input = (*input)[i:]
return Number(f), nil
}
func parseString(input *string) (String, error) {
consumeSpace(input)
if len(*input) < 2 || (*input)[0] != '"' {
return "", errors.New("missing opening \"")
}
*input = (*input)[1:] // remove "
var s strings.Builder
var esc bool
for {
if len(*input) == 0 {
return "", errors.New("missing closing \"")
}
r, w := utf8.DecodeRuneInString(*input)
*input = (*input)[w:]
switch {
case !esc && r == '\\':
esc = true
continue
case !esc && r == '"':
return String(s.String()), nil
case esc && r == 'n':
r = '\n'
case esc && r == 't':
r = '\t'
}
esc = false
s.WriteRune(r)
}
}
func digits(input string) int {
var i int
for {
r, w := utf8.DecodeRuneInString(input[i:])
if r < '0' || '9' < r {
return i
}
i += w
}
}
func consumeSpace(input *string) {
for len(*input) > 0 {
r, w := utf8.DecodeRuneInString(*input)
if !unicode.IsSpace(r) {
break
}
*input = (*input)[w:]
}
}
// Exec is a currently executing Interp.
// It is used as an argument to command evaluation functions,
// and is not intended to be stored outside of the execution of a command.
type Exec struct {
*Interp
}
// Var returns the value of a variable.
func (exec Exec) Var(v Variable) Value {
return *getVar(exec.Interp, v)
}
// SetVar sets the Value of a Variable.
func (exec Exec) SetVar(v Variable, val Value) {
*getVar(exec.Interp, v) = val
}
// End terminates the program after the current statement.
func (exec Exec) End() {
exec.end = true
}
// Pos returns the program position immediately after the current statement.
func (exec Exec) Pos() Pos {
return exec.pos
}
// SetPos sets the position to continue execution after the current statement.
func (exec Exec) SetPos(pos Pos) {
exec.pos = pos
}
// TopFrame returns the top frame of the control stack.
// If the control stack is empty, nil is returned.
func (exec Exec) TopFrame() interface{} {
if len(exec.stack) == 0 {
return nil
}
return exec.stack[len(exec.stack)-1]
}
// PopFrame removes and returns the top frame of the control stack.
// If the control stack is empty, nil is returned.
func (exec Exec) PopFrame() interface{} {
if len(exec.stack) == 0 {
return nil
}
f := exec.stack[len(exec.stack)-1]
exec.stack = exec.stack[:len(exec.stack)-1]
return f
}
// PushFrame pushes a new frame onto the control stack.
func (exec Exec) PushFrame(frame interface{}) {
exec.stack = append(exec.stack, frame)
}