Added the while loop
All checks were successful
Build / build (push) Successful in 1m57s

This commit is contained in:
Leon Mika 2025-02-08 09:50:35 +11:00
parent 67671ce475
commit a72724695b
4 changed files with 120 additions and 3 deletions

View file

@ -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.

View file

@ -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")

View file

@ -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))

View file

@ -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