mirror of https://github.com/velour/catbase.git
Add a generalized basic interpreter library.
It takes a custom basic-like language description defined by a set of commands, binary and unary operators. The execution of each command and operator is defined by a Go function. The interpreter itself handles the parsing and execution loop.
This commit is contained in:
parent
6bcf1142c4
commit
87688657e8
|
@ -0,0 +1,846 @@
|
|||
// 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)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/velour/catbase/plugins/catbasic/basic"
|
||||
"github.com/velour/catbase/plugins/catbasic/basic/lang"
|
||||
)
|
||||
|
||||
const source = `
|
||||
FOR I = 0 TO 5: PRINT "I:", I: NEXT I
|
||||
`
|
||||
|
||||
func main() {
|
||||
interp, err := basic.New(lang.Default)
|
||||
if err != nil {
|
||||
die(err)
|
||||
}
|
||||
if err := interp.Read(strings.NewReader(source)); err != nil {
|
||||
die(err)
|
||||
}
|
||||
}
|
||||
|
||||
func die(err error) {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package lang
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/velour/catbase/plugins/catbasic/basic"
|
||||
)
|
||||
|
||||
var Default = basic.Lang{
|
||||
Cmds: []basic.CmdDef{
|
||||
{
|
||||
Name: "END",
|
||||
Doc: "ends execution of the program",
|
||||
Eval: endCmd,
|
||||
},
|
||||
{
|
||||
Name: "PRINT",
|
||||
Doc: "prints each value, separated by a space, then a newline",
|
||||
Eval: printCmd,
|
||||
},
|
||||
{
|
||||
Name: "LET =",
|
||||
Doc: "assigns a value to a variable",
|
||||
Eval: letCmd,
|
||||
},
|
||||
{
|
||||
Name: "FOR = TO",
|
||||
Doc: "begins a for loop. The loop the variable is assigned and execution continues at the following statement. A subsequent NEXT statement with a variable matching the FOR variable will increment the variable; if the value is less than the TO value, then executinon will continue from the statement following the FOR statement, otherwise from the statement following the NEXT statement",
|
||||
Eval: forCmd,
|
||||
},
|
||||
{
|
||||
Name: "NEXT",
|
||||
Doc: "closes the body of a FOR loop. See FOR for details",
|
||||
Eval: nextCmd,
|
||||
},
|
||||
},
|
||||
BinOps: [][]basic.OpDef{
|
||||
{
|
||||
{Op: "OR", Eval: logic(func(a, b bool) bool { return a || b })},
|
||||
},
|
||||
{
|
||||
{Op: "AND", Eval: logic(func(a, b bool) bool { return a && b })},
|
||||
},
|
||||
{
|
||||
{Op: "<", Eval: rel("<", func(c int) bool { return c < 0 })},
|
||||
{Op: "<=", Eval: rel("<=", func(c int) bool { return c <= 0 })},
|
||||
{Op: ">", Eval: rel(">", func(c int) bool { return c > 0 })},
|
||||
{Op: ">=", Eval: rel(">=", func(c int) bool { return c >= 0 })},
|
||||
{Op: "=", Eval: rel("=", func(c int) bool { return c == 0 })},
|
||||
{Op: "<>", Eval: rel("<>", func(c int) bool { return c != 0 })},
|
||||
},
|
||||
{
|
||||
{Op: "+", Eval: add},
|
||||
{Op: "-", Eval: arithmetic("-", (*big.Float).Sub)},
|
||||
},
|
||||
{
|
||||
{Op: "*", Eval: arithmetic("-", (*big.Float).Mul)},
|
||||
{Op: "/", Eval: arithmetic("-", (*big.Float).Quo)},
|
||||
},
|
||||
},
|
||||
UnOps: []basic.OpDef{
|
||||
{Op: "NOT", Eval: not},
|
||||
},
|
||||
}
|
||||
|
||||
func endCmd(exec basic.Exec) {
|
||||
exec.End()
|
||||
}
|
||||
|
||||
func printCmd(exec basic.Exec, values []basic.Value) {
|
||||
for i, val := range values {
|
||||
if i > 0 {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
fmt.Print(val.String())
|
||||
}
|
||||
fmt.Print("\n")
|
||||
}
|
||||
|
||||
func letCmd(exec basic.Exec, vr basic.Variable, val basic.Value) error {
|
||||
if t := reflect.TypeOf(exec.Var(vr)); t != reflect.TypeOf(val) {
|
||||
return fmt.Errorf("cannot assign %T to a variable of type %T", val, t)
|
||||
}
|
||||
exec.SetVar(vr, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
type forFrame struct {
|
||||
name string
|
||||
v basic.Variable
|
||||
to basic.Number
|
||||
pos basic.Pos
|
||||
}
|
||||
|
||||
func forCmd(exec basic.Exec, v basic.Variable, from, to basic.Value) error {
|
||||
if _, ok := exec.Var(v).(basic.Number); !ok {
|
||||
return fmt.Errorf("FOR variable %s is a %T, not a Number", v.Name, exec.Var(v))
|
||||
}
|
||||
f, ok := from.(basic.Number)
|
||||
if !ok {
|
||||
return fmt.Errorf("FOR start value is a %T, not a Number", from)
|
||||
}
|
||||
t, ok := to.(basic.Number)
|
||||
if !ok {
|
||||
return fmt.Errorf("FOR end value is a %T, not a Number", to)
|
||||
}
|
||||
exec.SetVar(v, f)
|
||||
exec.PushFrame(forFrame{
|
||||
name: v.Name,
|
||||
v: v,
|
||||
to: t,
|
||||
pos: exec.Pos(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextCmd(exec basic.Exec, v basic.Variable) error {
|
||||
top, ok := exec.TopFrame().(forFrame)
|
||||
if !ok {
|
||||
return errors.New("NEXT called not within a FOR loop")
|
||||
}
|
||||
if v.Name != top.name {
|
||||
return fmt.Errorf("interleaved FOR loops: %s and %s", v.Name, top.name)
|
||||
}
|
||||
f := exec.Var(top.v).(basic.Number).Float()
|
||||
exec.SetVar(top.v, basic.Number(*f.Add(f, big.NewFloat(1))))
|
||||
if f.Cmp(top.to.Float()) >= 0 {
|
||||
exec.PopFrame()
|
||||
} else {
|
||||
exec.SetPos(top.pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logic(f func(a, b bool) bool) func(basic.Number, basic.Number) basic.Value {
|
||||
return func(a, b basic.Number) basic.Value {
|
||||
return basic.MakeBool(f(a.Bool(), b.Bool()))
|
||||
}
|
||||
}
|
||||
|
||||
func rel(n string, ok func(int) bool) func(basic.Value, basic.Value) (basic.Value, error) {
|
||||
return func(l, r basic.Value) (basic.Value, error) {
|
||||
var c int
|
||||
switch l := l.(type) {
|
||||
case basic.String:
|
||||
if r, ok := r.(basic.String); !ok {
|
||||
return nil, fmt.Errorf("String %s expects String, got %T", n, r)
|
||||
} else {
|
||||
c = strings.Compare(string(l), string(r))
|
||||
}
|
||||
case basic.Number:
|
||||
if r, ok := r.(basic.Number); !ok {
|
||||
return nil, fmt.Errorf("Number %s expects Numbe, got %T", n, r)
|
||||
} else {
|
||||
c = l.Float().Cmp(r.Float())
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%s expects String or Number, got %T", n, l)
|
||||
}
|
||||
return basic.MakeBool(ok(c)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func arithmetic(n string, op func(z, a, b *big.Float) *big.Float) func(l, r basic.Number) basic.Value {
|
||||
return func(l, r basic.Number) basic.Value {
|
||||
var z big.Float
|
||||
op(&z, l.Float(), r.Float())
|
||||
return basic.Number(z)
|
||||
}
|
||||
}
|
||||
|
||||
func add(l, r basic.Value) basic.Value {
|
||||
switch l := l.(type) {
|
||||
case basic.String:
|
||||
return basic.String(l + r.(basic.String))
|
||||
case basic.Number:
|
||||
return arithmetic("+", (*big.Float).Add)(l, r.(basic.Number))
|
||||
default:
|
||||
panic("impossible")
|
||||
}
|
||||
}
|
||||
|
||||
func not(b basic.Number) basic.Value { return basic.MakeBool(!b.Bool()) }
|
Loading…
Reference in New Issue