Added the foreach loop
This commit is contained in:
parent
ea31e568b6
commit
c0f5cfb180
|
@ -30,7 +30,8 @@ type astListOrHash struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type astBlock 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 {
|
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) {
|
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 {
|
} else if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
} else if err != nil {
|
||||||
return nil, err
|
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 {
|
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 {
|
} else if args.nargs() == 0 {
|
||||||
// no elif or else
|
// no elif or else
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -154,3 +154,36 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||||
|
|
||||||
return nil, errors.New("malformed if-elif-else")
|
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
|
vars map[string]object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *evalCtx) fork() *evalCtx {
|
||||||
|
return &evalCtx{parent: ec}
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) addCmd(name string, inv invokable) {
|
func (ec *evalCtx) addCmd(name string, inv invokable) {
|
||||||
if ec.commands == nil {
|
if ec.commands == nil {
|
||||||
ec.commands = make(map[string]invokable)
|
ec.commands = make(map[string]invokable)
|
||||||
|
|
|
@ -32,6 +32,7 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addCmd("cat", invokableFunc(catBuiltin))
|
rootEC.addCmd("cat", invokableFunc(catBuiltin))
|
||||||
|
|
||||||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||||
|
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
||||||
|
|
||||||
//rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin))
|
//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])
|
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)
|
obj, err := ma.evalArg(ctx, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 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 {
|
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