From 0ddffcc489c8e81a23e3b242794e71b830cc7649 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Thu, 10 Apr 2025 21:35:12 +1000 Subject: [PATCH] Fixed tests --- go.mod | 3 +-- ucl/ast.go | 4 ++-- ucl/builtins.go | 21 ++++++++++++++++++++- ucl/builtins/csv.go | 16 ++++++++++++++++ ucl/builtins/lists_test.go | 0 ucl/env.go | 12 ++++++------ ucl/eval.go | 16 ++++++++-------- ucl/evaldisplay.go | 35 ----------------------------------- ucl/inst.go | 10 ++++++++++ ucl/inst_test.go | 2 +- ucl/testbuiltins_test.go | 11 ++++------- ucl/userbuiltin.go | 2 +- ucl/userbuiltin_test.go | 1 + 13 files changed, 70 insertions(+), 63 deletions(-) delete mode 100644 ucl/builtins/lists_test.go delete mode 100644 ucl/evaldisplay.go diff --git a/go.mod b/go.mod index 981a55a..faa3ccb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module ucl.lmika.dev -go 1.21.1 +go 1.24 require ( github.com/alecthomas/participle/v2 v2.1.1 @@ -13,7 +13,6 @@ require ( github.com/BurntSushi/toml v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.9.0 // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/ucl/ast.go b/ucl/ast.go index 8b118ad..29478ab 100644 --- a/ucl/ast.go +++ b/ucl/ast.go @@ -75,7 +75,7 @@ type astListOrHash struct { type astBlock struct { Names []string `parser:"LC NL? (PIPE @Ident+ PIPE NL?)?"` - Statements []*astStatements `parser:"@@ NL? RC"` + Statements []*astStatements `parser:"@@* NL? RC"` } type astMaybeSub struct { @@ -139,7 +139,7 @@ var scanner = lexer.MustStateful(lexer.Rules{ {"RC", `\}`, nil}, {"NL", `[;\n][; \n\t]*`, nil}, {"PIPE", `\|`, nil}, - {"Ident", `[-]*[a-zA-Z_][\w-]*`, nil}, + {"Ident", `[-]*[a-zA-Z_][\w-!?]*`, nil}, }, "String": { {"Escaped", `\\.`, nil}, diff --git a/ucl/builtins.go b/ucl/builtins.go index ae1fcdc..f6ad77f 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -196,6 +196,25 @@ func setBuiltin(ctx context.Context, args invocationArgs) (Object, error) { 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 @@ -878,7 +897,7 @@ func ifBuiltin(ctx context.Context, args macroArgs) (Object, error) { return nil, errors.New("malformed if-elif-else") } -func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { +func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) { var ( items Object blockIdx int diff --git a/ucl/builtins/csv.go b/ucl/builtins/csv.go index 3b00cf4..be54667 100644 --- a/ucl/builtins/csv.go +++ b/ucl/builtins/csv.go @@ -68,6 +68,22 @@ func (h csvHandlers) eachRecord(ctx context.Context, args ucl.CallArgs) (any, er return nil, nil } +type stringSlice []string + +func (ss stringSlice) String() string { + return strings.Join(ss, ",") +} +func (ss stringSlice) Truthy() bool { + return len(ss) > 0 +} + +func (ss stringSlice) Len() int { + return len(ss) +} +func (ss stringSlice) Index(i int) ucl.Object { + return ucl.StringObject(ss[i]) +} + type headerIndexObject map[string]int func (hio headerIndexObject) String() string { diff --git a/ucl/builtins/lists_test.go b/ucl/builtins/lists_test.go deleted file mode 100644 index e69de29..0000000 diff --git a/ucl/env.go b/ucl/env.go index 5d2697f..0a3d52f 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,20 +47,20 @@ 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 + return ec.parent.getVar(name) } if v, ok := ec.vars[name]; ok { diff --git a/ucl/eval.go b/ucl/eval.go index 4f96ce3..237864b 100644 --- a/ucl/eval.go +++ b/ucl/eval.go @@ -108,9 +108,9 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object, func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd, cmd invokable) (Object, error) { var ( - pargs listObject - kwargs map[string]*listObject - argsPtr *listObject + pargs ListObject + kwargs map[string]*ListObject + argsPtr *ListObject ) argsPtr = &pargs @@ -121,10 +121,10 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe O if ident := arg.Arg.Ident; len(arg.DotSuffix) == 0 && ident != nil && ident.String()[0] == '-' { // Arg switch if kwargs == nil { - kwargs = make(map[string]*listObject) + kwargs = make(map[string]*ListObject) } - argsPtr = &listObject{} + argsPtr = &ListObject{} kwargs[ident.String()[1:]] = argsPtr } else { ae, err := e.evalDot(ctx, ec, arg) @@ -203,7 +203,7 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (Object, error) { if loh.EmptyList { - return listObject{}, nil + return &ListObject{}, nil } else if loh.EmptyHash { return hashObject{}, nil } @@ -230,7 +230,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList return h, nil } - l := listObject{} + l := ListObject{} for _, el := range loh.Elements { if el.Right != nil { return nil, errors.New("miss-match of lists and hash") @@ -241,7 +241,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList } l = append(l, v) } - return l, nil + return &l, nil } func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (Object, error) { diff --git a/ucl/evaldisplay.go b/ucl/evaldisplay.go deleted file mode 100644 index 5b4a209..0000000 --- a/ucl/evaldisplay.go +++ /dev/null @@ -1,35 +0,0 @@ -package ucl - -import ( - "context" - "fmt" -) - -func EvalAndDisplay(ctx context.Context, inst *Inst, expr string) error { - res, err := inst.eval(ctx, expr) - if err != nil { - return err - } - - return displayResult(ctx, inst, res) -} - -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: - for i := 0; i < v.Len(); i++ { - if err = displayResult(ctx, inst, v.Index(i)); err != nil { - return err - } - } - default: - if _, err = fmt.Fprintln(inst.out, v.String()); err != nil { - return err - } - } - return nil -} diff --git a/ucl/inst.go b/ucl/inst.go index 80135dd..81974ba 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -57,14 +57,19 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("echo", invokableFunc(echoBuiltin)) rootEC.addCmd("set", invokableFunc(setBuiltin)) + rootEC.addCmd("set!", invokableFunc(mustSetBuiltin)) rootEC.addCmd("len", invokableFunc(lenBuiltin)) rootEC.addCmd("index", invokableFunc(indexBuiltin)) rootEC.addCmd("call", invokableFunc(callBuiltin)) rootEC.addCmd("seq", invokableFunc(seqBuiltin)) rootEC.addCmd("map", invokableFunc(mapBuiltin)) + rootEC.addCmd("filter", invokableFunc(filterBuiltin)) + rootEC.addCmd("reduce", invokableFunc(reduceBuiltin)) rootEC.addCmd("head", invokableFunc(firstBuiltin)) + rootEC.addCmd("keys", invokableFunc(keysBuiltin)) + rootEC.addCmd("eq", invokableFunc(eqBuiltin)) rootEC.addCmd("ne", invokableFunc(neBuiltin)) rootEC.addCmd("gt", invokableFunc(gtBuiltin)) @@ -75,11 +80,16 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("str", invokableFunc(strBuiltin)) rootEC.addCmd("int", invokableFunc(intBuiltin)) + rootEC.addCmd("add", invokableFunc(addBuiltin)) rootEC.addCmd("sub", invokableFunc(subBuiltin)) rootEC.addCmd("mup", invokableFunc(mupBuiltin)) rootEC.addCmd("div", invokableFunc(divBuiltin)) rootEC.addCmd("mod", invokableFunc(modBuiltin)) + rootEC.addCmd("and", invokableFunc(andBuiltin)) + rootEC.addCmd("or", invokableFunc(orBuiltin)) + rootEC.addCmd("not", invokableFunc(notBuiltin)) + rootEC.addCmd("cat", invokableFunc(concatBuiltin)) rootEC.addCmd("break", invokableFunc(breakBuiltin)) rootEC.addCmd("continue", invokableFunc(continueBuiltin)) diff --git a/ucl/inst_test.go b/ucl/inst_test.go index ca20469..47e8eff 100644 --- a/ucl/inst_test.go +++ b/ucl/inst_test.go @@ -3,7 +3,7 @@ package ucl_test import ( "bytes" "context" - "github.com/lmika/ucl/ucl" + "ucl.lmika.dev/ucl" "github.com/stretchr/testify/assert" "testing" diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index ee24282..8cb8cfe 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -3,6 +3,7 @@ package ucl import ( "bytes" "context" + "errors" "fmt" "github.com/stretchr/testify/assert" "strings" @@ -237,7 +238,6 @@ func TestBuiltins_If(t *testing.T) { } } - func TestBuiltins_ForEach(t *testing.T) { tests := []struct { desc string @@ -1258,8 +1258,8 @@ func TestBuiltins_EqNe(t *testing.T) { {desc: "equal bools 1", expr: `eq $true $true`, want: true}, {desc: "equal bools 2", expr: `eq $false $false`, want: true}, {desc: "equal nil 1", expr: `eq () ()`, want: true}, - {desc: "equal opaque 1", expr: `eq $hello $hello`, want: true}, - {desc: "equal opaque 2", expr: `eq $world $world`, want: true}, + {desc: "equal undef 1", expr: `eq $undef $missing`, want: true}, + {desc: "equal undef 2", expr: `eq $missing $undef`, want: true}, {desc: "not equal strs 1", expr: `eq "hello" "world"`, want: false}, {desc: "not equal strs 2", expr: `eq "bla" "BLA"`, want: false}, @@ -1270,8 +1270,6 @@ func TestBuiltins_EqNe(t *testing.T) { {desc: "not equal hashes 1", expr: `eq ["this":1 "that":"thing"] ["that":"thing"]`, want: false}, {desc: "not equal hashes 2", expr: `eq ["this":1 "that":"thing"] ["this":1 "that":"thing" "other":"thing"]`, want: false}, {desc: "not equal hashes 3", expr: `eq ["this":1 "that":"thing"] ["this":"1" "that":"other"]`, want: false}, - {desc: "not equal opaque 1", expr: `eq $hello $world`, want: false}, - {desc: "not equal opaque 2", expr: `eq $hello "hello"`, want: false}, {desc: "not equal types 1", expr: `eq "123" 123`, want: false}, {desc: "not equal types 2", expr: `eq 0 ""`, want: false}, @@ -1280,7 +1278,7 @@ func TestBuiltins_EqNe(t *testing.T) { {desc: "not equal types 5", expr: `eq $true ()`, want: false}, {desc: "not equal types 6", expr: `eq () $false`, want: false}, {desc: "not equal types 7", expr: `eq () "yes"`, want: false}, - {desc: "not equal types 8", expr: `eq () $world`, want: false}, + {desc: "not equal types 8", expr: `eq () $undef`, want: true}, } for _, tt := range tests { @@ -1293,7 +1291,6 @@ func TestBuiltins_EqNe(t *testing.T) { outW := bytes.NewBuffer(nil) inst := New(WithOut(outW), WithTestBuiltin()) - // Removed code I don't have the rights to inst.SetVar("true", true) inst.SetVar("false", false) diff --git a/ucl/userbuiltin.go b/ucl/userbuiltin.go index 84dfa5b..ad39095 100644 --- a/ucl/userbuiltin.go +++ b/ucl/userbuiltin.go @@ -26,7 +26,7 @@ func (ca *CallArgs) Bind(vars ...interface{}) error { } for i, v := range vars { - if err := bindArg(v, ca.args.args[i]); err != nil { + if err := ca.bindArg(v, ca.args.args[i]); err != nil { return err } } diff --git a/ucl/userbuiltin_test.go b/ucl/userbuiltin_test.go index adb657a..5a0e1c0 100644 --- a/ucl/userbuiltin_test.go +++ b/ucl/userbuiltin_test.go @@ -1,6 +1,7 @@ package ucl_test import ( + "bytes" "context" "fmt" "strings"