ucl/ucl/builtins.go
Leon Mika 0cf2f816da
All checks were successful
Test / build (push) Successful in 56s
Added try blocks back
2025-05-24 09:52:10 +10:00

1262 lines
25 KiB
Go

package ucl
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
)
func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
echoPrinter := args.inst.echoPrinter
if echoPrinter != nil {
convertedArgs := make([]interface{}, len(args.args))
for i, arg := range args.args {
if convArg, ok := toGoValue(arg); ok {
convertedArgs[i] = convArg
} else {
convertedArgs[i] = arg
}
}
return nil, echoPrinter(ctx, args.inst.out, convertedArgs)
}
if len(args.args) == 0 {
if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
return nil, err
}
return nil, nil
}
var line strings.Builder
for _, arg := range args.args {
if s, ok := arg.(fmt.Stringer); ok {
line.WriteString(s.String())
}
}
if _, err := fmt.Fprintln(args.inst.Out(), line.String()); err != nil {
return nil, err
}
return nil, nil
}
func addBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 {
return IntObject(0), nil
}
n := 0
for i, a := range args.args {
switch t := a.(type) {
case IntObject:
n += int(t)
case StringObject:
v, err := strconv.Atoi(string(t))
if err != nil {
return nil, fmt.Errorf("arg %v of 'add' not convertable to an int", i)
}
n += v
default:
return nil, fmt.Errorf("arg %v of 'add' not convertable to an int", i)
}
}
return IntObject(n), nil
}
func subBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 {
return IntObject(0), nil
}
n := 0
for i, a := range args.args {
var p int
switch t := a.(type) {
case IntObject:
p = int(t)
case StringObject:
v, err := strconv.Atoi(string(t))
if err != nil {
return nil, fmt.Errorf("arg %v of 'sub' not convertable to an int", i)
}
p = v
default:
return nil, fmt.Errorf("arg %v of 'sub' not convertable to an int", i)
}
if i == 0 {
n = p
} else {
n -= p
}
}
return IntObject(n), nil
}
func mupBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 {
return IntObject(1), nil
}
n := 1
for i, a := range args.args {
switch t := a.(type) {
case IntObject:
n *= int(t)
case StringObject:
v, err := strconv.Atoi(string(t))
if err != nil {
return nil, fmt.Errorf("arg %v of 'mup' not convertable to an int", i)
}
n *= v
default:
return nil, fmt.Errorf("arg %v of 'mup' not convertable to an int", i)
}
}
return IntObject(n), nil
}
func divBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 {
return IntObject(1), nil
}
n := 1
for i, a := range args.args {
var p int
switch t := a.(type) {
case IntObject:
p = int(t)
case StringObject:
v, err := strconv.Atoi(string(t))
if err != nil {
return nil, fmt.Errorf("arg %v of 'div' not convertable to an int", i)
}
p = v
default:
return nil, fmt.Errorf("arg %v of 'div' not convertable to an int", i)
}
if i == 0 {
n = p
} else {
n /= p
}
}
return IntObject(n), nil
}
func modBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 {
return IntObject(0), nil
}
n := 0
for i, a := range args.args {
var p int
switch t := a.(type) {
case IntObject:
p = int(t)
case StringObject:
v, err := strconv.Atoi(string(t))
if err != nil {
return nil, fmt.Errorf("arg %v of 'mod' not convertable to an int", i)
}
p = v
default:
return nil, fmt.Errorf("arg %v of 'mod' not convertable to an int", i)
}
if i == 0 {
n = p
} else {
n %= p
}
}
return IntObject(n), nil
}
// TODO: this may need to be a macro
func setBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
name, err := args.stringArg(0)
if err != nil {
return nil, err
} else if len(name) == 0 {
return nil, fmt.Errorf("attempt to set empty string")
}
newVal := args.args[1]
if strings.HasPrefix(name, "@") {
pname := name[1:]
pvar, ok := args.ec.getPseudoVar(pname)
if ok {
if err := pvar.set(ctx, pname, newVal); err != nil {
return nil, err
}
return newVal, nil
}
if pvar := args.inst.missingPseudoVarHandler; pvar != nil {
if err := pvar.set(ctx, pname, newVal); err != nil {
return nil, err
}
return newVal, nil
}
return nil, fmt.Errorf("attempt to set '%v' to a non-existent pseudo-variable", name)
}
args.ec.setOrDefineVar(name, newVal)
return newVal, nil
}
func mustSetBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
name, err := args.stringArg(0)
if err != nil {
return nil, err
}
newVal := args.args[1]
if newVal == nil {
return nil, fmt.Errorf("attempt to set '%v' to a nil value", args.args[0])
}
args.ec.setOrDefineVar(name, newVal)
return newVal, nil
}
func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
l := args.args[0]
r := args.args[1]
return BoolObject(objectsEqual(l, r)), nil
}
func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
l := args.args[0]
r := args.args[1]
return BoolObject(!objectsEqual(l, r)), nil
}
func ltBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
isLess, err := objectsLessThan(args.args[0], args.args[1])
if err != nil {
return nil, err
}
return BoolObject(isLess), nil
}
func leBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
isLess, err := objectsLessThan(args.args[0], args.args[1])
if err != nil {
return nil, err
}
return BoolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil
}
func gtBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
isGreater, err := objectsLessThan(args.args[1], args.args[0])
if err != nil {
return nil, err
}
return BoolObject(isGreater), nil
}
func geBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
isGreater, err := objectsLessThan(args.args[1], args.args[0])
if err != nil {
return nil, err
}
return BoolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil
}
func andBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
for _, a := range args.args {
if a == nil || !a.Truthy() {
return a, nil
}
}
return args.args[len(args.args)-1], nil
}
func orBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
for _, a := range args.args[:len(args.args)-1] {
if a != nil && a.Truthy() {
return a, nil
}
}
return args.args[len(args.args)-1], nil
}
func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
return BoolObject(!args.args[0].Truthy()), nil
}
var errObjectsNotEqual = errors.New("objects not equal")
func objectsEqual(l, r Object) bool {
if l == nil || r == nil {
return l == nil && r == nil
}
switch lv := l.(type) {
case StringObject:
if rv, ok := r.(StringObject); ok {
return lv == rv
}
case IntObject:
if rv, ok := r.(IntObject); ok {
return lv == rv
}
case BoolObject:
if rv, ok := r.(BoolObject); ok {
return lv == rv
}
case Listable:
rv, ok := r.(Listable)
if !ok {
return false
}
if lv.Len() != rv.Len() {
return false
}
for i := 0; i < lv.Len(); i++ {
if !objectsEqual(lv.Index(i), rv.Index(i)) {
return false
}
}
return true
case Hashable:
rv, ok := r.(Hashable)
if !ok {
return false
}
if lv.Len() != rv.Len() {
return false
}
if err := lv.Each(func(k string, lkv Object) error {
rkv := rv.Value(k)
if rkv == nil {
return errObjectsNotEqual
} else if !objectsEqual(lkv, rkv) {
return errObjectsNotEqual
}
return nil
}); err != nil {
return false
}
return true
}
return false
}
func objectsLessThan(l, r Object) (bool, error) {
switch lv := l.(type) {
case StringObject:
if rv, ok := r.(StringObject); ok {
return lv < rv, nil
}
case IntObject:
if rv, ok := r.(IntObject); ok {
return lv < rv, nil
}
}
return false, errors.New("objects are not comparable")
}
func strBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
if args.args[0] == nil {
return StringObject(""), nil
}
return StringObject(args.args[0].String()), nil
}
func intBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
if args.args[0] == nil {
return IntObject(0), nil
}
switch v := args.args[0].(type) {
case IntObject:
return v, nil
case StringObject:
i, err := strconv.Atoi(string(v))
if err != nil {
return nil, errors.New("cannot convert to int")
}
return IntObject(i), nil
case BoolObject:
if v {
return IntObject(1), nil
}
return IntObject(0), nil
}
return nil, errors.New("cannot convert to int")
}
func concatBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
var sb strings.Builder
for _, a := range args.args {
if a == nil {
continue
}
sb.WriteString(a.String())
}
return StringObject(sb.String()), nil
}
func callBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
var inv invokable
switch t := args.args[0].(type) {
case invokable:
inv = t
case StringObject:
inv = args.ec.lookupInvokable(t.String())
if inv == nil {
return nil, errors.New("no such invokable: " + t.String())
}
default:
return nil, errors.New("expected string or invokable")
}
var calledArgs []Object
args = args.shift(1)
if len(args.args) > 0 {
argList, ok := args.args[0].(Listable)
if !ok {
return nil, errors.New("expected listable arg")
}
calledArgs = make([]Object, argList.Len())
for i := 0; i < argList.Len(); i++ {
calledArgs[i] = argList.Index(i)
}
args.shift(1)
}
invArgs := args.fork(calledArgs)
args = args.shift(1)
if len(args.args) > 0 {
kwArgs, ok := args.args[0].(Hashable)
if !ok {
return nil, errors.New("expected hashable arg")
}
kwArgs.Each(func(k string, v Object) error {
if invArgs.kwargs == nil {
invArgs.kwargs = make(map[string]*ListObject)
}
if invArgs.kwargs[k] == nil {
invArgs.kwargs[k] = &ListObject{}
}
kwArg := *(invArgs.kwargs[k])
kwArg = append(kwArg, v)
invArgs.kwargs[k] = &kwArg
return nil
})
}
return inv.invoke(ctx, invArgs)
}
func lenBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
switch v := args.args[0].(type) {
case StringObject:
return IntObject(len(string(v))), nil
case Listable:
return IntObject(v.Len()), nil
case Hashable:
return IntObject(v.Len()), nil
case Iterable:
cnt := 0
for v.HasNext() {
_, err := v.Next(ctx)
if err != nil {
return nil, err
}
cnt++
}
return IntObject(cnt), nil
}
return IntObject(0), nil
}
func indexLookup(ctx context.Context, obj, elem Object) (Object, error) {
switch v := obj.(type) {
case Listable:
intIdx, ok := elem.(IntObject)
if !ok {
return nil, nil
}
if int(intIdx) >= 0 && int(intIdx) < v.Len() {
return v.Index(int(intIdx)), nil
} else if int(intIdx) < 0 && int(intIdx) >= -v.Len() {
return v.Index(v.Len() + int(intIdx)), nil
}
return nil, nil
case Hashable:
strIdx, ok := elem.(StringObject)
if !ok {
return nil, errors.New("expected string for Hashable")
}
return v.Value(string(strIdx)), nil
}
return nil, nil
}
func indexBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
val := args.args[0]
for _, idx := range args.args[1:] {
newVal, err := indexLookup(ctx, val, idx)
if err != nil {
return nil, err
}
val = newVal
}
return val, nil
}
func keysBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
val := args.args[0]
switch v := val.(type) {
case Hashable:
keys := make(ListObject, 0, v.Len())
if err := v.Each(func(k string, _ Object) error {
keys = append(keys, StringObject(k))
return nil
}); err != nil {
return nil, err
}
return &keys, nil
}
return nil, nil
}
type mappedIter struct {
src Iterable
inv invokable
args invocationArgs
}
func (mi mappedIter) String() string {
return "mappedIter{}"
}
func (mi mappedIter) Truthy() bool {
return mi.src.HasNext()
}
func (mi mappedIter) HasNext() bool {
return mi.src.HasNext()
}
func (mi mappedIter) Next(ctx context.Context) (Object, error) {
v, err := mi.src.Next(ctx)
if err != nil {
return nil, err
}
return mi.inv.invoke(ctx, mi.args.fork([]Object{v}))
}
func mapBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
inv, err := args.invokableArg(1)
if err != nil {
return nil, err
}
switch t := args.args[0].(type) {
case Listable:
l := t.Len()
newList := ListObject{}
for i := 0; i < l; i++ {
v := t.Index(i)
m, err := inv.invoke(ctx, args.fork([]Object{v}))
if err != nil {
return nil, err
}
newList = append(newList, m)
}
return &newList, nil
case Iterable:
return mappedIter{src: t, inv: inv, args: args}, nil
}
return nil, errors.New("expected listable")
}
type filterIter struct {
src Iterable
inv invokable
args invocationArgs
hasNext bool
next Object
err error
}
func (mi *filterIter) prime(ctx context.Context) {
for mi.src.HasNext() {
v, err := mi.src.Next(ctx)
if err != nil {
mi.err = err
mi.hasNext = false
return
}
fv, err := mi.inv.invoke(ctx, mi.args.fork([]Object{v}))
if err != nil {
mi.err = err
mi.hasNext = false
return
} else if isTruthy(fv) {
mi.next = v
mi.hasNext = true
return
}
}
mi.hasNext = false
mi.err = nil
}
func (mi *filterIter) String() string {
return "filterIter{}"
}
func (mi *filterIter) Truthy() bool {
return mi.HasNext()
}
func (mi *filterIter) HasNext() bool {
return mi.hasNext
}
func (mi *filterIter) Next(ctx context.Context) (Object, error) {
next, err := mi.next, mi.err
mi.prime(ctx)
return next, err
}
func filterBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil {
return nil, err
}
inv, err := args.invokableArg(1)
if err != nil {
return nil, err
}
switch t := args.args[0].(type) {
case Listable:
l := t.Len()
newList := ListObject{}
for i := 0; i < l; i++ {
v := t.Index(i)
m, err := inv.invoke(ctx, args.fork([]Object{v}))
if err != nil {
return nil, err
} else if m != nil && m.Truthy() {
newList = append(newList, v)
}
}
return &newList, nil
case Hashable:
newHash := hashObject{}
if err := t.Each(func(k string, v Object) error {
if m, err := inv.invoke(ctx, args.fork([]Object{StringObject(k), v})); err != nil {
return err
} else if m != nil && m.Truthy() {
newHash[k] = v
}
return nil
}); err != nil {
return nil, err
}
return newHash, nil
case Iterable:
fi := &filterIter{src: t, inv: inv, args: args}
fi.prime(ctx)
return fi, nil
}
return nil, errors.New("expected listable")
}
func reduceBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
var err error
if err = args.expectArgn(2); err != nil {
return nil, err
}
var (
accum Object
setFirst bool
block invokable
)
if len(args.args) == 3 {
accum = args.args[1]
block, err = args.invokableArg(2)
if err != nil {
return nil, err
}
} else {
setFirst = true
block, err = args.invokableArg(1)
if err != nil {
return nil, err
}
}
switch t := args.args[0].(type) {
case Listable:
l := t.Len()
for i := 0; i < l; i++ {
v := t.Index(i)
if setFirst {
accum = v
setFirst = false
continue
}
newAccum, err := block.invoke(ctx, args.fork([]Object{v, accum}))
if err != nil {
return nil, err
}
accum = newAccum
}
return accum, nil
case Hashable:
// TODO: should raise error?
if err := t.Each(func(k string, v Object) error {
newAccum, err := block.invoke(ctx, args.fork([]Object{StringObject(k), v, accum}))
if err != nil {
return err
}
accum = newAccum
return nil
}); err != nil {
return nil, err
}
return accum, nil
case Iterable:
for t.HasNext() {
v, err := t.Next(ctx)
if err != nil {
return nil, err
}
newAccum, err := block.invoke(ctx, args.fork([]Object{v, accum}))
if err != nil {
return nil, err
}
accum = newAccum
}
return accum, nil
}
return nil, errors.New("expected listable")
}
func firstBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
switch t := args.args[0].(type) {
case Listable:
if t.Len() == 0 {
return nil, nil
}
return t.Index(0), nil
case Iterable:
if t.HasNext() {
return t.Next(ctx)
}
return nil, nil
}
return nil, errors.New("expected listable")
}
type seqObject struct {
from int
to int
inclusive bool
}
func (s seqObject) String() string {
return fmt.Sprintf("%d:%d", s.from, s.to)
}
func (s seqObject) Truthy() bool {
return s.from != s.to || s.inclusive
}
func (s seqObject) Len() int {
var l int
if s.from > s.to {
l = s.from - s.to
} else {
l = s.to - s.from
}
if s.inclusive {
l += 1
}
return l
}
func (s seqObject) Index(i int) Object {
l := s.Len()
if i < 0 || i > l {
return nil
}
if s.from > s.to {
return IntObject(s.from - i)
}
return IntObject(s.from + i)
}
func seqBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
inclusive := false
if inc, ok := args.kwargs["inc"]; ok {
inclusive = (inc.Len() == 0) || inc.Truthy()
}
switch len(args.args) {
case 1:
n, err := args.intArg(0)
if err != nil {
return nil, err
}
return seqObject{from: 0, to: n, inclusive: inclusive}, nil
case 2:
f, err := args.intArg(0)
if err != nil {
return nil, err
}
t, err := args.intArg(1)
if err != nil {
return nil, err
}
return seqObject{from: f, to: t, inclusive: inclusive}, nil
default:
return nil, errors.New("expected either 1 or 2 arguments")
}
}
func ifBuiltin(ctx context.Context, args macroArgs) (Object, error) {
if args.nargs() < 2 {
return nil, errors.New("need at least 2 arguments")
}
if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) {
return args.evalBlock(ctx, 1, nil, false)
} else if err != nil {
return nil, err
}
args.shift(2)
for args.identIs(ctx, 0, "elif") {
args.shift(1)
if args.nargs() < 2 {
return nil, errors.New("need at least 2 arguments")
}
if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) {
return args.evalBlock(ctx, 1, nil, false)
} else if err != nil {
return nil, err
}
args.shift(2)
}
if args.identIs(ctx, 0, "else") && args.nargs() > 1 {
return args.evalBlock(ctx, 1, nil, false)
} else if args.nargs() == 0 {
// no elif or else
return nil, nil
}
return nil, errors.New("malformed if-elif-else")
}
func tryBuiltin(ctx context.Context, args macroArgs) (res Object, err error) {
if args.nargs() < 2 {
return nil, errors.New("need at least 2 arguments")
} else if args.nargs()%2 == 0 {
return nil, errors.New("need an odd number of arguments")
}
// Select catches and finally
catchBlocks := make([]int, 0)
finallyBlocks := make([]int, 0)
for i := 1; i < args.nargs(); i += 2 {
if args.identIs(ctx, i, "catch") {
if len(finallyBlocks) > 0 {
return nil, errors.New("catch cannot be used after finally")
}
catchBlocks = append(catchBlocks, i+1)
} else if args.identIs(ctx, i, "finally") {
finallyBlocks = append(finallyBlocks, i+1)
}
}
defer func() {
if isBreakErr(err) {
return
}
var (
orgErr = err
lastFinallyErr error = nil
)
for _, idx := range finallyBlocks {
if _, fErr := args.evalBlock(ctx, idx, nil, false); fErr != nil {
if isBreakErr(fErr) {
if err == nil {
err = fErr
}
return
}
lastFinallyErr = fErr
}
}
if orgErr == nil {
err = lastFinallyErr
}
}()
res, err = args.evalBlock(ctx, 0, nil, false)
if err == nil {
return res, nil
} else if isBreakErr(err) {
return nil, err
}
for _, idx := range catchBlocks {
res, err = args.evalBlock(ctx, idx, []Object{errObject{err: err}}, false)
if err == nil {
return res, nil
} else if isBreakErr(err) {
return nil, err
}
}
return nil, err
}
func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
var (
items Object
blockIdx int
err error
)
if !args.hasPipe {
if args.nargs() < 2 {
return nil, errors.New("need at least 2 arguments")
}
items, err = args.evalArg(ctx, 0)
if err != nil {
return nil, err
}
blockIdx = 1
} else {
if args.nargs() < 1 {
return nil, errors.New("need at least 1 argument")
}
items = args.pipeArg
blockIdx = 0
}
var (
last Object
breakErr errBreak
)
switch t := items.(type) {
case Listable:
l := t.Len()
for i := 0; i < l; i++ {
v := t.Index(i)
last, err = args.evalBlock(ctx, blockIdx, []Object{v}, false) // TO INCLUDE: the index
if err != nil {
if errors.As(err, &breakErr) {
if !breakErr.isCont {
return breakErr.ret, nil
}
} else {
return nil, err
}
}
}
case Hashable:
err := t.Each(func(k string, v Object) error {
last, err = args.evalBlock(ctx, blockIdx, []Object{StringObject(k), v}, false)
return err
})
if errors.As(err, &breakErr) {
if !breakErr.isCont {
return breakErr.ret, nil
}
} else {
return nil, err
}
case Iterable:
for t.HasNext() {
v, err := t.Next(ctx)
if err != nil {
return nil, err
}
last, err = args.evalBlock(ctx, blockIdx, []Object{v}, false) // TO INCLUDE: the index
if err != nil {
if errors.As(err, &breakErr) {
if !breakErr.isCont {
return breakErr.ret, nil
}
} else {
return nil, err
}
}
}
return nil, nil
}
return last, nil
}
func whileBuiltin(ctx context.Context, args macroArgs) (Object, error) {
blockIdx := 1
loopForever := false
if args.nargs() < 2 {
blockIdx = 0
loopForever = true
}
var (
breakErr errBreak
)
for {
if !loopForever {
guard, err := args.evalArg(ctx, 0)
if err != nil {
return nil, err
}
if !isTruthy(guard) {
return nil, nil
}
}
if _, err := args.evalBlock(ctx, blockIdx, nil, false); err != nil {
if errors.As(err, &breakErr) {
if !breakErr.isCont {
return breakErr.ret, nil
}
} else {
return nil, err
}
}
}
}
func breakBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) < 1 {
return nil, errBreak{}
}
return nil, errBreak{ret: args.args[0]}
}
func continueBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
return nil, errBreak{isCont: true}
}
func returnBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) < 1 {
return nil, errReturn{}
}
return nil, errReturn{ret: args.args[0]}
}
// TODO - add tests
func errorBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) < 1 {
return nil, errors.New("need at least one arguments")
}
return nil, ErrRuntime{args.args[0].String()}
}
func assertBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) < 1 {
return nil, errors.New("need at least one arguments")
}
if isTruthy(args.args[0]) {
return nil, nil
}
if len(args.args) > 1 {
return nil, ErrRuntime{args.args[1].String()}
}
return nil, ErrRuntime{"assertion failed"}
}
func procBuiltin(ctx context.Context, args macroArgs) (Object, error) {
if args.nargs() < 1 {
return nil, errors.New("need at least one arguments")
}
var procName string
if args.nargs() == 2 {
name, ok := args.shiftIdent(ctx)
if !ok {
return nil, errors.New("malformed procedure: expected identifier as first argument")
}
procName = name
}
block, err := args.evalArg(ctx, 0)
if err != nil {
return nil, err
}
blockObj, ok := block.(blockObject)
if !ok {
return nil, fmt.Errorf("malformed procedure: expected block object, was %v", block.String())
}
obj := procObject{args.eval, args.ec, blockObj.block}
if procName != "" {
args.ec.addCmd(procName, obj)
}
return obj, nil
}
type procObject struct {
eval evaluator
ec *evalCtx
block *astBlock
}
func (b procObject) String() string {
return "(proc)"
}
func (b procObject) Truthy() bool {
return true
}
func (b procObject) invoke(ctx context.Context, args invocationArgs) (Object, error) {
newEc := b.ec.fork()
for i, name := range b.block.Names {
if i < len(args.args) {
newEc.setOrDefineVar(name, args.args[i])
} else {
newEc.setOrDefineVar(name, nil)
}
}
res, err := b.eval.evalBlock(ctx, newEc, b.block)
if err != nil {
var er errReturn
if errors.As(err, &er) {
return er.ret, nil
}
return nil, err
}
return res, nil
}