Added the foreach loop
This commit is contained in:
parent
ea31e568b6
commit
c0f5cfb180
|
@ -30,7 +30,8 @@ type astListOrHash struct {
|
|||
}
|
||||
|
||||
type astBlock struct {
|
||||
Statements []*astStatements `parser:"LC NL? @@ NL? RC"`
|
||||
Names []string `parser:"LC NL? (PIPE @Ident+ PIPE NL?)?"`
|
||||
Statements []*astStatements `parser:"@@ NL? RC"`
|
||||
}
|
||||
|
||||
type astCmdArg struct {
|
||||
|
|
|
@ -123,7 +123,7 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|||
}
|
||||
|
||||
if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) {
|
||||
return args.evalBlock(ctx, 1)
|
||||
return args.evalBlock(ctx, 1, nil, false)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|||
}
|
||||
|
||||
if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) {
|
||||
return args.evalBlock(ctx, 1)
|
||||
return args.evalBlock(ctx, 1, nil, false)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|||
}
|
||||
|
||||
if args.identIs(ctx, 0, "else") && args.nargs() > 1 {
|
||||
return args.evalBlock(ctx, 1)
|
||||
return args.evalBlock(ctx, 1, nil, false)
|
||||
} else if args.nargs() == 0 {
|
||||
// no elif or else
|
||||
return nil, nil
|
||||
|
@ -154,3 +154,36 @@ 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) {
|
||||
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
|
||||
}
|
||||
|
||||
var last object
|
||||
|
||||
switch t := items.(type) {
|
||||
case listObject:
|
||||
for _, v := range t {
|
||||
last, err = args.evalBlock(ctx, 1, []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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// TODO: streams
|
||||
}
|
||||
|
||||
return last, nil
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ type evalCtx struct {
|
|||
vars map[string]object
|
||||
}
|
||||
|
||||
func (ec *evalCtx) fork() *evalCtx {
|
||||
return &evalCtx{parent: ec}
|
||||
}
|
||||
|
||||
func (ec *evalCtx) addCmd(name string, inv invokable) {
|
||||
if ec.commands == nil {
|
||||
ec.commands = make(map[string]invokable)
|
||||
|
|
|
@ -32,6 +32,7 @@ func New(opts ...InstOption) *Inst {
|
|||
rootEC.addCmd("cat", invokableFunc(catBuiltin))
|
||||
|
||||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
||||
|
||||
//rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin))
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
|
|||
return ma.eval.evalArg(ctx, ma.ec, ma.ast.Args[ma.argShift+n])
|
||||
}
|
||||
|
||||
func (ma macroArgs) evalBlock(ctx context.Context, n int) (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
|
||||
|
@ -134,7 +134,17 @@ func (ma macroArgs) evalBlock(ctx context.Context, n int) (object, error) {
|
|||
return nil, errors.New("not a block object")
|
||||
}
|
||||
|
||||
return ma.eval.evalBlock(ctx, ma.ec, block.block)
|
||||
ec := ma.ec
|
||||
if pushScope {
|
||||
ec = ec.fork()
|
||||
}
|
||||
for i, n := range block.block.Names {
|
||||
if i < len(args) {
|
||||
ec.setVar(n, args[i])
|
||||
}
|
||||
}
|
||||
|
||||
return ma.eval.evalBlock(ctx, ec, block.block)
|
||||
}
|
||||
|
||||
type invocationArgs struct {
|
||||
|
|
|
@ -172,3 +172,31 @@ func TestBuiltins_If(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_ForEach(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want string
|
||||
}{
|
||||
{desc: "iterate over list", expr: `
|
||||
foreach ["1" "2" "3"] { |v|
|
||||
echo $v
|
||||
}`, want: "1\n2\n3\n(nil)\n"},
|
||||
{desc: "iterate over map", expr: `
|
||||
foreach [a:"1"] { |k v| echo $k "=" $v }`, want: "a=1\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 := inst.EvalAndDisplay(ctx, tt.expr)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, outW.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue