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.
|
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()
|
l := t.Len()
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
v := t.Index(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 err != nil {
|
||||||
if errors.As(err, &breakErr) {
|
if errors.As(err, &breakErr) {
|
||||||
if !breakErr.isCont {
|
if !breakErr.isCont {
|
||||||
|
@ -1052,7 +1052,7 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
}
|
}
|
||||||
case Hashable:
|
case Hashable:
|
||||||
err := t.Each(func(k string, v Object) error {
|
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
|
return err
|
||||||
})
|
})
|
||||||
if errors.As(err, &breakErr) {
|
if errors.As(err, &breakErr) {
|
||||||
|
@ -1069,7 +1069,7 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
return nil, err
|
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 err != nil {
|
||||||
if errors.As(err, &breakErr) {
|
if errors.As(err, &breakErr) {
|
||||||
if !breakErr.isCont {
|
if !breakErr.isCont {
|
||||||
|
@ -1086,6 +1086,42 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
return last, nil
|
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) {
|
func errorBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) < 1 {
|
if len(args.args) < 1 {
|
||||||
return nil, errors.New("need at least one arguments")
|
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("if", macroFunc(ifBuiltin))
|
||||||
rootEC.addMacro("for", macroFunc(foreachBuiltin))
|
rootEC.addMacro("for", macroFunc(foreachBuiltin))
|
||||||
|
rootEC.addMacro("while", macroFunc(whileBuiltin))
|
||||||
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
||||||
rootEC.addMacro("try", macroFunc(tryBuiltin))
|
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) {
|
func TestBuiltins_Break(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
Loading…
Reference in a new issue