From 24a484ab778467da79d3b11319a6d4b6a440224b Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Wed, 24 Apr 2024 20:12:39 +1000 Subject: [PATCH] Allowed foreach to pull from the pipe --- cmdlang/ast.go | 4 +++- cmdlang/builtins.go | 30 ++++++++++++++++++++++-------- cmdlang/eval.go | 7 +++++-- cmdlang/inst_test.go | 2 ++ cmdlang/objs.go | 13 +++++++++++++ cmdlang/userbuiltin_test.go | 1 + 6 files changed, 46 insertions(+), 11 deletions(-) diff --git a/cmdlang/ast.go b/cmdlang/ast.go index c3ff3e1..9e989de 100644 --- a/cmdlang/ast.go +++ b/cmdlang/ast.go @@ -9,6 +9,7 @@ import ( type astLiteral struct { Str *string `parser:"@String"` + Int *int `parser:"| @Int"` } type astHashKey struct { @@ -70,6 +71,7 @@ var scanner = lexer.MustStateful(lexer.Rules{ "Root": { {"Whitespace", `[ \t]+`, nil}, {"String", `"(\\"|[^"])*"`, nil}, + {"Int", `[-]?[0-9][0-9]*`, nil}, {"DOLLAR", `\$`, nil}, {"COLON", `\:`, nil}, {"LP", `\(`, nil}, @@ -80,7 +82,7 @@ var scanner = lexer.MustStateful(lexer.Rules{ {"RC", `\}`, nil}, {"NL", `[;\n][; \n\t]*`, nil}, {"PIPE", `\|`, nil}, - {"Ident", `[\w-]+`, nil}, + {"Ident", `[-]*[a-zA-Z_][\w-]*`, nil}, }, }) var parser = participle.MustBuild[astScript](participle.Lexer(scanner), diff --git a/cmdlang/builtins.go b/cmdlang/builtins.go index 261e558..8320edd 100644 --- a/cmdlang/builtins.go +++ b/cmdlang/builtins.go @@ -238,13 +238,27 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) { } func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { - if args.nargs() < 2 { - return nil, errors.New("need at least 2 arguments") - } + var ( + items object + blockIdx int + err error + ) + if !args.hasPipe { + if args.nargs() < 2 { + return nil, errors.New("need at least 2 arguments") + } - items, err := args.evalArg(ctx, 0) - if err != nil { - return nil, err + items, err = args.evalArg(ctx, 0) + if err != nil { + return nil, err + } + blockIdx = 1 + } else { + if args.nargs() < 1 { + return nil, errors.New("need at least 1 argument") + } + items = args.pipeArg + blockIdx = 0 } var last object @@ -254,14 +268,14 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { l := t.Len() for i := 0; i < l; i++ { v := t.Index(i) - last, err = args.evalBlock(ctx, 1, []object{v}, true) // TO INCLUDE: the index + last, err = args.evalBlock(ctx, blockIdx, []object{v}, true) // TO INCLUDE: the index if err != nil { return nil, err } } case hashObject: for k, v := range t { - last, err = args.evalBlock(ctx, 1, []object{strObject(k), v}, true) + last, err = args.evalBlock(ctx, blockIdx, []object{strObject(k), v}, true) if err != nil { return nil, err } diff --git a/cmdlang/eval.go b/cmdlang/eval.go index c8803f1..6784afc 100644 --- a/cmdlang/eval.go +++ b/cmdlang/eval.go @@ -74,7 +74,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object, if cmd := ec.lookupInvokable(name); cmd != nil { return e.evalInvokable(ctx, ec, currentPipe, ast, cmd) } else if macro := ec.lookupMacro(name); macro != nil { - return e.evalMacro(ctx, ec, currentPipe, ast, macro) + return e.evalMacro(ctx, ec, currentPipe != nil, currentPipe, ast, macro) } else { return nil, errors.New("unknown command: " + name) } @@ -132,10 +132,11 @@ 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, 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, + hasPipe: hasPipe, pipeArg: pipeArg, ast: ast, }) @@ -217,6 +218,8 @@ func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) return nil, err } return strObject(uq), nil + case n.Int != nil: + return intObject(*n.Int), nil } return nil, errors.New("unhandled literal type") } diff --git a/cmdlang/inst_test.go b/cmdlang/inst_test.go index e5141c0..ec28d47 100644 --- a/cmdlang/inst_test.go +++ b/cmdlang/inst_test.go @@ -16,6 +16,8 @@ func TestInst_Eval(t *testing.T) { want any }{ {desc: "simple string", expr: `firstarg "hello"`, want: "hello"}, + {desc: "simple int 1", expr: `firstarg 123`, want: 123}, + {desc: "simple int 2", expr: `firstarg -234`, want: -234}, {desc: "simple ident", expr: `firstarg a-test`, want: "a-test"}, // Sub-expressions diff --git a/cmdlang/objs.go b/cmdlang/objs.go index f0cbebc..b439a74 100644 --- a/cmdlang/objs.go +++ b/cmdlang/objs.go @@ -78,6 +78,16 @@ func (s strObject) Truthy() bool { return string(s) != "" } +type intObject int + +func (i intObject) String() string { + return strconv.Itoa(int(i)) +} + +func (i intObject) Truthy() bool { + return i != 0 +} + type boolObject bool func (b boolObject) String() string { @@ -97,6 +107,8 @@ func toGoValue(obj object) (interface{}, bool) { return nil, true case strObject: return string(v), true + case intObject: + return int(v), true case listObject: xs := make([]interface{}, 0, len(v)) for _, va := range v { @@ -145,6 +157,7 @@ func fromGoValue(v any) (object, error) { type macroArgs struct { eval evaluator ec *evalCtx + hasPipe bool pipeArg object ast *astCmd argShift int diff --git a/cmdlang/userbuiltin_test.go b/cmdlang/userbuiltin_test.go index bb6cbee..9fa6c30 100644 --- a/cmdlang/userbuiltin_test.go +++ b/cmdlang/userbuiltin_test.go @@ -143,6 +143,7 @@ func TestInst_SetBuiltin(t *testing.T) { }{ {descr: "return as is", expr: `countTo3`, want: []string{"1", "2", "3"}}, {descr: "iterate over", expr: `foreach (countTo3) { |x| echo $x }`, wantOut: "1\n2\n3\n"}, + {descr: "iterate via foreach", expr: `["2" "4" "6"] | foreach { |x| echo $x }`, wantOut: "2\n4\n6\n"}, } for _, tt := range tests {