Added switch options
This commit is contained in:
parent
478ba09ad5
commit
5265efedc4
|
@ -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 !poValue.Type().AssignableTo(argValue.Elem().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,6 +2,7 @@ package cmdlang_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/lmika/cmdlang-proto/cmdlang"
|
"github.com/lmika/cmdlang-proto/cmdlang"
|
||||||
|
@ -26,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
|
||||||
|
|
Loading…
Reference in a new issue