This commit is contained in:
parent
67671ce475
commit
a72724695b
|
@ -190,3 +190,13 @@ set! NAME VALUE
|
|||
|
||||
Sets the value of variable NAME to VALUE. VALUE must be non-null otherwise `set!` will raise an error.
|
||||
|
||||
### while
|
||||
|
||||
```
|
||||
while [GUARD] BLOCK
|
||||
```
|
||||
|
||||
Iterate over BLOCK while GUARD is true. If GUARD is not included, `while` will loop indefinitely.
|
||||
|
||||
BLOCK can call `break` and `continue` which will exit out of the loop, or jump to the start of the next iteration
|
||||
respectively. The return value of `while` will be nil, unless `break` is called with a value.
|
||||
|
|
|
@ -1039,7 +1039,7 @@ 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, blockIdx, []Object{v}, true) // TO INCLUDE: the index
|
||||
last, err = args.evalBlock(ctx, blockIdx, []Object{v}, false) // TO INCLUDE: the index
|
||||
if err != nil {
|
||||
if errors.As(err, &breakErr) {
|
||||
if !breakErr.isCont {
|
||||
|
@ -1052,7 +1052,7 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
|||
}
|
||||
case Hashable:
|
||||
err := t.Each(func(k string, v Object) error {
|
||||
last, err = args.evalBlock(ctx, blockIdx, []Object{StringObject(k), v}, true)
|
||||
last, err = args.evalBlock(ctx, blockIdx, []Object{StringObject(k), v}, false)
|
||||
return err
|
||||
})
|
||||
if errors.As(err, &breakErr) {
|
||||
|
@ -1069,7 +1069,7 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
last, err = args.evalBlock(ctx, blockIdx, []Object{v}, true) // TO INCLUDE: the index
|
||||
last, err = args.evalBlock(ctx, blockIdx, []Object{v}, false) // TO INCLUDE: the index
|
||||
if err != nil {
|
||||
if errors.As(err, &breakErr) {
|
||||
if !breakErr.isCont {
|
||||
|
@ -1086,6 +1086,42 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
|||
return last, nil
|
||||
}
|
||||
|
||||
func whileBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||
blockIdx := 1
|
||||
loopForever := false
|
||||
|
||||
if args.nargs() < 2 {
|
||||
blockIdx = 0
|
||||
loopForever = true
|
||||
}
|
||||
|
||||
var (
|
||||
breakErr errBreak
|
||||
)
|
||||
|
||||
for {
|
||||
if !loopForever {
|
||||
guard, err := args.evalArg(ctx, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isTruthy(guard) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := args.evalBlock(ctx, blockIdx, nil, false); err != nil {
|
||||
if errors.As(err, &breakErr) {
|
||||
if !breakErr.isCont {
|
||||
return breakErr.ret, nil
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func errorBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
if len(args.args) < 1 {
|
||||
return nil, errors.New("need at least one arguments")
|
||||
|
|
|
@ -105,6 +105,7 @@ func New(opts ...InstOption) *Inst {
|
|||
|
||||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||
rootEC.addMacro("for", macroFunc(foreachBuiltin))
|
||||
rootEC.addMacro("while", macroFunc(whileBuiltin))
|
||||
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
||||
rootEC.addMacro("try", macroFunc(tryBuiltin))
|
||||
|
||||
|
|
|
@ -466,6 +466,76 @@ func TestBuiltins_For(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_While(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want string
|
||||
}{
|
||||
{desc: "iterate while true 1", expr: `
|
||||
set x 0
|
||||
while (lt $x 5) {
|
||||
echo $x
|
||||
set x (add $x 1)
|
||||
}
|
||||
echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"},
|
||||
{desc: "iterate while true 2", expr: `
|
||||
set x 20
|
||||
while (lt $x 5) {
|
||||
echo $x
|
||||
set x (add $x 1)
|
||||
}
|
||||
echo "done"`, want: "done\n(nil)\n"},
|
||||
{desc: "iterate for ever with break 1", expr: `
|
||||
set x 0
|
||||
while {
|
||||
echo $x
|
||||
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: `
|
||||
set x 0
|
||||
echo (while {
|
||||
echo $x
|
||||
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: `
|
||||
set x 0
|
||||
while {
|
||||
set x (add $x 1)
|
||||
if (or (eq $x 2) (eq $x 4)) {
|
||||
echo "quack"
|
||||
continue
|
||||
}
|
||||
echo $x
|
||||
if (ge $x 5) {
|
||||
break
|
||||
}
|
||||
}
|
||||
echo "done"`, want: "1\nquack\n3\nquack\n5\ndone\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)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, outW.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_Break(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
|
|
Loading…
Reference in a new issue