diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index 6b08303..8ffd2dc 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -14,7 +14,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: 1.24 + go-version: 1.22.4 - uses: actions/setup-node@v4 with: node-version: 21.1 diff --git a/ucl/ast.go b/ucl/ast.go index ab2e2bc..250587d 100644 --- a/ucl/ast.go +++ b/ucl/ast.go @@ -103,9 +103,9 @@ type astDot struct { } type astCmd struct { - Pos lexer.Position - Name astDot `parser:"@@"` - InvokeArgs []astDot `parser:"@@*"` + Pos lexer.Position + Name astDot `parser:"@@"` + Args []astDot `parser:"@@*"` } type astPipeline struct { @@ -113,15 +113,9 @@ type astPipeline struct { Rest []*astCmd `parser:"( PIPE @@ )*"` } -type astAssignOrPipeline struct { - First *astCmd `parser:"@@"` - Assign *astPipeline `parser:"( EQ @@ "` - Pipeline []*astCmd `parser:"| ( PIPE @@ )+ )?"` -} - type astStatements struct { - First *astAssignOrPipeline `parser:"@@"` - Rest []*astAssignOrPipeline `parser:"( NL+ @@ )*"` // TODO: also add support for newlines + First *astPipeline `parser:"@@"` + Rest []*astPipeline `parser:"( NL+ @@ )*"` // TODO: also add support for newlines } type astScript struct { @@ -147,7 +141,6 @@ var scanner = lexer.MustStateful(lexer.Rules{ {"RC", `\}`, nil}, {"NL", `[;\n][; \n\t]*`, nil}, {"PIPE", `\|`, nil}, - {"EQ", `=`, nil}, {"Ident", `[-]*[a-zA-Z_][\w-!?]*`, nil}, }, "String": { diff --git a/ucl/builtins.go b/ucl/builtins.go index da7ddc4..59faca9 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -180,6 +180,64 @@ func modBuiltin(ctx context.Context, args invocationArgs) (Object, error) { 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 @@ -188,7 +246,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) { l := args.args[0] r := args.args[1] - return BoolObject(objectsEqual(l, r)), nil + return boolObject(objectsEqual(l, r)), nil } func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) { @@ -199,7 +257,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) { l := args.args[0] r := args.args[1] - return BoolObject(!objectsEqual(l, r)), nil + return boolObject(!objectsEqual(l, r)), nil } func ltBuiltin(ctx context.Context, args invocationArgs) (Object, error) { @@ -211,7 +269,7 @@ func ltBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err != nil { return nil, err } - return BoolObject(isLess), nil + return boolObject(isLess), nil } func leBuiltin(ctx context.Context, args invocationArgs) (Object, error) { @@ -223,7 +281,7 @@ func leBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err != nil { return nil, err } - return BoolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil + return boolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil } func gtBuiltin(ctx context.Context, args invocationArgs) (Object, error) { @@ -235,7 +293,7 @@ func gtBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err != nil { return nil, err } - return BoolObject(isGreater), nil + return boolObject(isGreater), nil } func geBuiltin(ctx context.Context, args invocationArgs) (Object, error) { @@ -247,7 +305,7 @@ func geBuiltin(ctx context.Context, args invocationArgs) (Object, error) { if err != nil { return nil, err } - return BoolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil + return boolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil } func andBuiltin(ctx context.Context, args invocationArgs) (Object, error) { @@ -281,7 +339,7 @@ func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) { return nil, err } - return BoolObject(!args.args[0].Truthy()), nil + return boolObject(!args.args[0].Truthy()), nil } var errObjectsNotEqual = errors.New("objects not equal") @@ -300,8 +358,8 @@ func objectsEqual(l, r Object) bool { if rv, ok := r.(IntObject); ok { return lv == rv } - case BoolObject: - if rv, ok := r.(BoolObject); ok { + case boolObject: + if rv, ok := r.(boolObject); ok { return lv == rv } case Listable: @@ -388,7 +446,7 @@ func intBuiltin(ctx context.Context, args invocationArgs) (Object, error) { return nil, errors.New("cannot convert to int") } return IntObject(i), nil - case BoolObject: + case boolObject: if v { return IntObject(1), nil } @@ -910,72 +968,6 @@ func ifBuiltin(ctx context.Context, args macroArgs) (Object, error) { 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 @@ -1111,31 +1103,6 @@ func returnBuiltin(ctx context.Context, args invocationArgs) (Object, error) { 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") diff --git a/ucl/builtins/strs.go b/ucl/builtins/strs.go index 03801d0..fb6a3d3 100644 --- a/ucl/builtins/strs.go +++ b/ucl/builtins/strs.go @@ -77,7 +77,7 @@ func split(ctx context.Context, args ucl.CallArgs) (any, error) { } } - return ucl.StringListObject(strings.SplitN(s, sep, n)), nil + return StringSlice(strings.SplitN(s, sep, n)), nil } func join(ctx context.Context, args ucl.CallArgs) (any, error) { @@ -128,3 +128,5 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) { return nil, errors.New("expected listable or iterable as arg 1") } + +type StringSlice []string diff --git a/ucl/builtins/strs_test.go b/ucl/builtins/strs_test.go index 3fc4cf6..6af7082 100644 --- a/ucl/builtins/strs_test.go +++ b/ucl/builtins/strs_test.go @@ -140,17 +140,17 @@ func TestStrs_Split(t *testing.T) { want any wantErr bool }{ - {desc: "split 1", eval: `strs:split "1,2,3" ","`, want: []string{"1", "2", "3"}}, - {desc: "split 2", eval: `strs:split "1,2,3" ";"`, want: []string{"1,2,3"}}, - {desc: "split 3", eval: `strs:split "" ";"`, want: []string{""}}, - {desc: "split 4", eval: `strs:split " " ";"`, want: []string{" "}}, + {desc: "split 1", eval: `strs:split "1,2,3" ","`, want: builtins.StringSlice{"1", "2", "3"}}, + {desc: "split 2", eval: `strs:split "1,2,3" ";"`, want: builtins.StringSlice{"1,2,3"}}, + {desc: "split 3", eval: `strs:split "" ";"`, want: builtins.StringSlice{""}}, + {desc: "split 4", eval: `strs:split " " ";"`, want: builtins.StringSlice{" "}}, - {desc: "split by char 1", eval: `strs:split "123"`, want: []string{"1", "2", "3"}}, + {desc: "split by char 1", eval: `strs:split "123"`, want: builtins.StringSlice{"1", "2", "3"}}, - {desc: "split max 1", eval: `strs:split "1,2,3" "," -max 2`, want: []string{"1", "2,3"}}, - {desc: "split max 2", eval: `strs:split "1,2,3" "," -max 5`, want: []string{"1", "2", "3"}}, + {desc: "split max 1", eval: `strs:split "1,2,3" "," -max 2`, want: builtins.StringSlice{"1", "2,3"}}, + {desc: "split max 2", eval: `strs:split "1,2,3" "," -max 5`, want: builtins.StringSlice{"1", "2", "3"}}, - {desc: "split by char max 1", eval: `strs:split "12345" -max 3`, want: []string{"1", "2", "345"}}, + {desc: "split by char max 1", eval: `strs:split "12345" -max 3`, want: builtins.StringSlice{"1", "2", "345"}}, {desc: "err 1", eval: `strs:split "1,2,3" -max []`, wantErr: true}, } diff --git a/ucl/eval.go b/ucl/eval.go index 89172f8..ab9f26a 100644 --- a/ucl/eval.go +++ b/ucl/eval.go @@ -32,7 +32,7 @@ func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStateme return nil, nil } - res, err := e.evalAssignOrPipeline(ctx, ec, n.First) + res, err := e.evalPipeline(ctx, ec, n.First) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStateme } for _, rest := range n.Rest { - out, err := e.evalAssignOrPipeline(ctx, ec, rest) + out, err := e.evalPipeline(ctx, ec, rest) if err != nil { return nil, err } @@ -50,36 +50,6 @@ func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStateme return res, nil } -func (e evaluator) evalAssignOrPipeline(ctx context.Context, ec *evalCtx, n *astAssignOrPipeline) (Object, error) { - switch { - case n.Assign != nil: - // Assignment - assignVal, err := e.evalPipeline(ctx, ec, n.Assign) - if err != nil { - return nil, err - } - - return e.assignCmd(ctx, ec, n.First, assignVal) - case len(n.Pipeline) > 0: - res, err := e.evalCmd(ctx, ec, nil, n.First) - if err != nil { - return nil, err - } - - for _, rest := range n.Pipeline { - out, err := e.evalCmd(ctx, ec, res, rest) - if err != nil { - return nil, err - } - res = out - } - return res, nil - - } - - return e.evalCmd(ctx, ec, nil, n.First) -} - 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 { @@ -115,7 +85,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object, } else { return nil, errors.New("unknown command: " + name) } - case len(ast.InvokeArgs) > 0: + case len(ast.Args) > 0: nameElem, err := e.evalDot(ctx, ec, ast.Name) if err != nil { return nil, err @@ -136,13 +106,6 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object, return nameElem, nil } -func (e evaluator) assignCmd(ctx context.Context, ec *evalCtx, ast *astCmd, toVal Object) (Object, error) { - if len(ast.InvokeArgs) != 0 { - return nil, errors.New("cannot assign to multiple values") - } - return e.assignDot(ctx, ec, ast.Name, toVal) -} - func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd, cmd invokable) (Object, error) { var ( pargs ListObject @@ -154,7 +117,7 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe O if currentPipe != nil { argsPtr.Append(currentPipe) } - for _, arg := range ast.InvokeArgs { + for _, arg := range ast.Args { if ident := arg.Arg.Ident; len(arg.DotSuffix) == 0 && ident != nil && ident.String()[0] == '-' { // Arg switch if kwargs == nil { @@ -213,14 +176,6 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (Object, return res, nil } -func (e evaluator) assignDot(ctx context.Context, ec *evalCtx, n astDot, toVal Object) (Object, error) { - if len(n.DotSuffix) == 0 { - return e.assignArg(ctx, ec, n.Arg, toVal) - } - - return nil, errors.New("TODO") -} - func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Object, error) { switch { case n.Literal != nil: @@ -256,40 +211,6 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec return nil, errors.New("unhandled arg type") } -func (e evaluator) assignArg(ctx context.Context, ec *evalCtx, n astCmdArg, toVal Object) (Object, error) { - switch { - case n.Literal != nil: - // We may use this for variable setting? - return nil, errors.New("cannot assign to a literal") - case n.Var != nil: - ec.setOrDefineVar(*n.Var, toVal) - return toVal, nil - case n.PseudoVar != nil: - pvar, ok := ec.getPseudoVar(*n.PseudoVar) - if ok { - if err := pvar.set(ctx, *n.PseudoVar, toVal); err != nil { - return nil, err - } - return toVal, nil - } - - if pvar := e.inst.missingPseudoVarHandler; pvar != nil { - if err := pvar.set(ctx, *n.PseudoVar, toVal); err != nil { - return nil, err - } - return toVal, nil - } - return nil, errors.New("unknown pseudo-variable: " + *n.Var) - case n.MaybeSub != nil: - return nil, errors.New("cannot assign to a subexpression") - case n.ListOrHash != nil: - return nil, errors.New("cannot assign to a list or hash") - case n.Block != nil: - return nil, errors.New("cannot assign to a block") - } - return nil, errors.New("unhandled arg type") -} - func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (Object, error) { if loh.EmptyList { return &ListObject{}, nil diff --git a/ucl/inst.go b/ucl/inst.go index 585bea3..74dbdcf 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -57,6 +57,8 @@ func New(opts ...InstOption) *Inst { rootEC.root = rootEC 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)) @@ -94,14 +96,11 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("continue", invokableFunc(continueBuiltin)) rootEC.addCmd("return", invokableFunc(returnBuiltin)) - rootEC.addCmd("error", invokableFunc(errorBuiltin)) - rootEC.addCmd("assert", invokableFunc(assertBuiltin)) - rootEC.addMacro("if", macroFunc(ifBuiltin)) + rootEC.addMacro("foreach", macroFunc(foreachBuiltin)) rootEC.addMacro("for", macroFunc(foreachBuiltin)) rootEC.addMacro("while", macroFunc(whileBuiltin)) rootEC.addMacro("proc", macroFunc(procBuiltin)) - rootEC.addMacro("try", macroFunc(tryBuiltin)) inst := &Inst{ out: os.Stdout, @@ -152,7 +151,7 @@ func (inst *Inst) Eval(ctx context.Context, expr string) (any, error) { goRes, ok := toGoValue(res) if !ok { - return res, nil + return nil, ErrNotConvertable } return goRes, nil @@ -208,7 +207,7 @@ func (n nativePseudoVarHandler) set(ctx context.Context, name string, v Object) gv, ok := toGoValue(v) if !ok { - return mpvh.Set(ctx, v) + return errors.New("cannot set non-matching type") } return mpvh.Set(ctx, gv) @@ -234,7 +233,7 @@ func (n nativeMissingPseudoVarHandler) set(ctx context.Context, name string, v O gv, ok := toGoValue(v) if !ok { - return mpvh.Set(ctx, name, v) + return errors.New("cannot set non-matching type") } return mpvh.Set(ctx, name, gv) diff --git a/ucl/inst_test.go b/ucl/inst_test.go index 479c845..069927e 100644 --- a/ucl/inst_test.go +++ b/ucl/inst_test.go @@ -14,7 +14,6 @@ func TestInst_Eval(t *testing.T) { desc string expr string want any - wantObj bool wantErr error }{ {desc: "simple string", expr: `firstarg "hello"`, want: "hello"}, @@ -23,15 +22,15 @@ func TestInst_Eval(t *testing.T) { {desc: "simple ident", expr: `firstarg a-test`, want: "a-test"}, // String interpolation - {desc: "interpolate string 1", expr: `$what = "world" ; firstarg "hello $what"`, want: "hello world"}, - {desc: "interpolate string 2", expr: `$what = "world" ; $when = "now" ; firstarg "$when, hello $what"`, want: "now, hello world"}, - {desc: "interpolate string 3", expr: `$what = "world" ; $when = "now" ; firstarg "${when}, hello ${what}"`, want: "now, hello world"}, - {desc: "interpolate string 4", expr: `$crazy = [far: "unknown"] ; firstarg "hello ${crazy.far}"`, want: "hello unknown"}, - {desc: "interpolate string 5", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(1)}"`, want: "hello thither"}, - {desc: "interpolate string 6", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 1 1)}"`, want: "hello yonder"}, - {desc: "interpolate string 7", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 2 | sub (sub 2 1) | sub 1)}"`, want: "hello hither"}, - {desc: "interpolate string 8", expr: `$words = ["old": ["hither" "thither" "yonder"] "new": ["near" "far"]] ; firstarg "hello ${words.old.(2)}"`, want: "hello yonder"}, - {desc: "interpolate string 9", expr: `$what = "world" ; firstarg "hello $($what)"`, want: "hello world"}, + {desc: "interpolate string 1", expr: `set what "world" ; firstarg "hello $what"`, want: "hello world"}, + {desc: "interpolate string 2", expr: `set what "world" ; set when "now" ; firstarg "$when, hello $what"`, want: "now, hello world"}, + {desc: "interpolate string 3", expr: `set what "world" ; set when "now" ; firstarg "${when}, hello ${what}"`, want: "now, hello world"}, + {desc: "interpolate string 4", expr: `set crazy [far: "unknown"] ; firstarg "hello ${crazy.far}"`, want: "hello unknown"}, + {desc: "interpolate string 5", expr: `set oldWords ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(1)}"`, want: "hello thither"}, + {desc: "interpolate string 6", expr: `set oldWords ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 1 1)}"`, want: "hello yonder"}, + {desc: "interpolate string 7", expr: `set oldWords ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 2 | sub (sub 2 1) | sub 1)}"`, want: "hello hither"}, + {desc: "interpolate string 8", expr: `set words ["old": ["hither" "thither" "yonder"] "new": ["near" "far"]] ; firstarg "hello ${words.old.(2)}"`, want: "hello yonder"}, + {desc: "interpolate string 9", expr: `set what "world" ; firstarg "hello $($what)"`, want: "hello world"}, {desc: "interpolate string 10", expr: `firstarg "hello $([1 2 3] | len)"`, want: "hello 3"}, {desc: "interpolate string 11", expr: `firstarg "hello $(add (add 1 2) 3)"`, want: "hello 6"}, {desc: "interpolate string 12", expr: `firstarg ("$(add 2 (add 1 1)) + $([1 2 3].(1) | cat ("$("")")) = $(("$(add 2 (4))"))")`, want: "4 + 2 = 6"}, @@ -58,53 +57,53 @@ func TestInst_Eval(t *testing.T) { // Multi-statements {desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"}, {desc: "multi 2", expr: `list "hello" | toUpper ; firstarg "world"`, want: "world"}, - {desc: "multi 3", expr: `$new = "this is new" ; firstarg $new`, want: "this is new"}, + {desc: "multi 3", expr: `set new "this is new" ; firstarg $new`, want: "this is new"}, // Lists {desc: "list 1", expr: `firstarg ["1" "2" "3"]`, want: []any{"1", "2", "3"}}, - {desc: "list 2", expr: `$one = "one" ; firstarg [$one (list "two" | map { |x| toUpper $x } | head) "three"]`, want: []any{"one", "TWO", "three"}}, + {desc: "list 2", expr: `set one "one" ; firstarg [$one (list "two" | map { |x| toUpper $x } | head) "three"]`, want: []any{"one", "TWO", "three"}}, {desc: "list 3", expr: `firstarg []`, want: []any{}}, - {desc: "list 4", expr: `$x = ["a" "b" "c"] ; firstarg [$x.(2) $x.(1) $x.(0)]`, want: []any{"c", "b", "a"}}, + {desc: "list 4", expr: `set x ["a" "b" "c"] ; firstarg [$x.(2) $x.(1) $x.(0)]`, want: []any{"c", "b", "a"}}, // Maps {desc: "map 1", expr: `firstarg [one:"1" two:"2" three:"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}}, {desc: "map 2", expr: `firstarg ["one":"1" "two":"2" "three":"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}}, {desc: "map 3", expr: ` - $one = "one" ; $n1 = "1" + set one "one" ; set n1 "1" firstarg [ $one:$n1 (list "two" | map { |x| toUpper $x } | head):(list "2" | map { |x| toUpper $x } | head) three:"3" ]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}}, {desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}}, - {desc: "map 5", expr: `$x = ["a" "b" "c"] ; firstarg ["one":$x.(2) "two":$x.(1) "three":$x.(0)]`, want: map[string]any{"one": "c", "two": "b", "three": "a"}}, - {desc: "map 6", expr: `$x = [a:"A" b:"B" c:"C"] ; firstarg ["one":$x.c "two":$x.b "three":$x.a]`, want: map[string]any{"one": "C", "two": "B", "three": "A"}}, + {desc: "map 5", expr: `set x ["a" "b" "c"] ; firstarg ["one":$x.(2) "two":$x.(1) "three":$x.(0)]`, want: map[string]any{"one": "c", "two": "b", "three": "a"}}, + {desc: "map 6", expr: `set x [a:"A" b:"B" c:"C"] ; firstarg ["one":$x.c "two":$x.b "three":$x.a]`, want: map[string]any{"one": "C", "two": "B", "three": "A"}}, // Dots - {desc: "dot expr 1", expr: `$x = [1 2 3] ; $x.(0)`, want: 1}, - {desc: "dot expr 2", expr: `$x = [1 2 3] ; $x.(1)`, want: 2}, - {desc: "dot expr 3", expr: `$x = [1 2 3] ; $x.(2)`, want: 3}, - {desc: "dot expr 4", expr: `$x = [1 2 3] ; $x.(3)`, want: nil}, - {desc: "dot expr 5", expr: `$x = [1 2 3] ; $x.(add 1 1)`, want: 3}, - {desc: "dot expr 6", expr: `$x = [1 2 3] ; $x.(-1)`, want: 3}, - {desc: "dot expr 7", expr: `$x = [1 2 3] ; $x.(-2)`, want: 2}, - {desc: "dot expr 8", expr: `$x = [1 2 3] ; $x.(-3)`, want: 1}, - {desc: "dot expr 9", expr: `$x = [1 2 3] ; $x.(-4)`, want: nil}, + {desc: "dot expr 1", expr: `set x [1 2 3] ; $x.(0)`, want: 1}, + {desc: "dot expr 2", expr: `set x [1 2 3] ; $x.(1)`, want: 2}, + {desc: "dot expr 3", expr: `set x [1 2 3] ; $x.(2)`, want: 3}, + {desc: "dot expr 4", expr: `set x [1 2 3] ; $x.(3)`, want: nil}, + {desc: "dot expr 5", expr: `set x [1 2 3] ; $x.(add 1 1)`, want: 3}, + {desc: "dot expr 6", expr: `set x [1 2 3] ; $x.(-1)`, want: 3}, + {desc: "dot expr 7", expr: `set x [1 2 3] ; $x.(-2)`, want: 2}, + {desc: "dot expr 8", expr: `set x [1 2 3] ; $x.(-3)`, want: 1}, + {desc: "dot expr 9", expr: `set x [1 2 3] ; $x.(-4)`, want: nil}, - {desc: "dot idents 1", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.alpha`, want: "hello"}, - {desc: "dot idents 2", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.bravo`, want: "world"}, - {desc: "dot idents 3", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.charlie`, want: nil}, - {desc: "dot idents 4", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("alpha")`, want: "hello"}, - {desc: "dot idents 5", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("bravo")`, want: "world"}, - {desc: "dot idents 6", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("charlie")`, want: nil}, - {desc: "dot idents 7", expr: `$x = [MORE:"stuff"] ; $x.("more" | toUpper)`, want: "stuff"}, - {desc: "dot idents 8", expr: `$x = [MORE:"stuff"] ; $x.(toUpper ("more"))`, want: "stuff"}, - {desc: "dot idents 9", expr: `$x = [MORE:"stuff"] ; x.y`, want: nil}, + {desc: "dot idents 1", expr: `set x [alpha:"hello" bravo:"world"] ; $x.alpha`, want: "hello"}, + {desc: "dot idents 2", expr: `set x [alpha:"hello" bravo:"world"] ; $x.bravo`, want: "world"}, + {desc: "dot idents 3", expr: `set x [alpha:"hello" bravo:"world"] ; $x.charlie`, want: nil}, + {desc: "dot idents 4", expr: `set x [alpha:"hello" bravo:"world"] ; $x.("alpha")`, want: "hello"}, + {desc: "dot idents 5", expr: `set x [alpha:"hello" bravo:"world"] ; $x.("bravo")`, want: "world"}, + {desc: "dot idents 6", expr: `set x [alpha:"hello" bravo:"world"] ; $x.("charlie")`, want: nil}, + {desc: "dot idents 7", expr: `set x [MORE:"stuff"] ; $x.("more" | toUpper)`, want: "stuff"}, + {desc: "dot idents 8", expr: `set x [MORE:"stuff"] ; $x.(toUpper ("more"))`, want: "stuff"}, + {desc: "dot idents 9", expr: `set x [MORE:"stuff"] ; x.y`, want: nil}, - {desc: "parse comments 1", expr: parseComments1, wantObj: true, wantErr: nil}, - {desc: "parse comments 2", expr: parseComments2, wantObj: true, wantErr: nil}, - {desc: "parse comments 3", expr: parseComments3, wantObj: true, wantErr: nil}, - {desc: "parse comments 4", expr: parseComments4, wantObj: true, wantErr: nil}, + {desc: "parse comments 1", expr: parseComments1, wantErr: ucl.ErrNotConvertable}, + {desc: "parse comments 2", expr: parseComments2, wantErr: ucl.ErrNotConvertable}, + {desc: "parse comments 3", expr: parseComments3, wantErr: ucl.ErrNotConvertable}, + {desc: "parse comments 4", expr: parseComments4, wantErr: ucl.ErrNotConvertable}, } for _, tt := range tests { @@ -117,10 +116,6 @@ func TestInst_Eval(t *testing.T) { if tt.wantErr != nil { assert.ErrorIs(t, err, tt.wantErr) - } else if tt.wantObj { - assert.NoError(t, err) - _, isObj := res.(ucl.Object) - assert.True(t, isObj) } else { assert.NoError(t, err) assert.Equal(t, tt.want, res) @@ -141,9 +136,9 @@ func TestInst_SetPseudoVar(t *testing.T) { {desc: "read var 2", expr: `@bar`, wantRes: "this is bar"}, {desc: "read var 3", expr: `@fla`, wantRes: "missing value of fla"}, - {desc: "write var 1", expr: `@foo = "hello" ; @foo`, wantRes: "hello"}, - {desc: "write var 2", expr: `@bar = "world" ; @bar`, wantRes: "world", wantBarVar: "world"}, - {desc: "write var 3", expr: `@blong = "hello"`, wantErr: true}, + {desc: "write var 1", expr: `set "@foo" "hello" ; @foo`, wantRes: "hello"}, + {desc: "write var 2", expr: `set "@bar" "world" ; @bar`, wantRes: "world", wantBarVar: "world"}, + {desc: "write var 3", expr: `set "@blong" "hello"`, wantErr: true}, } for _, tt := range tests { diff --git a/ucl/objs.go b/ucl/objs.go index f5894f1..e2bfda9 100644 --- a/ucl/objs.go +++ b/ucl/objs.go @@ -80,24 +80,6 @@ func (s *ListObject) Index(i int) Object { return (*s)[i] } -type StringListObject []string - -func (ss StringListObject) String() string { - return fmt.Sprintf("[%v]", strings.Join(ss, " ")) -} - -func (ss StringListObject) Truthy() bool { - return len(ss) > 0 -} - -func (ss StringListObject) Len() int { - return len(ss) -} - -func (ss StringListObject) Index(i int) Object { - return StringObject(ss[i]) -} - type iteratorObject struct { Iterable } @@ -172,26 +154,26 @@ func (i IntObject) Truthy() bool { return i != 0 } -type BoolObject bool +type boolObject bool -func (b BoolObject) String() string { +func (b boolObject) String() string { if b { return "true" } return "false" } -func (b BoolObject) Truthy() bool { +func (b boolObject) Truthy() bool { return bool(b) } -type TimeObject time.Time +type timeObject time.Time -func (t TimeObject) String() string { +func (t timeObject) String() string { return time.Time(t).Format(time.RFC3339) } -func (t TimeObject) Truthy() bool { +func (t timeObject) Truthy() bool { return !time.Time(t).IsZero() } @@ -203,11 +185,9 @@ func toGoValue(obj Object) (interface{}, bool) { return string(v), true case IntObject: return int(v), true - case StringListObject: - return []string(v), true - case BoolObject: + case boolObject: return bool(v), true - case TimeObject: + case timeObject: return time.Time(v), true case *ListObject: xs := make([]interface{}, 0, len(*v)) @@ -257,9 +237,9 @@ func fromGoValue(v any) (Object, error) { case int: return IntObject(t), nil case bool: - return BoolObject(t), nil + return boolObject(t), nil case time.Time: - return TimeObject(t), nil + return timeObject(t), nil } return fromGoReflectValue(reflect.ValueOf(v)) @@ -299,7 +279,7 @@ type macroArgs struct { } func (ma macroArgs) nargs() int { - return len(ma.ast.InvokeArgs[ma.argShift:]) + return len(ma.ast.Args[ma.argShift:]) } func (ma *macroArgs) shift(n int) { @@ -307,15 +287,15 @@ func (ma *macroArgs) shift(n int) { } func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bool { - if n >= len(ma.ast.InvokeArgs[ma.argShift:]) { + if n >= len(ma.ast.Args[ma.argShift:]) { return false } - if len(ma.ast.InvokeArgs[ma.argShift+n].DotSuffix) != 0 { + if len(ma.ast.Args[ma.argShift+n].DotSuffix) != 0 { return false } - lit := ma.ast.InvokeArgs[ma.argShift+n].Arg.Ident + lit := ma.ast.Args[ma.argShift+n].Arg.Ident if lit == nil { return false } @@ -324,15 +304,15 @@ func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bo } func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) { - if ma.argShift >= len(ma.ast.InvokeArgs) { + if ma.argShift >= len(ma.ast.Args) { return "", false } - if len(ma.ast.InvokeArgs[ma.argShift].DotSuffix) != 0 { + if len(ma.ast.Args[ma.argShift].DotSuffix) != 0 { return "", false } - lit := ma.ast.InvokeArgs[ma.argShift].Arg.Ident + lit := ma.ast.Args[ma.argShift].Arg.Ident if lit != nil { ma.argShift += 1 return lit.String(), true @@ -341,11 +321,11 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) { } func (ma macroArgs) evalArg(ctx context.Context, n int) (Object, error) { - if n >= len(ma.ast.InvokeArgs[ma.argShift:]) { + if n >= len(ma.ast.Args[ma.argShift:]) { return nil, errors.New("not enough arguments") // FIX } - return ma.eval.evalDot(ctx, ma.ec, ma.ast.InvokeArgs[ma.argShift+n]) + 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) { @@ -387,7 +367,7 @@ func (ma macroArgs) evalBlock(ctx context.Context, n int, args []Object, pushSco type errObject struct{ err error } func (eo errObject) String() string { - return eo.err.Error() + return "error:" + eo.err.Error() } func (eo errObject) Truthy() bool { @@ -643,14 +623,6 @@ func (p OpaqueObject) Truthy() bool { return p.v != nil } -type ErrRuntime struct { - errStr string -} - -func (e ErrRuntime) Error() string { - return e.errStr -} - type errBreak struct { isCont bool ret Object @@ -672,11 +644,3 @@ func (e errReturn) Error() string { } var ErrHalt = errors.New("halt") - -func isBreakErr(err error) bool { - if err == nil { - return false - } - - return errors.As(err, &errBreak{}) || errors.As(err, &errReturn{}) || errors.Is(err, ErrHalt) -} diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index 096e446..e138a1c 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -131,12 +131,12 @@ func TestBuiltins_Echo(t *testing.T) { echo "world" # command after this ; `, want: "Hello\nworld\n"}, - {desc: "interpolated string 1", expr: `$what = "world" ; echo "Hello, $what"`, want: "Hello, world\n"}, - {desc: "interpolated string 2", expr: `$what = "world" ; echo "Hello, \$what"`, want: "Hello, $what\n"}, + {desc: "interpolated string 1", expr: `set what "world" ; echo "Hello, $what"`, want: "Hello, world\n"}, + {desc: "interpolated string 2", expr: `set what "world" ; echo "Hello, \$what"`, want: "Hello, $what\n"}, {desc: "interpolated string 3", expr: `echo "separate\nlines\n\tand tabs"`, want: "separate\nlines\n\tand tabs\n"}, - {desc: "interpolated string 4", expr: `$what = "Hello" ; $where = "world" ; echo "$what, $where"`, want: "Hello, world\n"}, + {desc: "interpolated string 4", expr: `set what "Hello" ; set where "world" ; echo "$what, $where"`, want: "Hello, world\n"}, {desc: "interpolated string 5", expr: ` - for [123 "foo" true ()] { |x| + foreach [123 "foo" true ()] { |x| echo "[[$x]]" } `, want: "[[123]]\n[[foo]]\n[[true]]\n[[]]\n"}, @@ -167,19 +167,19 @@ func TestBuiltins_If(t *testing.T) { want string }{ {desc: "single then", expr: ` - $x = "Hello" + set x "Hello" if $x { echo "true" }`, want: "true\n(nil)\n"}, {desc: "single then and else", expr: ` - $x = "Hello" + set x "Hello" if $x { echo "true" } else { echo "false" }`, want: "true\n(nil)\n"}, {desc: "single then, elif and else", expr: ` - $x = "Hello" + set x "Hello" if $y { echo "y is true" } elif $x { @@ -188,14 +188,14 @@ func TestBuiltins_If(t *testing.T) { echo "nothings x" }`, want: "x is true\n(nil)\n"}, {desc: "single then and elif, no else", expr: ` - $x = "Hello" + set x "Hello" if $y { echo "y is true" } elif $x { echo "x is true" }`, want: "x is true\n(nil)\n"}, {desc: "single then, two elif, and else", expr: ` - $x = "Hello" + set x "Hello" if $z { echo "z is true" } elif $y { @@ -213,15 +213,15 @@ func TestBuiltins_If(t *testing.T) { } else { echo "none is true" }`, want: "none is true\n(nil)\n"}, - {desc: "compressed then", expr: `$x = "Hello" ; if $x { echo "true" }`, want: "true\n(nil)\n"}, + {desc: "compressed then", expr: `set x "Hello" ; if $x { echo "true" }`, want: "true\n(nil)\n"}, {desc: "compressed else", expr: `if $x { echo "true" } else { echo "false" }`, want: "false\n(nil)\n"}, {desc: "compressed if", expr: `if $x { echo "x" } elif $y { echo "y" } else { echo "false" }`, want: "false\n(nil)\n"}, - {desc: "if of itr 1", expr: `$i = itr ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, - {desc: "if of itr 2", expr: `$i = itr ; for (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, - {desc: "if of itr 3", expr: `$i = itr ; for (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"}, - {desc: "if of itr 4", expr: `$i = (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, - {desc: "if of itr 5", expr: `$i = (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"}, - {desc: "if of itr 6", expr: `$i = (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, + {desc: "if of itr 1", expr: `set i (itr) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, + {desc: "if of itr 2", expr: `set i (itr) ; foreach (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, + {desc: "if of itr 3", expr: `set i (itr) ; foreach (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"}, + {desc: "if of itr 4", expr: `set i (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, + {desc: "if of itr 5", expr: `set i (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"}, + {desc: "if of itr 6", expr: `set i (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, } for _, tt := range tests { @@ -245,19 +245,19 @@ func TestBuiltins_ForEach(t *testing.T) { want string }{ {desc: "iterate over list 1", expr: ` - for ["1" "2" "3"] { |v| + foreach ["1" "2" "3"] { |v| echo $v }`, want: "1\n2\n3\n(nil)\n"}, {desc: "iterate over list 2", - expr: `for ["1" "2" "3"] echo`, + expr: `foreach ["1" "2" "3"] echo`, want: "1\n2\n3\n(nil)\n"}, // TODO: hash is not sorted, so need to find a way to sort it {desc: "iterate over map 1", expr: ` - for [a:"1"] { |k v| echo $k "=" $v }`, want: "a=1\n(nil)\n"}, + foreach [a:"1"] { |k v| echo $k "=" $v }`, want: "a=1\n(nil)\n"}, {desc: "iterate over map 2", expr: ` - for [a:"1"] echo`, want: "a1\n(nil)\n"}, - {desc: "iterate via pipe", expr: `["2" "4" "6"] | for { |x| echo $x }`, want: "2\n4\n6\n(nil)\n"}, - {desc: "iterate from iterator 1", expr: `itr | for { |x| echo $x }`, want: "1\n2\n3\n(nil)\n"}, + foreach [a:"1"] echo`, want: "a1\n(nil)\n"}, + {desc: "iterate via pipe", expr: `["2" "4" "6"] | foreach { |x| echo $x }`, want: "2\n4\n6\n(nil)\n"}, + {desc: "iterate from iterator 1", expr: `itr | foreach { |x| echo $x }`, want: "1\n2\n3\n(nil)\n"}, } for _, tt := range tests { @@ -281,53 +281,53 @@ func TestBuiltins_While(t *testing.T) { want string }{ {desc: "iterate while true 1", expr: ` - $x = 0 + set x 0 while (lt $x 5) { echo $x - $x = (add $x 1) + set x (add $x 1) } echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"}, {desc: "iterate while true 2", expr: ` - $x = 20 + set x 20 while (lt $x 5) { echo $x - $x = (add $x 1) + set x (add $x 1) } echo "done"`, want: "done\n(nil)\n"}, {desc: "iterate while true with pipeline", expr: ` - $x = 0 + set x 0 while (lt $x 5) { echo $x - $x = (add $x 1) + set x (add $x 1) if (ge $x 3) { break "Ahh" } } | echo " was the break" echo "done"`, want: "0\n1\n2\nAhh was the break\ndone\n(nil)\n"}, {desc: "iterate for ever with break 1", expr: ` - $x = 0 + set x 0 while { echo $x - $x = (add $x 1) + set x (add $x 1) if (ge $x 5) { break } } echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"}, {desc: "iterate for ever with break 2", expr: ` - $x = 0 + set x 0 echo (while { echo $x - $x = add $x 1 + set x (add $x 1) if (ge $x 5) { break $x } }) `, want: "0\n1\n2\n3\n4\n5\n(nil)\n"}, {desc: "iterate for ever with continue", expr: ` - $x = 0 + set x 0 while { - $x = (add $x 1) + set x (add $x 1) if (or (eq $x 2) (eq $x 4)) { echo "quack" continue @@ -361,29 +361,29 @@ func TestBuiltins_Break(t *testing.T) { want string }{ {desc: "break unconditionally returning nothing", expr: ` - for ["1" "2" "3"] { |v| + foreach ["1" "2" "3"] { |v| break echo $v }`, want: "(nil)\n"}, {desc: "break conditionally returning nothing", expr: ` - for ["1" "2" "3"] { |v| + foreach ["1" "2" "3"] { |v| echo $v if (eq $v "2") { break } }`, want: "1\n2\n(nil)\n"}, {desc: "break inner loop only returning nothing", expr: ` - for ["a" "b"] { |u| - for ["1" "2" "3"] { |v| + foreach ["a" "b"] { |u| + foreach ["1" "2" "3"] { |v| echo $u $v if (eq $v "2") { break } } }`, want: "a1\na2\nb1\nb2\n(nil)\n"}, {desc: "break returning value 1", expr: ` - echo (for ["1" "2" "3"] { |v| + echo (foreach ["1" "2" "3"] { |v| echo $v if (eq $v "2") { break "hello" } })`, want: "1\n2\nhello\n(nil)\n"}, {desc: "break returning value 2", expr: ` - echo (for (itr) { |v| + echo (foreach (itr) { |v| echo $v if (eq $v 2) { break "hello" } })`, want: "1\n2\nhello\n(nil)\n"}, @@ -410,20 +410,20 @@ func TestBuiltins_Continue(t *testing.T) { want string }{ {desc: "continue unconditionally", expr: ` - for ["1" "2" "3"] { |v| + foreach ["1" "2" "3"] { |v| echo $v "s" continue echo $v "e" }`, want: "1s\n2s\n3s\n(nil)\n"}, {desc: "conditionally conditionally", expr: ` - for ["1" "2" "3"] { |v| + foreach ["1" "2" "3"] { |v| echo $v "s" if (eq $v "2") { continue } echo $v "e" }`, want: "1s\n1e\n2s\n3s\n3e\n(nil)\n"}, {desc: "continue inner loop only", expr: ` - for ["a" "b"] { |u| - for ["1" "2" "3"] { |v| + foreach ["a" "b"] { |u| + foreach ["1" "2" "3"] { |v| if (eq $v "2") { continue } echo $u $v } @@ -487,10 +487,10 @@ func TestBuiltins_Procs(t *testing.T) { } } - $helloGreater = makeGreeter "Hello" + set helloGreater (makeGreeter "Hello") $helloGreater "world" - $goodbye = makeGreeter "Goodbye cruel" + set goodbye (makeGreeter "Goodbye cruel") $goodbye "world" call (makeGreeter "Quick") ["call me"] @@ -498,13 +498,13 @@ func TestBuiltins_Procs(t *testing.T) { `, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"}, {desc: "modifying closed over variables", expr: ` proc makeSetter { - $bla = "X" + set bla "X" proc appendToBla { |x| - $bla = cat $bla $x + set bla (cat $bla $x) } } - $er = makeSetter + set er (makeSetter) echo (call $er ["xxx"]) echo (call $er ["yyy"]) `, want: "Xxxx\nXxxxyyy\n(nil)\n"}, @@ -606,7 +606,7 @@ func TestBuiltins_Return(t *testing.T) { echo "world" } proc greet { - $what = (greetWhat) + set what (greetWhat) echo "Hello, " $what } @@ -614,7 +614,7 @@ func TestBuiltins_Return(t *testing.T) { `, want: "Greet the\nHello, moon\n(nil)\n"}, {desc: "return in loop", expr: ` proc countdown { |nums| - for $nums { |n| + foreach $nums { |n| echo $n if (eq $n 3) { return "abort" @@ -639,7 +639,7 @@ func TestBuiltins_Return(t *testing.T) { } proc test-thing { - for [1 2 3] { |x| + foreach [1 2 3] { |x| do-thing { echo $x } @@ -654,7 +654,7 @@ func TestBuiltins_Return(t *testing.T) { } proc test-thing { - for [1 2 3] { |x| + foreach [1 2 3] { |x| do-thing (proc { echo $x }) @@ -669,8 +669,8 @@ func TestBuiltins_Return(t *testing.T) { } proc test-thing { - for [1 2 3] { |x| - $myClosure = proc { echo $x } + foreach [1 2 3] { |x| + set myClosure (proc { echo $x }) do-thing $myClosure } } @@ -688,7 +688,7 @@ func TestBuiltins_Return(t *testing.T) { } } - for (test-thing) { |y| call $y } + foreach (test-thing) { |y| call $y } `, want: "1\n2\n3\n(nil)\n"}, {desc: "check closure 5", expr: ` proc do-thing { |p| @@ -697,13 +697,13 @@ func TestBuiltins_Return(t *testing.T) { proc test-thing { [1 2 3] | map { |x| - $myProc = proc { echo $x } + set myProc (proc { echo $x }) proc { do-thing $myProc } } } - $hello = "xx" - for (test-thing) { |y| call $y ; echo $hello } + set hello "xx" + foreach (test-thing) { |y| call $y ; echo $hello } `, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"}, {desc: "check closure 7", expr: ` proc do-thing { |p| @@ -711,16 +711,16 @@ func TestBuiltins_Return(t *testing.T) { } proc test-thing { - $f = 0 + set f 0 [1 2 3] | map { |x| - $myProc = proc { echo $f } - $f = (add $f 1) + set myProc (proc { echo $f }) + set f (add $f 1) proc { do-thing $myProc } } } - $hello = "xx" - for (test-thing) { |y| call $y ; echo $hello } + set hello "xx" + foreach (test-thing) { |y| call $y ; echo $hello } `, want: "3\nxx\n3\nxx\n3\nxx\n(nil)\n"}, {desc: "check closure 7", expr: ` proc do-thing { |p| @@ -728,17 +728,17 @@ func TestBuiltins_Return(t *testing.T) { } proc test-thing { - $f = 1 + set f 1 [1 2 3] | map { |x| - $g = $f - $myProc = (proc { echo $g }) - $f = (add $f 1) + set g $f + set myProc (proc { echo $g }) + set f (add $f 1) proc { do-thing $myProc } } } - $hello = "xx" - for (test-thing) { |y| call $y ; echo $hello } + set hello "xx" + foreach (test-thing) { |y| call $y ; echo $hello } `, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"}, } @@ -756,239 +756,6 @@ func TestBuiltins_Return(t *testing.T) { } } -func TestBuiltins_Try(t *testing.T) { - tests := []struct { - desc string - expr string - want string - wantErr string - }{ - {desc: "try 1", expr: ` - try { - echo "Hello" - error "bang" - echo "World" - } catch { - echo "Caught" - } - `, want: "Hello\nCaught\n(nil)\n"}, - {desc: "try 2", expr: ` - try { - echo "Hello" - error "bang" - echo "World" - } finally { - echo "Always" - } - `, want: "Hello\nAlways\n", wantErr: "bang"}, - {desc: "try 3", expr: ` - try { - echo "Hello" - error "bang" - echo "World" - } catch { |e| - echo "Error was: ${e}" - } finally { - echo "Always" - } - `, want: "Hello\nError was: bang\nAlways\n(nil)\n"}, - {desc: "try 4", expr: ` - try { - echo "Hello" - echo "World" - } catch { |e| - echo "Should not call me" - } finally { - echo "Always" - } - `, want: "Hello\nWorld\nAlways\n(nil)\n"}, - {desc: "try 5", expr: ` - try { - echo "Hello" - - try { - echo "Nested" - error "bang" - echo "World" - } catch { |f| - echo "Catch me: $f" - } finally { - echo "Always 2" - } - } catch { |e| - echo "Should not call me" - } finally { - echo "Always" - } - `, want: "Hello\nNested\nCatch me: bang\nAlways 2\nAlways\n(nil)\n"}, - {desc: "try 6", expr: ` - try { - echo "Hello" - - try { - echo "Nested" - error "bang" - echo "World" - } finally { - echo "Always 2" - } - } catch { |e| - echo "Catch me: $e" - } finally { - echo "Always" - } - `, want: "Hello\nNested\nAlways 2\nCatch me: bang\nAlways\n(nil)\n"}, - {desc: "try 7", expr: ` - try { - echo "Hello" - error "bang" - } catch { |e| - echo "Catch me: $e" - error $e - } finally { - echo "Always" - } - `, want: "Hello\nCatch me: bang\nAlways\n", wantErr: "bang"}, - {desc: "try 8", expr: ` - try { - echo "Hello" - error "bang" - } catch { |e| - echo "Catch me: $e" - } catch { |e| - echo "Catch not me: $e" - } finally { - echo "Always" - } - `, want: "Hello\nCatch me: bang\nAlways\n(nil)\n"}, - {desc: "try 9", expr: ` - try { - echo "Hello" - error "bang" - } catch { |e| - echo "Catch me: $e" - error "boom" - } catch { |e| - echo "Catch me too: $e" - } finally { - echo "Always" - } - `, want: "Hello\nCatch me: bang\nCatch me too: boom\nAlways\n(nil)\n"}, - {desc: "try 10", expr: ` - try { - echo "Hello" - error "bang" - } catch { |e| - echo "Catch me: $e" - error "boom" - } catch { |e| - echo "Catch me too: $e" - error "mint" - } finally { - echo "Always" - } - `, want: "Hello\nCatch me: bang\nCatch me too: boom\nAlways\n", wantErr: "mint"}, - {desc: "try 11", expr: ` - try { - echo "Hello" - error "bang" - } catch { |e| - echo "Catch me: $e" - } finally { - echo "Always" - error "boom" - } - `, want: "Hello\nCatch me: bang\nAlways\n", wantErr: "boom"}, - {desc: "try 12", expr: ` - $a = try { "e" } catch { "f" } - echo $a - `, want: "e\n(nil)\n"}, - {desc: "try 13", expr: ` - $a = try { error "bang" } catch { "f" } - echo $a - `, want: "f\n(nil)\n"}, - {desc: "try 14", expr: ` - for [1 2 3] { |x| - try { - echo $x - continue - echo "No" - } catch { |e| - echo "Catch me: $x" - } finally { - echo "Never" - } - } - `, want: "1\n2\n3\n(nil)\n"}, - {desc: "try 15", expr: ` - for [1 2 3] { |x| - try { - echo $x - break - echo "No" - } catch { |e| - echo "Catch me: $x" - } finally { - echo "Never" - } - } - `, want: "1\n(nil)\n"}, - {desc: "try 16", expr: ` - for [1 2 3] { |x| - try { - echo $x - error "bang" - echo "No" - } catch { |e| - echo "Catch me at $x: $e" - } - } - `, want: "1\nCatch me at 1: bang\n2\nCatch me at 2: bang\n3\nCatch me at 3: bang\n(nil)\n"}, - {desc: "try 17", expr: ` - for [1 2 3] { |x| - try { - echo $x - error "bang" - echo "No" - } catch { |e| - echo "Catch me: $e" - break - } finally { - echo "Never" - } - } - `, want: "1\nCatch me: bang\n(nil)\n"}, - {desc: "try 18", expr: ` - for [1 2 3] { |x| - try { - echo $x - } finally { - echo "Always $x" - continue - } - } - `, want: "1\nAlways 1\n2\nAlways 2\n3\nAlways 3\n(nil)\n"}, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - ctx := context.Background() - outW := bytes.NewBuffer(nil) - - inst := New(WithOut(outW), WithTestBuiltin()) - err := evalAndDisplay(ctx, inst, tt.expr) - - if tt.wantErr != "" { - assert.Error(t, err) - assert.Equal(t, tt.wantErr, err.Error()) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.want, outW.String()) - }) - } -} - func TestBuiltins_Seq(t *testing.T) { tests := []struct { desc string @@ -1064,12 +831,12 @@ func TestBuiltins_Map(t *testing.T) { map ["a" "b" "c"] (proc { |x| makeUpper $x }) `, want: "A\nB\nC\n"}, {desc: "map list 2", expr: ` - $makeUpper = proc { |x| $x | toUpper } + set makeUpper (proc { |x| $x | toUpper }) map ["a" "b" "c"] $makeUpper `, want: "A\nB\nC\n"}, {desc: "map list with pipe", expr: ` - $makeUpper = proc { |x| $x | toUpper } + set makeUpper (proc { |x| $x | toUpper }) ["a" "b" "c"] | map $makeUpper `, want: "A\nB\nC\n"}, @@ -1077,16 +844,16 @@ func TestBuiltins_Map(t *testing.T) { map ["a" "b" "c"] { |x| toUpper $x } `, want: "A\nB\nC\n"}, {desc: "map list with stream", expr: ` - $makeUpper = proc { |x| toUpper $x } + set makeUpper (proc { |x| toUpper $x }) - $l = ["a" "b" "c"] | map $makeUpper + set l (["a" "b" "c"] | map $makeUpper) echo $l `, want: "[A B C]\n(nil)\n"}, {desc: "map itr stream", expr: ` - $add2 = proc { |x| add $x 2 } + set add2 (proc { |x| add $x 2 }) - $l = itr | map $add2 - for $l { |x| echo $x } + set l (itr | map $add2) + foreach $l { |x| echo $x } `, want: "3\n4\n5\n(nil)\n"}, } @@ -1339,7 +1106,7 @@ func TestBuiltins_Filter(t *testing.T) { }}, {desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}}, - {desc: "filter itr 1", expr: `$s = "" ; itr | filter { |x| ne $x 2 } | for { |x| $s = "$s $x" }; $s`, want: " 1 3"}, + {desc: "filter itr 1", expr: `set s "" ; itr | filter { |x| ne $x 2 } | foreach { |x| set s "$s $x" }; $s`, want: " 1 3"}, } for _, tt := range tests { @@ -1391,10 +1158,10 @@ func TestBuiltins_Head(t *testing.T) { {desc: "head list 1", expr: `head [1 2 3]`, want: 1}, {desc: "head itr 1", expr: `head (itr)`, want: 1}, - {desc: "head itr 2", expr: `$h = (itr) ; head $h`, want: 1}, - {desc: "head itr 3", expr: `$h = (itr) ; head $h ; head $h`, want: 2}, - {desc: "head itr 4", expr: `$h = (itr) ; head $h ; head $h ; head $h`, want: 3}, - {desc: "head itr 5", expr: `$h = (itr) ; head $h ; head $h ; head $h ; head $h`, want: nil}, + {desc: "head itr 2", expr: `set h (itr) ; head $h`, want: 1}, + {desc: "head itr 3", expr: `set h (itr) ; head $h ; head $h`, want: 2}, + {desc: "head itr 4", expr: `set h (itr) ; head $h ; head $h ; head $h`, want: 3}, + {desc: "head itr 5", expr: `set h (itr) ; head $h ; head $h ; head $h ; head $h`, want: nil}, } for _, tt := range tests { @@ -1730,7 +1497,7 @@ func TestBuiltins_Cat(t *testing.T) { {desc: "cat 6", expr: `cat "array = " [1 3 2 4]`, want: "array = [1 3 2 4]"}, {desc: "cat 7", expr: `cat 1 $true 3 [4]`, want: "1true3[4]"}, {desc: "cat 8", expr: `cat`, want: ""}, - {desc: "cat 9", expr: `$x = ["a" "b" "c"] ; cat "array = " [1 $x.(0) $x.(2) $x.(1)]`, want: "array = [1 a c b]"}, + {desc: "cat 9", expr: `set x ["a" "b" "c"] ; cat "array = " [1 $x.(0) $x.(2) $x.(1)]`, want: "array = [1 a c b]"}, } for _, tt := range tests { diff --git a/ucl/userbuiltin.go b/ucl/userbuiltin.go index 7058b0e..e38a2f8 100644 --- a/ucl/userbuiltin.go +++ b/ucl/userbuiltin.go @@ -165,7 +165,7 @@ func (ca CallArgs) bindArg(v interface{}, arg Object) error { return bindProxyObject(v, t.v) } - return bindProxyObject(v, reflect.ValueOf(arg)) + return nil } func canBindArg(v interface{}, arg Object) bool { diff --git a/ucl/userbuiltin_test.go b/ucl/userbuiltin_test.go index 8b1951f..9c631e7 100644 --- a/ucl/userbuiltin_test.go +++ b/ucl/userbuiltin_test.go @@ -124,7 +124,7 @@ func TestInst_SetBuiltin(t *testing.T) { want string }{ {descr: "pass via args", expr: `join (add2 "left" "right")`, want: "left:right"}, - {descr: "pass via vars", expr: `$x = (add2 "blue" "green") ; join $x`, want: "blue:green"}, + {descr: "pass via vars", expr: `set x (add2 "blue" "green") ; join $x`, want: "blue:green"}, } for _, tt := range tests { @@ -164,7 +164,7 @@ func TestInst_SetBuiltin(t *testing.T) { wantOut string }{ {descr: "return as is", expr: `countTo3`, want: []string{"1", "2", "3"}}, - {descr: "iterate over", expr: `for (countTo3) { |x| echo $x }`, wantOut: "1\n2\n3\n"}, + {descr: "iterate over", expr: `foreach (countTo3) { |x| echo $x }`, wantOut: "1\n2\n3\n"}, } for _, tt := range tests {