986 lines
20 KiB
Go
986 lines
20 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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
newVal := args.args[1]
|
|
|
|
args.ec.setOrDefineVar(name, newVal)
|
|
return newVal, nil
|
|
}
|
|
|
|
func toUpperBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|
if err := args.expectArgn(1); err != nil {
|
|
return nil, err
|
|
}
|
|
sarg, err := args.stringArg(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return StringObject(strings.ToUpper(sarg)), 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 boolObject(false), 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 {
|
|
if a != nil && a.Truthy() {
|
|
return a, nil
|
|
}
|
|
}
|
|
return boolObject(false), 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
|
|
case OpaqueObject:
|
|
rv, ok := r.(OpaqueObject)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
return lv.v == rv.v
|
|
}
|
|
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
|
|
}
|
|
|
|
inv, ok := args.args[0].(invokable)
|
|
if !ok {
|
|
return nil, errors.New("expected invokable")
|
|
}
|
|
|
|
return inv.invoke(ctx, args.shift(1))
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
return nil, errors.New("expected listable")
|
|
}
|
|
|
|
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.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.Truthy() {
|
|
newHash[k] = v
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return newHash, 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
|
|
}
|
|
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
|
|
}
|
|
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) (_ Object, fnErr error) {
|
|
if args.nargs() < 1 {
|
|
return nil, errors.New("need at least 1 arguments")
|
|
}
|
|
|
|
var currError errObject
|
|
defer func() {
|
|
if errors.As(fnErr, &errBadUsage{}) {
|
|
return
|
|
}
|
|
|
|
if args.nargs() >= 2 && args.identIs(ctx, args.nargs()-2, "finally") {
|
|
var blockArgs []Object = nil
|
|
if fnErr != nil {
|
|
blockArgs = []Object{errObject{err: fnErr}}
|
|
}
|
|
|
|
_, err := args.evalBlock(ctx, args.nargs()-1, blockArgs, false)
|
|
if err != nil && fnErr == nil {
|
|
fnErr = err
|
|
}
|
|
}
|
|
}()
|
|
|
|
res, err := args.evalBlock(ctx, 0, nil, false)
|
|
args.shift(1)
|
|
if err == nil {
|
|
return res, nil
|
|
}
|
|
|
|
currError = errObject{err: err}
|
|
|
|
for args.identIs(ctx, 0, "catch") {
|
|
args.shift(1)
|
|
|
|
if args.nargs() < 1 {
|
|
return nil, errors.New("need at least 1 arguments")
|
|
}
|
|
|
|
res, err := args.evalBlock(ctx, 0, []Object{currError}, false)
|
|
if err == nil {
|
|
return res, nil
|
|
}
|
|
|
|
currError = errObject{err: err}
|
|
args.shift(1)
|
|
}
|
|
|
|
if args.identIs(ctx, 0, "finally") && args.nargs() > 2 {
|
|
return nil, tooManyFinallyBlocksError(args.ast.Pos)
|
|
}
|
|
|
|
return nil, currError.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}, true) // 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}, true)
|
|
return err
|
|
})
|
|
if errors.As(err, &breakErr) {
|
|
if !breakErr.isCont {
|
|
return breakErr.ret, nil
|
|
}
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return last, nil
|
|
}
|
|
|
|
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]}
|
|
}
|
|
|
|
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
|
|
}
|