catbase/plugins/catbasic/basic/lang/lang.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()) }