mirror of https://github.com/velour/catbase.git
188 lines
4.8 KiB
Go
188 lines
4.8 KiB
Go
|
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()) }
|