From c4e4a0977b8045e6e7209892813e034e83fda13e Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Tue, 22 Oct 2024 09:14:24 +1100 Subject: [PATCH] Added strs:split This involved exposing the internal object types, which I think was about time --- ucl/builtins.go | 136 +++++++++++++++++++------------------- ucl/builtins/strs.go | 27 ++++++++ ucl/builtins/strs_test.go | 35 ++++++++++ ucl/env.go | 10 +-- ucl/eval.go | 26 ++++---- ucl/evaldisplay.go | 4 +- ucl/inst.go | 2 +- ucl/objs.go | 68 +++++++++---------- ucl/testbuiltins_test.go | 14 ++-- ucl/userbuiltin.go | 17 +++-- 10 files changed, 204 insertions(+), 135 deletions(-) diff --git a/ucl/builtins.go b/ucl/builtins.go index 4ec30b9..371f2a3 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -8,7 +8,7 @@ import ( "strings" ) -func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if len(args.args) == 0 { if _, err := fmt.Fprintln(args.inst.Out()); err != nil { return nil, err @@ -29,7 +29,7 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) { return nil, nil } -func addBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func addBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if len(args.args) == 0 { return intObject(0), nil } @@ -53,7 +53,7 @@ func addBuiltin(ctx context.Context, args invocationArgs) (object, error) { return intObject(n), nil } -func subBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func subBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if len(args.args) == 0 { return intObject(0), nil } @@ -83,7 +83,7 @@ func subBuiltin(ctx context.Context, args invocationArgs) (object, error) { return intObject(n), nil } -func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func mupBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if len(args.args) == 0 { return intObject(1), nil } @@ -107,7 +107,7 @@ func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) { return intObject(n), nil } -func divBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func divBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if len(args.args) == 0 { return intObject(1), nil } @@ -137,7 +137,7 @@ func divBuiltin(ctx context.Context, args invocationArgs) (object, error) { return intObject(n), nil } -func modBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func modBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if len(args.args) == 0 { return intObject(0), nil } @@ -167,7 +167,7 @@ func modBuiltin(ctx context.Context, args invocationArgs) (object, error) { return intObject(n), nil } -func setBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func setBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -183,7 +183,7 @@ func setBuiltin(ctx context.Context, args invocationArgs) (object, error) { return newVal, nil } -func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func toUpperBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(1); err != nil { return nil, err } @@ -194,7 +194,7 @@ func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) { return strObject(strings.ToUpper(sarg)), nil } -func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -205,7 +205,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(objectsEqual(l, r)), nil } -func neBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -216,7 +216,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(!objectsEqual(l, r)), nil } -func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func ltBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -228,7 +228,7 @@ func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(isLess), nil } -func leBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func leBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -240,7 +240,7 @@ func leBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil } -func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func gtBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -252,7 +252,7 @@ func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(isGreater), nil } -func geBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func geBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -264,7 +264,7 @@ func geBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil } -func andBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func andBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -277,7 +277,7 @@ func andBuiltin(ctx context.Context, args invocationArgs) (object, error) { return args.args[len(args.args)-1], nil } -func orBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func orBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -290,7 +290,7 @@ func orBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(false), nil } -func notBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(1); err != nil { return nil, err } @@ -300,7 +300,7 @@ func notBuiltin(ctx context.Context, args invocationArgs) (object, error) { var errObjectsNotEqual = errors.New("objects not equal") -func objectsEqual(l, r object) bool { +func objectsEqual(l, r Object) bool { if l == nil || r == nil { return l == nil && r == nil } @@ -318,8 +318,8 @@ func objectsEqual(l, r object) bool { if rv, ok := r.(boolObject); ok { return lv == rv } - case listable: - rv, ok := r.(listable) + case Listable: + rv, ok := r.(Listable) if !ok { return false } @@ -342,7 +342,7 @@ func objectsEqual(l, r object) bool { if lv.Len() != rv.Len() { return false } - if err := lv.Each(func(k string, lkv object) error { + if err := lv.Each(func(k string, lkv Object) error { rkv := rv.Value(k) if rkv == nil { return errObjectsNotEqual @@ -365,7 +365,7 @@ func objectsEqual(l, r object) bool { return false } -func objectsLessThan(l, r object) (bool, error) { +func objectsLessThan(l, r Object) (bool, error) { switch lv := l.(type) { case strObject: if rv, ok := r.(strObject); ok { @@ -379,7 +379,7 @@ func objectsLessThan(l, r object) (bool, error) { return false, errors.New("objects are not comparable") } -func strBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func strBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(1); err != nil { return nil, err } @@ -391,7 +391,7 @@ func strBuiltin(ctx context.Context, args invocationArgs) (object, error) { return strObject(args.args[0].String()), nil } -func intBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func intBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(1); err != nil { return nil, err } @@ -419,7 +419,7 @@ func intBuiltin(ctx context.Context, args invocationArgs) (object, error) { return nil, errors.New("cannot convert to int") } -func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func concatBuiltin(ctx context.Context, args invocationArgs) (Object, error) { var sb strings.Builder for _, a := range args.args { @@ -432,7 +432,7 @@ func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) { return strObject(sb.String()), nil } -func callBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func callBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(1); err != nil { return nil, err } @@ -445,7 +445,7 @@ func callBuiltin(ctx context.Context, args invocationArgs) (object, error) { return inv.invoke(ctx, args.shift(1)) } -func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func lenBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(1); err != nil { return nil, err } @@ -453,7 +453,7 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) { switch v := args.args[0].(type) { case strObject: return intObject(len(string(v))), nil - case listable: + case Listable: return intObject(v.Len()), nil case hashable: return intObject(v.Len()), nil @@ -462,9 +462,9 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) { return intObject(0), nil } -func indexLookup(ctx context.Context, obj, elem object) (object, error) { +func indexLookup(ctx context.Context, obj, elem Object) (Object, error) { switch v := obj.(type) { - case listable: + case Listable: intIdx, ok := elem.(intObject) if !ok { return nil, nil @@ -483,7 +483,7 @@ func indexLookup(ctx context.Context, obj, elem object) (object, error) { return nil, nil } -func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func indexBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(1); err != nil { return nil, err } @@ -500,7 +500,7 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) { return val, nil } -func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func keysBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(1); err != nil { return nil, err } @@ -509,7 +509,7 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) { switch v := val.(type) { case hashable: keys := make(listObject, 0, v.Len()) - if err := v.Each(func(k string, _ object) error { + if err := v.Each(func(k string, _ Object) error { keys = append(keys, strObject(k)) return nil }); err != nil { @@ -521,7 +521,7 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) { return nil, nil } -func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func mapBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -532,12 +532,12 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) { } switch t := args.args[0].(type) { - case listable: + 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})) + m, err := inv.invoke(ctx, args.fork([]Object{v})) if err != nil { return nil, err } @@ -548,7 +548,7 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) { return nil, errors.New("expected listable") } -func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func filterBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err := args.expectArgn(2); err != nil { return nil, err } @@ -559,12 +559,12 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) { } switch t := args.args[0].(type) { - case listable: + 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})) + m, err := inv.invoke(ctx, args.fork([]Object{v})) if err != nil { return nil, err } else if m.Truthy() { @@ -574,8 +574,8 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) { 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{strObject(k), v})); err != nil { + if err := t.Each(func(k string, v Object) error { + if m, err := inv.invoke(ctx, args.fork([]Object{strObject(k), v})); err != nil { return err } else if m.Truthy() { newHash[k] = v @@ -589,14 +589,14 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) { return nil, errors.New("expected listable") } -func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) { +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 + accum Object setFirst bool block invokable ) @@ -615,7 +615,7 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) { } switch t := args.args[0].(type) { - case listable: + case Listable: l := t.Len() for i := 0; i < l; i++ { v := t.Index(i) @@ -625,7 +625,7 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) { continue } - newAccum, err := block.invoke(ctx, args.fork([]object{v, accum})) + newAccum, err := block.invoke(ctx, args.fork([]Object{v, accum})) if err != nil { return nil, err } @@ -635,8 +635,8 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) { 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{strObject(k), v, accum})) + if err := t.Each(func(k string, v Object) error { + newAccum, err := block.invoke(ctx, args.fork([]Object{strObject(k), v, accum})) if err != nil { return err } @@ -650,13 +650,13 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) { return nil, errors.New("expected listable") } -func firstBuiltin(ctx context.Context, args invocationArgs) (object, error) { +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: + case Listable: if t.Len() == 0 { return nil, nil } @@ -692,7 +692,7 @@ func (s seqObject) Len() int { return l } -func (s seqObject) Index(i int) object { +func (s seqObject) Index(i int) Object { l := s.Len() if i < 0 || i > l { return nil @@ -703,7 +703,7 @@ func (s seqObject) Index(i int) object { return intObject(s.from + i) } -func seqBuiltin(ctx context.Context, args invocationArgs) (object, error) { +func seqBuiltin(ctx context.Context, args invocationArgs) (Object, error) { inclusive := false if inc, ok := args.kwargs["inc"]; ok { inclusive = (inc.Len() == 0) || inc.Truthy() @@ -732,7 +732,7 @@ func seqBuiltin(ctx context.Context, args invocationArgs) (object, error) { } } -func ifBuiltin(ctx context.Context, args macroArgs) (object, error) { +func ifBuiltin(ctx context.Context, args macroArgs) (Object, error) { if args.nargs() < 2 { return nil, errors.New("need at least 2 arguments") } @@ -770,7 +770,7 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) { return nil, errors.New("malformed if-elif-else") } -func tryBuiltin(ctx context.Context, args macroArgs) (_ object, fnErr error) { +func tryBuiltin(ctx context.Context, args macroArgs) (_ Object, fnErr error) { if args.nargs() < 1 { return nil, errors.New("need at least 1 arguments") } @@ -782,9 +782,9 @@ func tryBuiltin(ctx context.Context, args macroArgs) (_ object, fnErr error) { } if args.nargs() >= 2 && args.identIs(ctx, args.nargs()-2, "finally") { - var blockArgs []object = nil + var blockArgs []Object = nil if fnErr != nil { - blockArgs = []object{errObject{err: fnErr}} + blockArgs = []Object{errObject{err: fnErr}} } _, err := args.evalBlock(ctx, args.nargs()-1, blockArgs, false) @@ -809,7 +809,7 @@ func tryBuiltin(ctx context.Context, args macroArgs) (_ object, fnErr error) { return nil, errors.New("need at least 1 arguments") } - res, err := args.evalBlock(ctx, 0, []object{currError}, false) + res, err := args.evalBlock(ctx, 0, []Object{currError}, false) if err == nil { return res, nil } @@ -825,9 +825,9 @@ func tryBuiltin(ctx context.Context, args macroArgs) (_ object, fnErr error) { return nil, currError.err } -func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { +func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) { var ( - items object + items Object blockIdx int err error ) @@ -850,16 +850,16 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { } var ( - last object + last Object breakErr errBreak ) switch t := items.(type) { - case listable: + 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 + last, err = args.evalBlock(ctx, blockIdx, []Object{v}, true) // TO INCLUDE: the index if err != nil { if errors.As(err, &breakErr) { if !breakErr.isCont { @@ -871,8 +871,8 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { } } case hashable: - err := t.Each(func(k string, v object) error { - last, err = args.evalBlock(ctx, blockIdx, []object{strObject(k), v}, true) + err := t.Each(func(k string, v Object) error { + last, err = args.evalBlock(ctx, blockIdx, []Object{strObject(k), v}, true) return err }) if errors.As(err, &breakErr) { @@ -887,25 +887,25 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { return last, nil } -func breakBuiltin(ctx context.Context, args invocationArgs) (object, error) { +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) { +func continueBuiltin(ctx context.Context, args invocationArgs) (Object, error) { return nil, errBreak{isCont: true} } -func returnBuiltin(ctx context.Context, args invocationArgs) (object, error) { +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) { +func procBuiltin(ctx context.Context, args macroArgs) (Object, error) { if args.nargs() < 1 { return nil, errors.New("need at least one arguments") } @@ -949,7 +949,7 @@ func (b procObject) Truthy() bool { return true } -func (b procObject) invoke(ctx context.Context, args invocationArgs) (object, error) { +func (b procObject) invoke(ctx context.Context, args invocationArgs) (Object, error) { newEc := b.ec.fork() for i, name := range b.block.Names { diff --git a/ucl/builtins/strs.go b/ucl/builtins/strs.go index a10893d..ff30cd4 100644 --- a/ucl/builtins/strs.go +++ b/ucl/builtins/strs.go @@ -13,6 +13,7 @@ func Strs() ucl.Module { "to-upper": toUpper, "to-lower": toLower, "trim": trim, + "join": join, }, } } @@ -43,3 +44,29 @@ func trim(ctx context.Context, args ucl.CallArgs) (any, error) { return strings.TrimSpace(s), nil } + +func join(ctx context.Context, args ucl.CallArgs) (any, error) { + var ( + l ucl.Listable + joinStr string + ) + if err := args.Bind(&l, &joinStr); err != nil { + return nil, err + } + + if l == nil { + return "", nil + } else if l.Len() == 0 { + return "", nil + } + + sb := strings.Builder{} + for i := 0; i < l.Len(); i++ { + if i > 0 { + sb.WriteString(joinStr) + } + sb.WriteString(l.Index(i).String()) + } + + return sb.String(), nil +} diff --git a/ucl/builtins/strs_test.go b/ucl/builtins/strs_test.go index c6ef90a..de97d7d 100644 --- a/ucl/builtins/strs_test.go +++ b/ucl/builtins/strs_test.go @@ -100,3 +100,38 @@ func TestStrs_Trim(t *testing.T) { }) } } + +func TestStrs_Join(t *testing.T) { + tests := []struct { + desc string + eval string + want any + wantErr bool + }{ + {desc: "join 1", eval: `strs:join ["a" "b" "c"] ","`, want: "a,b,c"}, + {desc: "join 2", eval: `strs:join ["a" "b" "c"] "\n"`, want: "a\nb\nc"}, + {desc: "join 3", eval: `strs:join ["a" "b" "c"] ""`, want: "abc"}, + {desc: "join 4", eval: `strs:join [] ","`, want: ""}, + + // Hmm, not super happy about this one... + {desc: "join 5", eval: `strs:join ["a" "b"] ["what"]`, want: "a[what]b"}, + + {desc: "err join 1", eval: `strs:join 123 ","`, wantErr: true}, + {desc: "err join 2", eval: `strs:join () ","`, wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + inst := ucl.New( + ucl.WithModule(builtins.Strs()), + ) + res, err := inst.Eval(context.Background(), tt.eval) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, res) + } + }) + } +} diff --git a/ucl/env.go b/ucl/env.go index 5d2697f..533d984 100644 --- a/ucl/env.go +++ b/ucl/env.go @@ -5,7 +5,7 @@ type evalCtx struct { parent *evalCtx commands map[string]invokable macros map[string]macroable - vars map[string]object + vars map[string]Object } func (ec *evalCtx) forkAndIsolate() *evalCtx { @@ -34,7 +34,7 @@ func (ec *evalCtx) addMacro(name string, inv macroable) { ec.root.macros[name] = inv } -func (ec *evalCtx) setVar(name string, val object) bool { +func (ec *evalCtx) setVar(name string, val Object) bool { if ec == nil || ec.vars == nil { return false } @@ -47,18 +47,18 @@ func (ec *evalCtx) setVar(name string, val object) bool { return ec.parent.setVar(name, val) } -func (ec *evalCtx) setOrDefineVar(name string, val object) { +func (ec *evalCtx) setOrDefineVar(name string, val Object) { if ec.setVar(name, val) { return } if ec.vars == nil { - ec.vars = make(map[string]object) + ec.vars = make(map[string]Object) } ec.vars[name] = val } -func (ec *evalCtx) getVar(name string) (object, bool) { +func (ec *evalCtx) getVar(name string) (Object, bool) { if ec.vars == nil { return nil, false } diff --git a/ucl/eval.go b/ucl/eval.go index 42b8bf0..90bde5e 100644 --- a/ucl/eval.go +++ b/ucl/eval.go @@ -10,7 +10,7 @@ type evaluator struct { inst *Inst } -func (e evaluator) evalBlock(ctx context.Context, ec *evalCtx, n *astBlock) (lastRes object, err error) { +func (e evaluator) evalBlock(ctx context.Context, ec *evalCtx, n *astBlock) (lastRes Object, err error) { // TODO: push scope? for _, s := range n.Statements { @@ -22,11 +22,11 @@ func (e evaluator) evalBlock(ctx context.Context, ec *evalCtx, n *astBlock) (las return lastRes, nil } -func (e evaluator) evalScript(ctx context.Context, ec *evalCtx, n *astScript) (lastRes object, err error) { +func (e evaluator) evalScript(ctx context.Context, ec *evalCtx, n *astScript) (lastRes Object, err error) { return e.evalStatement(ctx, ec, n.Statements) } -func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (object, error) { +func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (Object, error) { if n == nil { return nil, nil } @@ -49,7 +49,7 @@ func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStateme return res, nil } -func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline) (object, error) { +func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline) (Object, error) { res, err := e.evalCmd(ctx, ec, nil, n.First) if err != nil { return nil, err @@ -69,7 +69,7 @@ func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline return res, nil } -func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd) (object, error) { +func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd) (Object, error) { switch { case (ast.Name.Arg.Ident != nil) && len(ast.Name.DotSuffix) == 0: name := ast.Name.Arg.Ident.String() @@ -105,7 +105,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object, return nameElem, nil } -func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd, cmd invokable) (object, error) { +func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd, cmd invokable) (Object, error) { var ( pargs listObject kwargs map[string]*listObject @@ -138,7 +138,7 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe o return cmd.invoke(ctx, invArgs) } -func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, hasPipe bool, pipeArg object, ast *astCmd, cmd macroable) (object, error) { +func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, hasPipe bool, pipeArg Object, ast *astCmd, cmd macroable) (Object, error) { return cmd.invokeMacro(ctx, macroArgs{ eval: e, ec: ec, @@ -148,7 +148,7 @@ func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, hasPipe bool, pip }) } -func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object, error) { +func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (Object, error) { res, err := e.evalArg(ctx, ec, n.Arg) if err != nil { return nil, err @@ -157,7 +157,7 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object, } for _, dot := range n.DotSuffix { - var idx object + var idx Object if dot.KeyIdent != nil { idx = strObject(dot.KeyIdent.String()) } else { @@ -175,7 +175,7 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object, return res, nil } -func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (object, error) { +func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Object, error) { switch { case n.Literal != nil: return e.evalLiteral(ctx, ec, n.Literal) @@ -200,7 +200,7 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (objec return nil, errors.New("unhandled arg type") } -func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (object, error) { +func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (Object, error) { if loh.EmptyList { return listObject{}, nil } else if loh.EmptyHash { @@ -243,7 +243,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList return l, nil } -func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (object, error) { +func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (Object, error) { switch { case n.Str != nil: uq, err := strconv.Unquote(*n.Str) @@ -257,7 +257,7 @@ func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) return nil, errors.New("unhandled literal type") } -func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (object, error) { +func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (Object, error) { pipelineRes, err := e.evalPipeline(ctx, ec, n) if err != nil { return nil, err diff --git a/ucl/evaldisplay.go b/ucl/evaldisplay.go index 5b4a209..b73fe87 100644 --- a/ucl/evaldisplay.go +++ b/ucl/evaldisplay.go @@ -14,13 +14,13 @@ func EvalAndDisplay(ctx context.Context, inst *Inst, expr string) error { return displayResult(ctx, inst, res) } -func displayResult(ctx context.Context, inst *Inst, res object) (err error) { +func displayResult(ctx context.Context, inst *Inst, res Object) (err error) { switch v := res.(type) { case nil: if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil { return err } - case listable: + case Listable: for i := 0; i < v.Len(); i++ { if err = displayResult(ctx, inst, v.Index(i)); err != nil { return err diff --git a/ucl/inst.go b/ucl/inst.go index 5091ac1..07b316c 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -134,7 +134,7 @@ func (inst *Inst) Eval(ctx context.Context, expr string) (any, error) { return goRes, nil } -func (inst *Inst) eval(ctx context.Context, expr string) (object, error) { +func (inst *Inst) eval(ctx context.Context, expr string) (Object, error) { ast, err := parse(strings.NewReader(expr)) if err != nil { return nil, err diff --git a/ucl/objs.go b/ucl/objs.go index 5544cf3..50bb7a1 100644 --- a/ucl/objs.go +++ b/ucl/objs.go @@ -11,30 +11,30 @@ import ( "github.com/lmika/gopkgs/fp/slices" ) -type object interface { +type Object interface { String() string Truthy() bool } -type listable interface { +type Listable interface { Len() int - Index(i int) object + Index(i int) Object } type hashable interface { Len() int - Value(k string) object - Each(func(k string, v object) error) error + Value(k string) Object + Each(func(k string, v Object) error) error } -type listObject []object +type listObject []Object -func (lo *listObject) Append(o object) { +func (lo *listObject) Append(o Object) { *lo = append(*lo, o) } func (s listObject) String() string { - return fmt.Sprintf("%v", []object(s)) + return fmt.Sprintf("%v", []Object(s)) } func (s listObject) Truthy() bool { @@ -45,11 +45,11 @@ func (s listObject) Len() int { return len(s) } -func (s listObject) Index(i int) object { +func (s listObject) Index(i int) Object { return s[i] } -type hashObject map[string]object +type hashObject map[string]Object func (s hashObject) String() string { if len(s) == 0 { @@ -78,11 +78,11 @@ func (s hashObject) Len() int { return len(s) } -func (s hashObject) Value(k string) object { +func (s hashObject) Value(k string) Object { return s[k] } -func (s hashObject) Each(fn func(k string, v object) error) error { +func (s hashObject) Each(fn func(k string, v Object) error) error { for k, v := range s { if err := fn(k, v); err != nil { return err @@ -124,7 +124,7 @@ func (b boolObject) Truthy() bool { return bool(b) } -func toGoValue(obj object) (interface{}, bool) { +func toGoValue(obj Object) (interface{}, bool) { switch v := obj.(type) { case OpaqueObject: return v.v, true @@ -167,7 +167,7 @@ func toGoValue(obj object) (interface{}, bool) { return nil, false } -func fromGoValue(v any) (object, error) { +func fromGoValue(v any) (Object, error) { switch t := v.(type) { case OpaqueObject: return t, nil @@ -184,7 +184,7 @@ func fromGoValue(v any) (object, error) { return fromGoReflectValue(reflect.ValueOf(v)) } -func fromGoReflectValue(resVal reflect.Value) (object, error) { +func fromGoReflectValue(resVal reflect.Value) (Object, error) { if !resVal.IsValid() { return nil, nil } @@ -212,7 +212,7 @@ type macroArgs struct { eval evaluator ec *evalCtx hasPipe bool - pipeArg object + pipeArg Object ast *astCmd argShift int } @@ -259,7 +259,7 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) { return "", false } -func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) { +func (ma macroArgs) evalArg(ctx context.Context, n int) (Object, error) { if n >= len(ma.ast.Args[ma.argShift:]) { return nil, errors.New("not enough arguments") // FIX } @@ -267,7 +267,7 @@ func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) { return ma.eval.evalDot(ctx, ma.ec, ma.ast.Args[ma.argShift+n]) } -func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushScope bool) (object, error) { +func (ma macroArgs) evalBlock(ctx context.Context, n int, args []Object, pushScope bool) (Object, error) { obj, err := ma.evalArg(ctx, n) if err != nil { return nil, err @@ -317,7 +317,7 @@ type invocationArgs struct { eval evaluator inst *Inst ec *evalCtx - args []object + args []Object kwargs map[string]*listObject } @@ -370,7 +370,7 @@ func (ia invocationArgs) invokableArg(i int) (invokable, error) { return nil, errors.New("expected an invokable arg") } -func (ia invocationArgs) fork(args []object) invocationArgs { +func (ia invocationArgs) fork(args []Object) invocationArgs { return invocationArgs{ eval: ia.eval, inst: ia.inst, @@ -393,22 +393,22 @@ func (ia invocationArgs) shift(i int) invocationArgs { } } -// invokable is an object that can be executed as a command +// invokable is an Object that can be executed as a command type invokable interface { - invoke(ctx context.Context, args invocationArgs) (object, error) + invoke(ctx context.Context, args invocationArgs) (Object, error) } type macroable interface { - invokeMacro(ctx context.Context, args macroArgs) (object, error) + invokeMacro(ctx context.Context, args macroArgs) (Object, error) } type pipeInvokable interface { invokable } -type invokableFunc func(ctx context.Context, args invocationArgs) (object, error) +type invokableFunc func(ctx context.Context, args invocationArgs) (Object, error) -func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (object, error) { +func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (Object, error) { return i(ctx, args) } @@ -424,7 +424,7 @@ func (bo blockObject) Truthy() bool { return len(bo.block.Statements) > 0 } -func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object, error) { +func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (Object, error) { ec := args.ec.fork() for i, n := range bo.block.Names { if i < len(args.args) { @@ -435,13 +435,13 @@ func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object, return args.eval.evalBlock(ctx, ec, bo.block) } -type macroFunc func(ctx context.Context, args macroArgs) (object, error) +type macroFunc func(ctx context.Context, args macroArgs) (Object, error) -func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (object, error) { +func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (Object, error) { return i(ctx, args) } -func isTruthy(obj object) bool { +func isTruthy(obj Object) bool { if obj == nil { return false } @@ -477,7 +477,7 @@ func (p listableProxyObject) Len() int { return p.v.Len() } -func (p listableProxyObject) Index(i int) object { +func (p listableProxyObject) Index(i int) Object { e, err := fromGoValue(p.v.Index(i).Interface()) if err != nil { return nil @@ -511,7 +511,7 @@ func (s structProxyObject) Len() int { return len(s.vf) } -func (s structProxyObject) Value(k string) object { +func (s structProxyObject) Value(k string) Object { f := s.v.FieldByName(k) if !f.IsValid() { return nil @@ -531,7 +531,7 @@ func (s structProxyObject) Value(k string) object { return e } -func (s structProxyObject) Each(fn func(k string, v object) error) error { +func (s structProxyObject) Each(fn func(k string, v Object) error) error { for _, f := range s.vf { v, err := fromGoValue(s.v.FieldByName(f.Name).Interface()) if err != nil { @@ -563,7 +563,7 @@ func (p OpaqueObject) Truthy() bool { type errBreak struct { isCont bool - ret object + ret Object } func (e errBreak) Error() string { @@ -574,7 +574,7 @@ func (e errBreak) Error() string { } type errReturn struct { - ret object + ret Object } func (e errReturn) Error() string { diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index 81f321f..5fa3a15 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -14,15 +14,15 @@ import ( // Builtins used for test func WithTestBuiltin() InstOption { return func(i *Inst) { - i.rootEC.addCmd("firstarg", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) { + i.rootEC.addCmd("firstarg", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { return args.args[0], nil })) - i.rootEC.addCmd("toUpper", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) { + i.rootEC.addCmd("toUpper", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { return strObject(strings.ToUpper(args.args[0].String())), nil })) - i.rootEC.addCmd("sjoin", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) { + i.rootEC.addCmd("sjoin", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { if len(args.args) == 0 { return strObject(""), nil } @@ -37,21 +37,21 @@ func WithTestBuiltin() InstOption { return strObject(line.String()), nil })) - i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) { + i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { return listObject(args.args), nil })) - i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) { + i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { if len(args.args) == 0 { return nil, errors.New("an error occurred") } return nil, errors.New(args.args[0].String()) })) - i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) { + i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { sb := strings.Builder{} - lst, ok := args.args[0].(listable) + lst, ok := args.args[0].(Listable) if !ok { return strObject(""), nil } diff --git a/ucl/userbuiltin.go b/ucl/userbuiltin.go index 79b5b05..04021c3 100644 --- a/ucl/userbuiltin.go +++ b/ucl/userbuiltin.go @@ -85,7 +85,7 @@ type userBuiltin struct { fn func(ctx context.Context, args CallArgs) (any, error) } -func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, error) { +func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (Object, error) { v, err := u.fn(ctx, CallArgs{args: args}) if err != nil { return nil, err @@ -94,7 +94,7 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e return fromGoValue(v) } -func (ca CallArgs) bindArg(v interface{}, arg object) error { +func (ca CallArgs) bindArg(v interface{}, arg Object) error { switch t := v.(type) { case *interface{}: *t, _ = toGoValue(arg) @@ -110,6 +110,13 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error { ec: ca.args.ec, } return nil + case *Listable: + i, ok := arg.(Listable) + if !ok { + return errors.New("exepected listable") + } + *t = i + return nil case *string: if arg != nil { *t = arg.String() @@ -153,7 +160,7 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error { return nil } -func canBindArg(v interface{}, arg object) bool { +func canBindArg(v interface{}, arg Object) bool { switch v.(type) { case *string: return true @@ -233,7 +240,7 @@ type missingHandlerInvokable struct { handler MissingBuiltinHandler } -func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs) (object, error) { +func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs) (Object, error) { v, err := m.handler(ctx, m.name, CallArgs{args: args}) if err != nil { return nil, err @@ -265,7 +272,7 @@ func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) { inst: i.inst, } - invArgs.args, err = slices.MapWithError(args, func(a any) (object, error) { + invArgs.args, err = slices.MapWithError(args, func(a any) (Object, error) { return fromGoValue(a) }) if err != nil {