Added switch options

This commit is contained in:
Leon Mika 2024-04-17 20:43:25 +10:00
parent 478ba09ad5
commit 5265efedc4
4 changed files with 118 additions and 28 deletions

View file

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"github.com/lmika/gopkgs/fp/slices"
"strconv"
"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) {
args, err := slices.MapWithError(ast.Args, func(a astCmdArg) (object, error) {
return e.evalArg(ctx, ec, a)
})
if err != nil {
return nil, err
var (
pargs listObject
kwargs map[string]*listObject
argsPtr *listObject
)
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 si, ok := cmd.(streamInvokable); ok {

View file

@ -14,6 +14,10 @@ type object interface {
type listObject []object
func (lo *listObject) Append(o object) {
*lo = append(*lo, o)
}
func (s listObject) String() string {
return fmt.Sprintf("%v", []object(s))
}
@ -152,6 +156,7 @@ type invocationArgs struct {
ec *evalCtx
currentStream stream
args []object
kwargs map[string]*listObject
}
func (ia invocationArgs) expectArgn(x int) error {

View file

@ -16,32 +16,35 @@ func (ca CallArgs) Bind(vars ...interface{}) error {
}
for i, v := range vars {
switch t := v.(type) {
case *string:
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)
if err := bindArg(v, ca.args.args[i]); err != nil {
return err
}
}
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)) {
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)
}
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
}

View file

@ -2,6 +2,7 @@ package cmdlang_test
import (
"context"
"strings"
"testing"
"github.com/lmika/cmdlang-proto/cmdlang"
@ -26,6 +27,48 @@ func TestInst_SetBuiltin(t *testing.T) {
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) {
type pair struct {
x, y string