Made undefined variables return an error
All checks were successful
Build / build (push) Successful in 2m5s
All checks were successful
Build / build (push) Successful in 2m5s
Previously undefined variables would be set to nil, which introduced subtle errors. Changed this so that referencing an undefined variable will produce an error. This behaviour can changed as part of the instance configuration by declaring a missing-var handler.
This commit is contained in:
parent
2d56517de9
commit
c433e4bf53
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 {
|
||||||
|
|
|
@ -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|
|
||||||
|
|
|
@ -197,7 +197,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)
|
||||||
|
@ -387,7 +389,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 != "" {
|
||||||
|
|
|
@ -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