Merge remote-tracking branch 'origin/main'
All checks were successful
Build / build (push) Successful in 2m1s
All checks were successful
Build / build (push) Successful in 2m1s
This commit is contained in:
commit
e55f0e1ef3
|
@ -20,6 +20,15 @@ echo [ARGS...]
|
||||||
|
|
||||||
Displays the string representation of ARGS to stdout followed by a new line.
|
Displays the string representation of ARGS to stdout followed by a new line.
|
||||||
|
|
||||||
|
### error
|
||||||
|
|
||||||
|
```
|
||||||
|
error MSG
|
||||||
|
```
|
||||||
|
|
||||||
|
Raises an error with MSG as the given value. This will start unrolling the stack until a `try` block is
|
||||||
|
encountered, or until it reaches the top level stack, at which it will be displayed as a "runtime error".
|
||||||
|
|
||||||
### foreach
|
### foreach
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -297,7 +297,12 @@ func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return boolObject(!args.args[0].Truthy()), nil
|
v := args.args[0]
|
||||||
|
if v == nil {
|
||||||
|
return boolObject(true), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return boolObject(!v.Truthy()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var errObjectsNotEqual = errors.New("objects not equal")
|
var errObjectsNotEqual = errors.New("objects not equal")
|
||||||
|
@ -1021,6 +1026,13 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
return last, nil
|
return last, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func errorBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
|
if len(args.args) < 1 {
|
||||||
|
return nil, errors.New("need at least one arguments")
|
||||||
|
}
|
||||||
|
return nil, errRuntime{val: args.args[0]}
|
||||||
|
}
|
||||||
|
|
||||||
func breakBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
func breakBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) < 1 {
|
if len(args.args) < 1 {
|
||||||
return nil, errBreak{}
|
return nil, errBreak{}
|
||||||
|
|
11
ucl/eval.go
11
ucl/eval.go
|
@ -186,7 +186,16 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec
|
||||||
if v, ok := ec.getVar(*n.Var); ok {
|
if v, ok := ec.getVar(*n.Var); ok {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
|
||||||
|
if e.inst.missingVarHandler != nil {
|
||||||
|
dv, err := e.inst.missingVarHandler(ctx, *n.Var)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fromGoValue(dv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("undefined variable: " + *n.Var)
|
||||||
case n.MaybeSub != nil:
|
case n.MaybeSub != nil:
|
||||||
sub := n.MaybeSub.Sub
|
sub := n.MaybeSub.Sub
|
||||||
if sub == nil {
|
if sub == nil {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
type Inst struct {
|
type Inst struct {
|
||||||
out io.Writer
|
out io.Writer
|
||||||
missingBuiltinHandler MissingBuiltinHandler
|
missingBuiltinHandler MissingBuiltinHandler
|
||||||
|
missingVarHandler MissingVarHandler
|
||||||
echoPrinter EchoPrinter
|
echoPrinter EchoPrinter
|
||||||
|
|
||||||
rootEC *evalCtx
|
rootEC *evalCtx
|
||||||
|
@ -30,6 +31,12 @@ func WithMissingBuiltinHandler(handler MissingBuiltinHandler) InstOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithMissingVarHandler(handler MissingVarHandler) InstOption {
|
||||||
|
return func(i *Inst) {
|
||||||
|
i.missingVarHandler = handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithModule(module Module) InstOption {
|
func WithModule(module Module) InstOption {
|
||||||
return func(i *Inst) {
|
return func(i *Inst) {
|
||||||
for name, builtin := range module.Builtins {
|
for name, builtin := range module.Builtins {
|
||||||
|
@ -92,6 +99,7 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addCmd("break", invokableFunc(breakBuiltin))
|
rootEC.addCmd("break", invokableFunc(breakBuiltin))
|
||||||
rootEC.addCmd("continue", invokableFunc(continueBuiltin))
|
rootEC.addCmd("continue", invokableFunc(continueBuiltin))
|
||||||
rootEC.addCmd("return", invokableFunc(returnBuiltin))
|
rootEC.addCmd("return", invokableFunc(returnBuiltin))
|
||||||
|
rootEC.addCmd("error", invokableFunc(errorBuiltin))
|
||||||
|
|
||||||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||||
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
||||||
|
|
|
@ -119,6 +119,64 @@ func TestInst_Eval(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInst_MissingVarHandler(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
handler ucl.MissingVarHandler
|
||||||
|
want any
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "default - error",
|
||||||
|
expr: `$bla`,
|
||||||
|
wantErr: "undefined variable: bla",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "handler - set to nil",
|
||||||
|
handler: func(ctx context.Context, name string) (any, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
expr: `$bla`,
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "handler - set to a string 1",
|
||||||
|
handler: func(ctx context.Context, name string) (any, error) {
|
||||||
|
return "I am " + name, nil
|
||||||
|
},
|
||||||
|
expr: `$bla`,
|
||||||
|
want: "I am bla",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "handler - set to a string 2",
|
||||||
|
handler: func(ctx context.Context, name string) (any, error) {
|
||||||
|
return "I am " + name, nil
|
||||||
|
},
|
||||||
|
expr: `$something`,
|
||||||
|
want: "I am something",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := ucl.New(ucl.WithOut(outW), ucl.WithTestBuiltin(), ucl.WithMissingVarHandler(tt.handler))
|
||||||
|
res, err := inst.Eval(ctx, tt.expr)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, err.Error(), tt.wantErr)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var parseComments1 = `
|
var parseComments1 = `
|
||||||
proc lookup { |file|
|
proc lookup { |file|
|
||||||
foreach { |toks|
|
foreach { |toks|
|
||||||
|
|
11
ucl/objs.go
11
ucl/objs.go
|
@ -648,3 +648,14 @@ func (e errReturn) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrHalt = errors.New("halt")
|
var ErrHalt = errors.New("halt")
|
||||||
|
|
||||||
|
type errRuntime struct {
|
||||||
|
val Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errRuntime) Error() string {
|
||||||
|
if e.val == nil {
|
||||||
|
return "runtime error: (nil)"
|
||||||
|
}
|
||||||
|
return "runtime error: " + e.val.String()
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package ucl
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -58,13 +57,6 @@ func WithTestBuiltin() InstOption {
|
||||||
return &a, nil
|
return &a, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
|
||||||
if len(args.args) == 0 {
|
|
||||||
return nil, errors.New("an error occurred")
|
|
||||||
}
|
|
||||||
return nil, errors.New(args.args[0].String())
|
|
||||||
}))
|
|
||||||
|
|
||||||
i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
|
|
||||||
|
@ -230,7 +222,9 @@ func TestBuiltins_If(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
outW := bytes.NewBuffer(nil)
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin(), WithMissingVarHandler(func(ctx context.Context, name string) (any, error) {
|
||||||
|
return nil, nil
|
||||||
|
}))
|
||||||
err := evalAndDisplay(ctx, inst, tt.expr)
|
err := evalAndDisplay(ctx, inst, tt.expr)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -255,7 +249,7 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
try {
|
try {
|
||||||
error "bang"
|
error "bang"
|
||||||
}
|
}
|
||||||
echo "after"`, wantErr: "bang"},
|
echo "after"`, wantErr: "runtime error: bang"},
|
||||||
{desc: "try with catch - successful", expr: `
|
{desc: "try with catch - successful", expr: `
|
||||||
try {
|
try {
|
||||||
echo "good"
|
echo "good"
|
||||||
|
@ -276,7 +270,7 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
} catch { |err|
|
} catch { |err|
|
||||||
echo (cat "the error was = " $err)
|
echo (cat "the error was = " $err)
|
||||||
}
|
}
|
||||||
echo "after"`, want: "the error was = error:bang\nafter\n(nil)\n"},
|
echo "after"`, want: "the error was = error:runtime error: bang\nafter\n(nil)\n"},
|
||||||
{desc: "try with two catch - successful", expr: `
|
{desc: "try with two catch - successful", expr: `
|
||||||
try {
|
try {
|
||||||
echo "i'm good"
|
echo "i'm good"
|
||||||
|
@ -304,18 +298,18 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
echo "wow, we made it here"
|
echo "wow, we made it here"
|
||||||
}
|
}
|
||||||
echo "after"`, want: "wow, we made it here\nafter\n(nil)\n"},
|
echo "after"`, want: "wow, we made it here\nafter\n(nil)\n"},
|
||||||
{desc: "return value - single try", expr: `
|
{desc: "return value - single try 1", expr: `
|
||||||
set x (try { error "bang" })
|
set x (try { error "bang" })
|
||||||
$x`, wantErr: "bang"},
|
$x`, wantErr: "runtime error: bang"},
|
||||||
{desc: "return value - single try", expr: `
|
{desc: "return value - single try 2", expr: `
|
||||||
set x (try { error "bang" } catch { |err| $err })
|
set x (try { error "bang" } catch { |err| $err })
|
||||||
$x`, want: "error:bang\n"},
|
$x`, want: "error:runtime error: bang\n"},
|
||||||
{desc: "return value - try and catch - successful", expr: `
|
{desc: "return value - try and catch - successful", expr: `
|
||||||
set x (try { error "bang" } catch { "hello" })
|
set x (try { error "bang" } catch { "hello" })
|
||||||
$x`, want: "hello\n"},
|
$x`, want: "hello\n"},
|
||||||
{desc: "return value - try and catch - unsuccessful", expr: `
|
{desc: "return value - try and catch - unsuccessful", expr: `
|
||||||
set x (try { error "bang" } catch { error "boom" })
|
set x (try { error "bang" } catch { error "boom" })
|
||||||
$x`, wantErr: "boom"},
|
$x`, wantErr: "runtime error: boom"},
|
||||||
{desc: "try with finally - successful", expr: `
|
{desc: "try with finally - successful", expr: `
|
||||||
try {
|
try {
|
||||||
echo "all good"
|
echo "all good"
|
||||||
|
@ -329,7 +323,7 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
} finally {
|
} finally {
|
||||||
echo "always at end"
|
echo "always at end"
|
||||||
}
|
}
|
||||||
echo "after"`, want: "always at end\n", wantErr: "bang"},
|
echo "after"`, want: "always at end\n", wantErr: "runtime error: bang"},
|
||||||
{desc: "try with catch and finally - successful", expr: `
|
{desc: "try with catch and finally - successful", expr: `
|
||||||
try {
|
try {
|
||||||
echo "all good"
|
echo "all good"
|
||||||
|
@ -356,7 +350,7 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
} finally {
|
} finally {
|
||||||
echo "always at end"
|
echo "always at end"
|
||||||
}
|
}
|
||||||
echo "after"`, want: "always at end\n", wantErr: "boom"},
|
echo "after"`, want: "always at end\n", wantErr: "runtime error: boom"},
|
||||||
{desc: "try with finally - finally result discarded", expr: `
|
{desc: "try with finally - finally result discarded", expr: `
|
||||||
set a (try {
|
set a (try {
|
||||||
"return me"
|
"return me"
|
||||||
|
@ -369,13 +363,13 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
error "bang"
|
error "bang"
|
||||||
} finally {
|
} finally {
|
||||||
error "kaboom"
|
error "kaboom"
|
||||||
}`, wantErr: "bang"},
|
}`, wantErr: "runtime error: bang"},
|
||||||
{desc: "try with finally - error not discarded if try succeeds", expr: `
|
{desc: "try with finally - error not discarded if try succeeds", expr: `
|
||||||
try {
|
try {
|
||||||
echo "all good"
|
echo "all good"
|
||||||
} finally {
|
} finally {
|
||||||
error "kaboom"
|
error "kaboom"
|
||||||
}`, want: "all good\n", wantErr: "kaboom"},
|
}`, want: "all good\n", wantErr: "runtime error: kaboom"},
|
||||||
{desc: "try with finally with error - successful", expr: `
|
{desc: "try with finally with error - successful", expr: `
|
||||||
try {
|
try {
|
||||||
echo "all good"
|
echo "all good"
|
||||||
|
@ -384,13 +378,13 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
if (eq $err ()) { echo "that's nil" }
|
if (eq $err ()) { echo "that's nil" }
|
||||||
}
|
}
|
||||||
echo "after"`, want: "all good\nthe error was \nthat's nil\nafter\n(nil)\n"},
|
echo "after"`, want: "all good\nthe error was \nthat's nil\nafter\n(nil)\n"},
|
||||||
{desc: "try with finally - unsuccessful", expr: `
|
{desc: "try with finally - unsuccessful 2", expr: `
|
||||||
try {
|
try {
|
||||||
error "bang"
|
error "bang"
|
||||||
} finally { |err|
|
} finally { |err|
|
||||||
echo (cat "the error was " $err)
|
echo (cat "the error was " $err)
|
||||||
}
|
}
|
||||||
echo "after"`, want: "the error was error:bang\n", wantErr: "bang"},
|
echo "after"`, want: "the error was error:runtime error: bang\n", wantErr: "runtime error: bang"},
|
||||||
{desc: "try with too many finallies - unsuccessful", expr: `
|
{desc: "try with too many finallies - unsuccessful", expr: `
|
||||||
try {
|
try {
|
||||||
error "bang"
|
error "bang"
|
||||||
|
@ -412,7 +406,7 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
} finally {
|
} finally {
|
||||||
echo "outer"
|
echo "outer"
|
||||||
}
|
}
|
||||||
echo "after"`, want: "the error was error:bang\nouter caught\nouter\nafter\n(nil)\n"},
|
echo "after"`, want: "the error was error:runtime error: bang\nouter caught\nouter\nafter\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -420,7 +414,9 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
outW := bytes.NewBuffer(nil)
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin(), WithMissingVarHandler(func(ctx context.Context, name string) (any, error) {
|
||||||
|
return nil, nil
|
||||||
|
}))
|
||||||
err := evalAndDisplay(ctx, inst, tt.expr)
|
err := evalAndDisplay(ctx, inst, tt.expr)
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
|
@ -1568,6 +1564,7 @@ func TestBuiltins_AndOrNot(t *testing.T) {
|
||||||
{desc: "not 1", expr: `not $true`, want: false},
|
{desc: "not 1", expr: `not $true`, want: false},
|
||||||
{desc: "not 2", expr: `not $false`, want: true},
|
{desc: "not 2", expr: `not $false`, want: true},
|
||||||
{desc: "not 3", expr: `not $false $true`, want: true},
|
{desc: "not 3", expr: `not $false $true`, want: true},
|
||||||
|
{desc: "not 4", expr: `not ()`, want: true},
|
||||||
|
|
||||||
{desc: "short circuit and 1", expr: `and "hello" "world"`, want: "world"},
|
{desc: "short circuit and 1", expr: `and "hello" "world"`, want: "world"},
|
||||||
{desc: "short circuit and 2", expr: `and () "world"`, want: nil},
|
{desc: "short circuit and 2", expr: `and () "world"`, want: nil},
|
||||||
|
@ -1636,6 +1633,31 @@ func TestBuiltins_Cat(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_Error(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{desc: "error 1", expr: `error "bang"`, wantErr: "runtime error: bang"},
|
||||||
|
{desc: "error 2", expr: `error 123`, wantErr: "runtime error: 123"},
|
||||||
|
{desc: "error 3", expr: `error ()`, wantErr: "runtime error: (nil)"},
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Eval(ctx, tt.expr)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, tt.wantErr, err.Error())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func evalAndDisplay(ctx context.Context, inst *Inst, expr string) error {
|
func evalAndDisplay(ctx context.Context, inst *Inst, expr string) error {
|
||||||
res, err := inst.eval(ctx, expr)
|
res, err := inst.eval(ctx, expr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error)
|
type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error)
|
||||||
|
|
||||||
type MissingBuiltinHandler func(ctx context.Context, name string, args CallArgs) (any, error)
|
type MissingBuiltinHandler func(ctx context.Context, name string, args CallArgs) (any, error)
|
||||||
|
type MissingVarHandler func(ctx context.Context, name string) (any, error)
|
||||||
|
|
||||||
type CallArgs struct {
|
type CallArgs struct {
|
||||||
args invocationArgs
|
args invocationArgs
|
||||||
|
|
Loading…
Reference in a new issue