mirror of https://github.com/velour/catbase.git
847 lines
19 KiB
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)
|
|
}
|