Added switch options
This commit is contained in:
parent
e7138b6fe2
commit
cb6c6abb5f
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lmika/gopkgs/fp/slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -86,14 +85,32 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream strea
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd, cmd invokable) (object, error) {
|
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd, cmd invokable) (object, error) {
|
||||||
args, err := slices.MapWithError(ast.Args, func(a astCmdArg) (object, error) {
|
var (
|
||||||
return e.evalArg(ctx, ec, a)
|
pargs listObject
|
||||||
})
|
kwargs map[string]*listObject
|
||||||
if err != nil {
|
argsPtr *listObject
|
||||||
return nil, err
|
)
|
||||||
|
|
||||||
|
argsPtr = &pargs
|
||||||
|
for _, arg := range ast.Args {
|
||||||
|
if ident := arg.Ident; ident != nil && (*ident)[0] == '-' {
|
||||||
|
// Arg switch
|
||||||
|
if kwargs == nil {
|
||||||
|
kwargs = make(map[string]*listObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
argsPtr = &listObject{}
|
||||||
|
kwargs[(*ident)[1:]] = argsPtr
|
||||||
|
} else {
|
||||||
|
ae, err := e.evalArg(ctx, ec, arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argsPtr.Append(ae)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
invArgs := invocationArgs{ec: ec, inst: e.inst, args: args, currentStream: currentStream}
|
invArgs := invocationArgs{ec: ec, inst: e.inst, args: pargs, kwargs: kwargs, currentStream: currentStream}
|
||||||
|
|
||||||
if currentStream != nil {
|
if currentStream != nil {
|
||||||
if si, ok := cmd.(streamInvokable); ok {
|
if si, ok := cmd.(streamInvokable); ok {
|
||||||
|
|
|
@ -14,6 +14,10 @@ type object interface {
|
||||||
|
|
||||||
type listObject []object
|
type listObject []object
|
||||||
|
|
||||||
|
func (lo *listObject) Append(o object) {
|
||||||
|
*lo = append(*lo, o)
|
||||||
|
}
|
||||||
|
|
||||||
func (s listObject) String() string {
|
func (s listObject) String() string {
|
||||||
return fmt.Sprintf("%v", []object(s))
|
return fmt.Sprintf("%v", []object(s))
|
||||||
}
|
}
|
||||||
|
@ -152,6 +156,7 @@ type invocationArgs struct {
|
||||||
ec *evalCtx
|
ec *evalCtx
|
||||||
currentStream stream
|
currentStream stream
|
||||||
args []object
|
args []object
|
||||||
|
kwargs map[string]*listObject
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia invocationArgs) expectArgn(x int) error {
|
func (ia invocationArgs) expectArgn(x int) error {
|
||||||
|
|
|
@ -16,32 +16,35 @@ func (ca CallArgs) Bind(vars ...interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, v := range vars {
|
for i, v := range vars {
|
||||||
switch t := v.(type) {
|
if err := bindArg(v, ca.args.args[i]); err != nil {
|
||||||
case *string:
|
return err
|
||||||
tv, err := ca.args.stringArg(i)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*t = tv
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for proxy object
|
|
||||||
if po, ok := ca.args.args[i].(proxyObject); ok {
|
|
||||||
poValue := reflect.ValueOf(po.p)
|
|
||||||
argValue := reflect.ValueOf(v)
|
|
||||||
|
|
||||||
if argValue.Type().Kind() != reflect.Pointer {
|
|
||||||
continue
|
|
||||||
} else if argValue.Elem().Type() != poValue.Type() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
argValue.Elem().Set(poValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ca CallArgs) HasSwitch(name string) bool {
|
||||||
|
if ca.args.kwargs == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := ca.args.kwargs[name]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca CallArgs) BindSwitch(name string, val interface{}) error {
|
||||||
|
if ca.args.kwargs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vars, ok := ca.args.kwargs[name]
|
||||||
|
if !ok || len(*vars) != 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindArg(val, (*vars)[0])
|
||||||
|
}
|
||||||
|
|
||||||
func (inst *Inst) SetBuiltin(name string, fn func(ctx context.Context, args CallArgs) (any, error)) {
|
func (inst *Inst) SetBuiltin(name string, fn func(ctx context.Context, args CallArgs) (any, error)) {
|
||||||
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
|
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
|
||||||
}
|
}
|
||||||
|
@ -58,3 +61,25 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e
|
||||||
|
|
||||||
return fromGoValue(v)
|
return fromGoValue(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bindArg(v interface{}, arg object) error {
|
||||||
|
switch t := v.(type) {
|
||||||
|
case *string:
|
||||||
|
*t = arg.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for proxy object
|
||||||
|
if po, ok := arg.(proxyObject); ok {
|
||||||
|
poValue := reflect.ValueOf(po.p)
|
||||||
|
argValue := reflect.ValueOf(v)
|
||||||
|
|
||||||
|
if argValue.Type().Kind() != reflect.Pointer {
|
||||||
|
return nil
|
||||||
|
} else if !poValue.Type().AssignableTo(argValue.Elem().Type()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
argValue.Elem().Set(poValue)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@ package cmdlang_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/lmika/cmdlang-proto/cmdlang"
|
"github.com/lmika/cmdlang-proto/cmdlang"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInst_SetBuiltin(t *testing.T) {
|
func TestInst_SetBuiltin(t *testing.T) {
|
||||||
|
@ -25,6 +27,48 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
assert.Equal(t, "Hello, World", res)
|
assert.Equal(t, "Hello, World", res)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("simple builtin with optional switches and strings", func(t *testing.T) {
|
||||||
|
inst := cmdlang.New()
|
||||||
|
inst.SetBuiltin("add2", func(ctx context.Context, args cmdlang.CallArgs) (any, error) {
|
||||||
|
var x, y, sep string
|
||||||
|
|
||||||
|
if err := args.BindSwitch("sep", &sep); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := args.BindSwitch("left", &x); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := args.BindSwitch("right", &y); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := x + sep + y
|
||||||
|
if args.HasSwitch("upcase") {
|
||||||
|
v = strings.ToUpper(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
descr string
|
||||||
|
expr string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"plain eval", `add2 -sep ", " -right "world" -left "Hello"`, "Hello, world"},
|
||||||
|
{"swap 1", `add2 -right "right" -left "left" -sep ":"`, "left:right"},
|
||||||
|
{"swap 2", `add2 -left "left" -sep ":" -right "right" -upcase`, "LEFT:RIGHT"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.descr, func(t *testing.T) {
|
||||||
|
res, err := inst.Eval(context.Background(), tt.expr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("builtin return proxy object", func(t *testing.T) {
|
t.Run("builtin return proxy object", func(t *testing.T) {
|
||||||
type pair struct {
|
type pair struct {
|
||||||
x, y string
|
x, y string
|
||||||
|
@ -89,3 +133,54 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCallArgs_Bind(t *testing.T) {
|
||||||
|
t.Run("bind to an interface", func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
inst := cmdlang.New()
|
||||||
|
inst.SetBuiltin("sa", func(ctx context.Context, args cmdlang.CallArgs) (any, error) {
|
||||||
|
return doStringA{this: "a val"}, nil
|
||||||
|
})
|
||||||
|
inst.SetBuiltin("sb", func(ctx context.Context, args cmdlang.CallArgs) (any, error) {
|
||||||
|
return doStringB{left: "foo", right: "bar"}, nil
|
||||||
|
})
|
||||||
|
inst.SetBuiltin("dostr", func(ctx context.Context, args cmdlang.CallArgs) (any, error) {
|
||||||
|
var ds doStringable
|
||||||
|
|
||||||
|
if err := args.Bind(&ds); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ds.DoString(), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
va, err := inst.Eval(ctx, `dostr (sa)`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "do string A: a val", va)
|
||||||
|
|
||||||
|
vb, err := inst.Eval(ctx, `dostr (sb)`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "do string B: foo bar", vb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type doStringable interface {
|
||||||
|
DoString() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type doStringA struct {
|
||||||
|
this string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da doStringA) DoString() string {
|
||||||
|
return "do string A: " + da.this
|
||||||
|
}
|
||||||
|
|
||||||
|
type doStringB struct {
|
||||||
|
left, right string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (da doStringB) DoString() string {
|
||||||
|
return "do string B: " + da.left + " " + da.right
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue