This commit is contained in:
commit
3076897eb7
|
@ -14,7 +14,7 @@ import (
|
||||||
type NoResults struct{}
|
type NoResults struct{}
|
||||||
|
|
||||||
func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
|
func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
|
||||||
res, err := r.inst.Eval(ctx, expr)
|
res, err := r.inst.EvalString(ctx, expr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ucl.ErrNotConvertable) {
|
if errors.Is(err, ucl.ErrNotConvertable) {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -172,6 +172,6 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
||||||
var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
|
var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
|
||||||
participle.Elide("Whitespace", "Comment"))
|
participle.Elide("Whitespace", "Comment"))
|
||||||
|
|
||||||
func parse(r io.Reader) (*astScript, error) {
|
func parse(fname string, r io.Reader) (*astScript, error) {
|
||||||
return parser.Parse("test", r)
|
return parser.Parse("test", r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1161,7 +1161,7 @@ func procBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
|
|
||||||
obj := procObject{args.eval, args.ec, blockObj.block}
|
obj := procObject{args.eval, args.ec, blockObj.block}
|
||||||
if procName != "" {
|
if procName != "" {
|
||||||
args.ec.addCmd(procName, obj)
|
args.ec.addUserCmd(procName, obj)
|
||||||
}
|
}
|
||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ func TestCSV_ReadRecord(t *testing.T) {
|
||||||
ucl.WithOut(&bfr),
|
ucl.WithOut(&bfr),
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err := inst.Eval(context.Background(), tt.eval)
|
_, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.wantOut, bfr.String())
|
assert.Equal(t, tt.wantOut, bfr.String())
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,7 +29,7 @@ func TestFS_Cat(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.FS(testFS)),
|
ucl.WithModule(builtins.FS(testFS)),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,7 +26,7 @@ func TestItrs_ToList(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.Itrs()),
|
ucl.WithModule(builtins.Itrs()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,7 +34,7 @@ func TestLists_First(t *testing.T) {
|
||||||
ucl.WithModule(builtins.Itrs()),
|
ucl.WithModule(builtins.Itrs()),
|
||||||
ucl.WithModule(builtins.Lists()),
|
ucl.WithModule(builtins.Lists()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,7 +34,7 @@ func TestLog_Puts(t *testing.T) {
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -28,7 +28,7 @@ func TestOS_Env(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.OS()),
|
ucl.WithModule(builtins.OS()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
|
|
@ -28,7 +28,7 @@ func TestStrs_ToUpper(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.Strs()),
|
ucl.WithModule(builtins.Strs()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,7 +59,7 @@ func TestStrs_ToLower(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.Strs()),
|
ucl.WithModule(builtins.Strs()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -90,7 +90,7 @@ func TestStrs_Trim(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.Strs()),
|
ucl.WithModule(builtins.Strs()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -122,7 +122,7 @@ func TestStrs_HasPrefix(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.Strs()),
|
ucl.WithModule(builtins.Strs()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -160,7 +160,7 @@ func TestStrs_Split(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.Strs()),
|
ucl.WithModule(builtins.Strs()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,7 +191,7 @@ func TestStrs_Join(t *testing.T) {
|
||||||
ucl.WithModule(builtins.Itrs()),
|
ucl.WithModule(builtins.Itrs()),
|
||||||
ucl.WithModule(builtins.Strs()),
|
ucl.WithModule(builtins.Strs()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func TestTime_FromUnix(t *testing.T) {
|
||||||
inst := ucl.New(
|
inst := ucl.New(
|
||||||
ucl.WithModule(builtins.Time()),
|
ucl.WithModule(builtins.Time()),
|
||||||
)
|
)
|
||||||
res, err := inst.Eval(context.Background(), tt.eval)
|
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -47,7 +47,7 @@ func TestTime_Sleep(t *testing.T) {
|
||||||
ucl.WithModule(builtins.Time()),
|
ucl.WithModule(builtins.Time()),
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err := inst.Eval(ctx, `time:sleep 1`)
|
_, err := inst.EvalString(ctx, `time:sleep 1`)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, "context canceled", err.Error())
|
assert.Equal(t, "context canceled", err.Error())
|
||||||
assert.True(t, time.Now().Sub(st) < time.Second)
|
assert.True(t, time.Now().Sub(st) < time.Second)
|
||||||
|
|
39
ucl/env.go
39
ucl/env.go
|
@ -1,18 +1,17 @@
|
||||||
package ucl
|
package ucl
|
||||||
|
|
||||||
type evalCtx struct {
|
type evalCtx struct {
|
||||||
root *evalCtx
|
root *evalCtx
|
||||||
parent *evalCtx
|
parent *evalCtx
|
||||||
commands map[string]invokable
|
commands map[string]invokable
|
||||||
macros map[string]macroable
|
macros map[string]macroable
|
||||||
vars map[string]Object
|
vars map[string]Object
|
||||||
pseudoVars map[string]pseudoVar
|
pseudoVars map[string]pseudoVar
|
||||||
|
userCommandFrame bool // Frame to use for user-defined commands
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) forkAndIsolate() *evalCtx {
|
func (ec *evalCtx) forkAndIsolate() *evalCtx {
|
||||||
newEc := &evalCtx{parent: ec}
|
return &evalCtx{parent: ec, root: ec.root, userCommandFrame: true}
|
||||||
newEc.root = newEc
|
|
||||||
return newEc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) fork() *evalCtx {
|
func (ec *evalCtx) fork() *evalCtx {
|
||||||
|
@ -27,6 +26,25 @@ func (ec *evalCtx) addCmd(name string, inv invokable) {
|
||||||
ec.root.commands[name] = inv
|
ec.root.commands[name] = inv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *evalCtx) addUserCmd(name string, inv invokable) {
|
||||||
|
frame := ec
|
||||||
|
for frame != nil {
|
||||||
|
if frame.userCommandFrame {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
frame = frame.parent
|
||||||
|
}
|
||||||
|
if frame == nil {
|
||||||
|
panic("no user command frame found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if frame.commands == nil {
|
||||||
|
frame.commands = make(map[string]invokable)
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.commands[name] = inv
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) addMacro(name string, inv macroable) {
|
func (ec *evalCtx) addMacro(name string, inv macroable) {
|
||||||
if ec.root.macros == nil {
|
if ec.root.macros == nil {
|
||||||
ec.root.macros = make(map[string]macroable)
|
ec.root.macros = make(map[string]macroable)
|
||||||
|
@ -61,6 +79,9 @@ func (ec *evalCtx) setOrDefineVar(name string, val Object) {
|
||||||
|
|
||||||
func (ec *evalCtx) getVar(name string) (Object, bool) {
|
func (ec *evalCtx) getVar(name string) (Object, bool) {
|
||||||
if ec.vars == nil {
|
if ec.vars == nil {
|
||||||
|
if ec.parent == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
return ec.parent.getVar(name)
|
return ec.parent.getVar(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
46
ucl/inst.go
46
ucl/inst.go
|
@ -53,7 +53,9 @@ type Module struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(opts ...InstOption) *Inst {
|
func New(opts ...InstOption) *Inst {
|
||||||
rootEC := &evalCtx{}
|
rootEC := &evalCtx{
|
||||||
|
userCommandFrame: true,
|
||||||
|
}
|
||||||
rootEC.root = rootEC
|
rootEC.root = rootEC
|
||||||
|
|
||||||
rootEC.addCmd("echo", invokableFunc(echoBuiltin))
|
rootEC.addCmd("echo", invokableFunc(echoBuiltin))
|
||||||
|
@ -141,8 +143,25 @@ func (inst *Inst) Out() io.Writer {
|
||||||
return inst.out
|
return inst.out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inst *Inst) Eval(ctx context.Context, expr string) (any, error) {
|
type EvalOption func(*evalOptions)
|
||||||
res, err := inst.eval(ctx, expr)
|
|
||||||
|
func WithSubEnv() EvalOption {
|
||||||
|
return func(opts *evalOptions) {
|
||||||
|
opts.forkEnv = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inst *Inst) Eval(ctx context.Context, r io.Reader, options ...EvalOption) (any, error) {
|
||||||
|
opts := evalOptions{
|
||||||
|
filename: "unnamed",
|
||||||
|
forkEnv: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(&opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := inst.eval(ctx, r, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrHalt) {
|
if errors.Is(err, ErrHalt) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -158,16 +177,29 @@ func (inst *Inst) Eval(ctx context.Context, expr string) (any, error) {
|
||||||
return goRes, nil
|
return goRes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inst *Inst) eval(ctx context.Context, expr string) (Object, error) {
|
func (inst *Inst) EvalString(ctx context.Context, expr string) (any, error) {
|
||||||
ast, err := parse(strings.NewReader(expr))
|
return inst.Eval(ctx, strings.NewReader(expr))
|
||||||
|
}
|
||||||
|
|
||||||
|
type evalOptions struct {
|
||||||
|
filename string
|
||||||
|
forkEnv bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inst *Inst) eval(ctx context.Context, r io.Reader, opts evalOptions) (Object, error) {
|
||||||
|
ast, err := parse(opts.filename, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
eval := evaluator{inst: inst}
|
eval := evaluator{inst: inst}
|
||||||
|
|
||||||
// TODO: this should be a separate forkAndIsolate() session
|
env := inst.rootEC
|
||||||
return eval.evalScript(ctx, inst.rootEC, ast)
|
if opts.forkEnv {
|
||||||
|
env = env.forkAndIsolate()
|
||||||
|
}
|
||||||
|
|
||||||
|
return eval.evalScript(ctx, env, ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PseudoVarHandler interface {
|
type PseudoVarHandler interface {
|
||||||
|
|
116
ucl/inst_test.go
116
ucl/inst_test.go
|
@ -3,6 +3,7 @@ package ucl_test
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -113,7 +114,7 @@ func TestInst_Eval(t *testing.T) {
|
||||||
outW := bytes.NewBuffer(nil)
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
inst := ucl.New(ucl.WithOut(outW), ucl.WithTestBuiltin())
|
inst := ucl.New(ucl.WithOut(outW), ucl.WithTestBuiltin())
|
||||||
res, err := inst.Eval(ctx, tt.expr)
|
res, err := inst.EvalString(ctx, tt.expr)
|
||||||
|
|
||||||
if tt.wantErr != nil {
|
if tt.wantErr != nil {
|
||||||
assert.ErrorIs(t, err, tt.wantErr)
|
assert.ErrorIs(t, err, tt.wantErr)
|
||||||
|
@ -129,6 +130,117 @@ func TestInst_Eval(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInst_Eval_WithSubEnv(t *testing.T) {
|
||||||
|
t.Run("global symbols should not leak across environments", func(t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
inst := ucl.New()
|
||||||
|
|
||||||
|
res, err := inst.Eval(ctx, strings.NewReader(`$a = "hello" ; $a`), ucl.WithSubEnv())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "hello", res)
|
||||||
|
|
||||||
|
res, err = inst.Eval(ctx, strings.NewReader(`$a`), ucl.WithSubEnv())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("environments should not leak when using hooks", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
descr string
|
||||||
|
eval1 string
|
||||||
|
eval2 string
|
||||||
|
want1 any
|
||||||
|
want2 any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
descr: "reading vars",
|
||||||
|
eval1: `$a = "hello" ; hook { $a }`,
|
||||||
|
eval2: `$a = "world" ; hook { $a }`,
|
||||||
|
want1: "hello",
|
||||||
|
want2: "world",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
descr: "modifying vars",
|
||||||
|
eval1: `$a = "hello" ; hook { $a = "new value" ; $a }`,
|
||||||
|
eval2: `$a = "world" ; hook { $a }`,
|
||||||
|
want1: "new value",
|
||||||
|
want2: "world",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
descr: "defining procs",
|
||||||
|
eval1: `proc say_hello { "hello" } ; hook { say_hello }`,
|
||||||
|
eval2: `proc say_hello { "world" } ; hook { say_hello }`,
|
||||||
|
want1: "hello",
|
||||||
|
want2: "world",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
descr: "exporting procs 1",
|
||||||
|
eval1: `export say_hello { "hello" } ; hook { say_hello }`,
|
||||||
|
eval2: `hook { say_hello }`,
|
||||||
|
want1: "hello",
|
||||||
|
want2: "hello",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
descr: "exporting procs 2",
|
||||||
|
eval1: `$a = "hello" ; export say_hello { $a = "world"; $a } ; hook { say_hello }`,
|
||||||
|
eval2: `$a = "other" ; hook { say_hello }`,
|
||||||
|
want1: "world",
|
||||||
|
want2: "world",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.descr, func(t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
hooks := make([]ucl.Invokable, 0)
|
||||||
|
|
||||||
|
inst := ucl.New()
|
||||||
|
inst.SetBuiltin("hook", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var hookProc ucl.Invokable
|
||||||
|
|
||||||
|
if err := args.Bind(&hookProc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hooks = append(hooks, hookProc)
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
inst.SetBuiltin("export", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var (
|
||||||
|
name string
|
||||||
|
hookProc ucl.Invokable
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := args.Bind(&name, &hookProc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inst.SetBuiltinInvokable(name, hookProc)
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
res, err := inst.Eval(ctx, strings.NewReader(tt.eval1), ucl.WithSubEnv())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, res)
|
||||||
|
|
||||||
|
res, err = inst.Eval(ctx, strings.NewReader(tt.eval2), ucl.WithSubEnv())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, res)
|
||||||
|
|
||||||
|
h1, err := hooks[0].Invoke(ctx, ucl.CallArgs{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want1, h1)
|
||||||
|
|
||||||
|
h2, err := hooks[1].Invoke(ctx, ucl.CallArgs{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want2, h2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestInst_SetPseudoVar(t *testing.T) {
|
func TestInst_SetPseudoVar(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
@ -155,7 +267,7 @@ func TestInst_SetPseudoVar(t *testing.T) {
|
||||||
inst.SetPseudoVar("bar", bar)
|
inst.SetPseudoVar("bar", bar)
|
||||||
inst.SetMissingPseudoVarHandler(missingPseudoVarType{})
|
inst.SetMissingPseudoVarHandler(missingPseudoVarType{})
|
||||||
|
|
||||||
res, err := inst.Eval(t.Context(), tt.expr)
|
res, err := inst.EvalString(t.Context(), tt.expr)
|
||||||
|
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
|
@ -151,7 +151,7 @@ func TestBuiltins_Echo(t *testing.T) {
|
||||||
outW := bytes.NewBuffer(nil)
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
res, err := inst.Eval(ctx, tt.expr)
|
res, err := inst.EvalString(ctx, tt.expr)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, res)
|
assert.Nil(t, res)
|
||||||
|
@ -1309,7 +1309,7 @@ func TestBuiltins_Keys(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, tt.expr)
|
res, err := inst.EvalString(ctx, tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, res, len(tt.wantItems))
|
assert.Len(t, res, len(tt.wantItems))
|
||||||
for _, i := range tt.wantItems {
|
for _, i := range tt.wantItems {
|
||||||
|
@ -1349,7 +1349,7 @@ func TestBuiltins_Filter(t *testing.T) {
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, tt.expr)
|
res, err := inst.EvalString(ctx, tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
@ -1375,7 +1375,7 @@ func TestBuiltins_Reduce(t *testing.T) {
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, tt.expr)
|
res, err := inst.EvalString(ctx, tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
@ -1404,7 +1404,7 @@ func TestBuiltins_Head(t *testing.T) {
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, tt.expr)
|
res, err := inst.EvalString(ctx, tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
@ -1459,7 +1459,7 @@ func TestBuiltins_LtLeGtLe(t *testing.T) {
|
||||||
inst.SetVar("true", true)
|
inst.SetVar("true", true)
|
||||||
inst.SetVar("false", false)
|
inst.SetVar("false", false)
|
||||||
|
|
||||||
eqRes, err := inst.Eval(ctx, tt.expr)
|
eqRes, err := inst.EvalString(ctx, tt.expr)
|
||||||
|
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
@ -1527,11 +1527,11 @@ func TestBuiltins_EqNe(t *testing.T) {
|
||||||
inst.SetVar("true", true)
|
inst.SetVar("true", true)
|
||||||
inst.SetVar("false", false)
|
inst.SetVar("false", false)
|
||||||
|
|
||||||
eqRes, err := inst.Eval(ctx, tt.expr)
|
eqRes, err := inst.EvalString(ctx, tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, eqRes)
|
assert.Equal(t, tt.want, eqRes)
|
||||||
|
|
||||||
neRes, err := inst.Eval(ctx, strings.ReplaceAll(tt.expr, "eq", "ne"))
|
neRes, err := inst.EvalString(ctx, strings.ReplaceAll(tt.expr, "eq", "ne"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, !tt.want, neRes)
|
assert.Equal(t, !tt.want, neRes)
|
||||||
})
|
})
|
||||||
|
@ -1562,7 +1562,7 @@ func TestBuiltins_Str(t *testing.T) {
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
eqRes, err := inst.Eval(ctx, tt.expr)
|
eqRes, err := inst.EvalString(ctx, tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, eqRes)
|
assert.Equal(t, tt.want, eqRes)
|
||||||
})
|
})
|
||||||
|
@ -1598,7 +1598,7 @@ func TestBuiltins_Int(t *testing.T) {
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
eqRes, err := inst.Eval(ctx, tt.expr)
|
eqRes, err := inst.EvalString(ctx, tt.expr)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1654,7 +1654,7 @@ func TestBuiltins_AddSubMupDivMod(t *testing.T) {
|
||||||
|
|
||||||
inst := New(WithOut(outW), WithTestBuiltin())
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
eqRes, err := inst.Eval(ctx, tt.expr)
|
eqRes, err := inst.EvalString(ctx, tt.expr)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1705,7 +1705,7 @@ func TestBuiltins_AndOrNot(t *testing.T) {
|
||||||
inst.SetVar("true", true)
|
inst.SetVar("true", true)
|
||||||
inst.SetVar("false", false)
|
inst.SetVar("false", false)
|
||||||
|
|
||||||
eqRes, err := inst.Eval(ctx, tt.expr)
|
eqRes, err := inst.EvalString(ctx, tt.expr)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1742,7 +1742,7 @@ func TestBuiltins_Cat(t *testing.T) {
|
||||||
inst.SetVar("true", true)
|
inst.SetVar("true", true)
|
||||||
inst.SetVar("false", false)
|
inst.SetVar("false", false)
|
||||||
|
|
||||||
eqRes, err := inst.Eval(ctx, tt.expr)
|
eqRes, err := inst.EvalString(ctx, tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, eqRes)
|
assert.Equal(t, tt.want, eqRes)
|
||||||
})
|
})
|
||||||
|
@ -1750,7 +1750,7 @@ func TestBuiltins_Cat(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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, strings.NewReader(expr), evalOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,10 @@ func (inst *Inst) SetBuiltin(name string, fn BuiltinHandler) {
|
||||||
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
|
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inst *Inst) SetBuiltinInvokable(name string, fn Invokable) {
|
||||||
|
inst.rootEC.addCmd(name, fn.inv)
|
||||||
|
}
|
||||||
|
|
||||||
type userBuiltin struct {
|
type userBuiltin struct {
|
||||||
fn func(ctx context.Context, args CallArgs) (any, error)
|
fn func(ctx context.Context, args CallArgs) (any, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
return x + y, nil
|
return x + y, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := inst.Eval(context.Background(), `add2 "Hello, " "World"`)
|
res, err := inst.EvalString(context.Background(), `add2 "Hello, " "World"`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "Hello, World", res)
|
assert.Equal(t, "Hello, World", res)
|
||||||
})
|
})
|
||||||
|
@ -44,7 +44,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
return x + y, nil
|
return x + y, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := inst.Eval(context.Background(), `add2 "Hello, " "World"`)
|
res, err := inst.EvalString(context.Background(), `add2 "Hello, " "World"`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "Hello, World", res)
|
assert.Equal(t, "Hello, World", res)
|
||||||
})
|
})
|
||||||
|
@ -85,7 +85,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.descr, func(t *testing.T) {
|
t.Run(tt.descr, func(t *testing.T) {
|
||||||
res, err := inst.Eval(context.Background(), tt.expr)
|
res, err := inst.EvalString(context.Background(), tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
@ -108,7 +108,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
return pair{x, y}, nil
|
return pair{x, y}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := inst.Eval(context.Background(), `add2 "Hello" "World"`)
|
res, err := inst.EvalString(context.Background(), `add2 "Hello" "World"`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, pair{"Hello", "World"}, res)
|
assert.Equal(t, pair{"Hello", "World"}, res)
|
||||||
})
|
})
|
||||||
|
@ -149,7 +149,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
return x.x + ":" + x.y, nil
|
return x.x + ":" + x.y, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := inst.Eval(context.Background(), tt.expr)
|
res, err := inst.EvalString(context.Background(), tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
@ -176,7 +176,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
return []string{"1", "2", "3"}, nil
|
return []string{"1", "2", "3"}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := inst.Eval(context.Background(), tt.expr)
|
res, err := inst.EvalString(context.Background(), tt.expr)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
assert.Equal(t, tt.wantOut, outW.String())
|
assert.Equal(t, tt.wantOut, outW.String())
|
||||||
|
@ -206,11 +206,11 @@ func TestCallArgs_Bind(t *testing.T) {
|
||||||
return ds.DoString(), nil
|
return ds.DoString(), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
va, err := inst.Eval(ctx, `dostr (sa)`)
|
va, err := inst.EvalString(ctx, `dostr (sa)`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "do string A: a val", va)
|
assert.Equal(t, "do string A: a val", va)
|
||||||
|
|
||||||
vb, err := inst.Eval(ctx, `dostr (sb)`)
|
vb, err := inst.EvalString(ctx, `dostr (sb)`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "do string B: foo bar", vb)
|
assert.Equal(t, "do string B: foo bar", vb)
|
||||||
})
|
})
|
||||||
|
@ -240,7 +240,7 @@ func TestCallArgs_Bind(t *testing.T) {
|
||||||
return fmt.Sprintf("[%v]", v), nil
|
return fmt.Sprintf("[%v]", v), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, tt.eval)
|
res, err := inst.EvalString(ctx, tt.eval)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
@ -295,7 +295,7 @@ func TestCallArgs_CanBind(t *testing.T) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err := inst.Eval(ctx, tt.eval)
|
_, err := inst.EvalString(ctx, tt.eval)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
@ -332,7 +332,7 @@ func TestCallArgs_CanBind(t *testing.T) {
|
||||||
return h.Value(k), nil
|
return h.Value(k), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, tt.eval)
|
res, err := inst.EvalString(ctx, tt.eval)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, res)
|
assert.Nil(t, res)
|
||||||
|
@ -370,7 +370,7 @@ func TestCallArgs_CanBind(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, `wrap { |x| toUpper $x }`)
|
res, err := inst.EvalString(ctx, `wrap { |x| toUpper $x }`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "[[HELLO]]", res)
|
assert.Equal(t, "[[HELLO]]", res)
|
||||||
})
|
})
|
||||||
|
@ -401,7 +401,7 @@ func TestCallArgs_CanBind(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, before)
|
assert.Nil(t, before)
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, `wrap { |x| toUpper $x }`)
|
res, err := inst.EvalString(ctx, `wrap { |x| toUpper $x }`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, res)
|
assert.Nil(t, res)
|
||||||
|
|
||||||
|
@ -437,7 +437,7 @@ func TestCallArgs_MissingCommandHandler(t *testing.T) {
|
||||||
return fmt.Sprintf("was %v", name), nil
|
return fmt.Sprintf("was %v", name), nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, tt.eval)
|
res, err := inst.EvalString(ctx, tt.eval)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, res)
|
assert.Equal(t, tt.want, res)
|
||||||
})
|
})
|
||||||
|
@ -460,27 +460,27 @@ func TestCallArgs_IsTopLevel(t *testing.T) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err := inst.Eval(ctx, `lvl "one"`)
|
_, err := inst.EvalString(ctx, `lvl "one"`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, res["one"])
|
assert.True(t, res["one"])
|
||||||
|
|
||||||
_, err = inst.Eval(ctx, `echo (lvl "two")`)
|
_, err = inst.EvalString(ctx, `echo (lvl "two")`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, res["two"])
|
assert.True(t, res["two"])
|
||||||
|
|
||||||
_, err = inst.Eval(ctx, `proc doLvl { |n| lvl $n } ; doLvl "three"`)
|
_, err = inst.EvalString(ctx, `proc doLvl { |n| lvl $n } ; doLvl "three"`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, res["three"])
|
assert.False(t, res["three"])
|
||||||
|
|
||||||
_, err = inst.Eval(ctx, `doLvl "four"`)
|
_, err = inst.EvalString(ctx, `doLvl "four"`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, res["four"])
|
assert.False(t, res["four"])
|
||||||
|
|
||||||
_, err = inst.Eval(ctx, `["a"] | map { |x| doLvl "five" ; $x }`)
|
_, err = inst.EvalString(ctx, `["a"] | map { |x| doLvl "five" ; $x }`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, res["five"])
|
assert.False(t, res["five"])
|
||||||
|
|
||||||
_, err = inst.Eval(ctx, `if 1 { lvl "six" }`)
|
_, err = inst.EvalString(ctx, `if 1 { lvl "six" }`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, res["six"])
|
assert.True(t, res["six"])
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue