From 9dbacdcc8b591cbba3d06429fb58a8a61c5b2879 Mon Sep 17 00:00:00 2001 From: "Moises P. Sena" Date: Wed, 13 Dec 2023 09:06:49 -0300 Subject: [PATCH] core!: 1) add Pipe expression; 2) Converters at VM level; 3) IndexGet Proxy --- builtin_types.go | 3 ++ builtins.go | 32 +++++++++++- builtins_funcs.go | 58 +++++++++++++++++++-- builtins_zfuncs.go | 33 ++++++++++++ compiler.go | 2 + compiler_nodes.go | 26 +++++----- compiler_test.go | 33 ++++++++++-- converter.go | 53 +++++++++++++++++++ eval.go | 13 ++--- eval_test.go | 2 +- methods_test.go | 5 -- objects.go | 112 +++++++++++++++++++++++++++++++++++----- objects_api.go | 4 +- objects_namedargs.go | 30 ++++++++--- objects_reflect.go | 73 +++++++++++++++++--------- objects_reflect_test.go | 27 ++++++---- objectwriter.go | 7 ++- parser/node/expr.go | 21 ++++++++ parser/parser.go | 18 +++++++ parser/parser_test.go | 6 +-- parser/scanner.go | 21 ++------ parser/scanner_test.go | 2 +- token/token.go | 4 +- vm.go | 63 +++++++++++++--------- vm_loop.go | 2 +- vm_run.go | 6 ++- vm_test.go | 21 ++++++-- 27 files changed, 531 insertions(+), 146 deletions(-) create mode 100644 converter.go diff --git a/builtin_types.go b/builtin_types.go index 0c163a4..ffaea6d 100644 --- a/builtin_types.go +++ b/builtin_types.go @@ -126,6 +126,9 @@ var ( TReflectMethod = &BuiltinObjType{ NameValue: "reflectMethod", } + TIndexGetProxy = &BuiltinObjType{ + NameValue: "indexGetProxy", + } ) func init() { diff --git a/builtins.go b/builtins.go index 4dbeb97..cf56d95 100644 --- a/builtins.go +++ b/builtins.go @@ -49,6 +49,7 @@ const ( BuiltinSortReverse BuiltinFilter BuiltinMap + BuiltinEach BuiltinReduce BuiltinForEach BuiltinTypeName @@ -127,6 +128,7 @@ var BuiltinsMap = map[string]BuiltinType{ "sortReverse": BuiltinSortReverse, "filter": BuiltinFilter, "map": BuiltinMap, + "each": BuiltinEach, "reduce": BuiltinReduce, "foreach": BuiltinForEach, "typeName": BuiltinTypeName, @@ -206,6 +208,24 @@ func (m BuiltinObjectsMap) Build() BuiltinObjectsMap { return cp } +func (m BuiltinObjectsMap) Append(obj ...Object) BuiltinObjectsMap { + var ( + cp = make(BuiltinObjectsMap, len(m)) + max BuiltinType + ) + + for t, obj := range m { + cp[t] = obj + if t > max { + max = t + } + } + for i, object := range obj { + cp[max+BuiltinType(i)] = object + } + return cp +} + // BuiltinObjects is list of builtins, exported for REPL. var BuiltinObjects = BuiltinObjectsMap{ // :makeArray is a private builtin function to help destructuring array assignments @@ -256,11 +276,11 @@ var BuiltinObjects = BuiltinObjectsMap{ }, BuiltinSort: &BuiltinFunction{ Name: "sort", - Value: funcPOROe(BuiltinSortFunc), + Value: funcPpVM_OCoROe(BuiltinSortFunc), }, BuiltinSortReverse: &BuiltinFunction{ Name: "sortReverse", - Value: funcPOROe(BuiltinSortReverseFunc), + Value: funcPpVM_OCoROe(BuiltinSortReverseFunc), }, BuiltinTypeName: &BuiltinFunction{ Name: "typeName", @@ -456,6 +476,10 @@ func init() { Name: "map", Value: BuiltinMapFunc, } + BuiltinObjects[BuiltinEach] = &BuiltinFunction{ + Name: "each", + Value: BuiltinEachFunc, + } BuiltinObjects[BuiltinReduce] = &BuiltinFunction{ Name: "reduce", Value: BuiltinReduceFunc, @@ -513,3 +537,7 @@ func init() { // builtin addMethod // //gad:callable func(vm *VM, o CallerObject, handler CallerObject, override=bool) (err error) + +// builtin sort, sortReverse +// +//gad:callable func(vm *VM, v Object, less=CallerObject) (ret Object, err error) diff --git a/builtins_funcs.go b/builtins_funcs.go index c74cf6f..acaf01d 100644 --- a/builtins_funcs.go +++ b/builtins_funcs.go @@ -103,7 +103,7 @@ func BuiltinAppendFunc(c Call) (Object, error) { } return obj, nil case Appender: - return obj.Append(c.Args.Values()...) + return obj.Append(c.VM, c.Args.Values()...) default: return Nil, NewArgumentTypeError( "1st", @@ -245,10 +245,10 @@ func BuiltinCapFunc(arg Object) Object { return Int(n) } -func BuiltinSortFunc(arg Object) (ret Object, err error) { +func BuiltinSortFunc(vm *VM, arg Object, less CallerObject) (ret Object, err error) { switch obj := arg.(type) { case Sorter: - ret, err = obj.Sort() + ret, err = obj.Sort(vm, less) case String: s := []rune(obj) sort.Slice(s, func(i, j int) bool { @@ -273,7 +273,7 @@ func BuiltinSortFunc(arg Object) (ret Object, err error) { return } -func BuiltinSortReverseFunc(arg Object) (Object, error) { +func BuiltinSortReverseFunc(vm *VM, arg Object, less CallerObject) (Object, error) { switch obj := arg.(type) { case ReverseSorter: return obj.SortReverse() @@ -441,6 +441,56 @@ func BuiltinMapFunc(c Call) (_ Object, err error) { return ret, nil } +func BuiltinEachFunc(c Call) (_ Object, err error) { + var ( + iterabler = &Arg{ + Name: "iterable", + Accept: func(v Object) string { + if !Mapable(v) && !Iterable(v) { + return "mapable|iterable" + } + return "" + }, + } + + callback = &Arg{ + Name: "callback", + Accept: func(v Object) string { + if !Callable(v) { + return "callable" + } + return "" + }, + } + ) + + if err = c.Args.Destructure(iterabler, callback); err != nil { + return + } + + var ( + args = Array{Nil, Nil} + caller VMCaller + ) + + if caller, err = NewInvoker(c.VM, callback.Value).Caller(Args{args}, &c.NamedArgs); err != nil { + return + } + + var ( + it = iterabler.Value.(Iterabler).Iterate(c.VM) + fe = NewForEach(it, args, 0, caller) + ) + + for fe.Next() { + if _, err = fe.Call(); err != nil { + return + } + } + + return iterabler.Value, nil +} + func BuiltinReduceFunc(c Call) (_ Object, err error) { var ( iterabler = &Arg{ diff --git a/builtins_zfuncs.go b/builtins_zfuncs.go index 7d02013..0324bf9 100644 --- a/builtins_zfuncs.go +++ b/builtins_zfuncs.go @@ -246,3 +246,36 @@ func funcPpVM_CoCobRe(fn func(*VM, CallerObject, CallerObject, bool) error) Call return } } + +// funcPpVM_OCoROe is a generated function to make CallableFunc. +// Source: func(vm *VM, v Object, less=CallerObject) (ret Object, err error) +func funcPpVM_OCoROe(fn func(*VM, Object, CallerObject) (Object, error)) CallableFunc { + return func(c Call) (ret Object, err error) { + if err := c.Args.CheckLen(1); err != nil { + return Nil, err + } + + var ( + less CallerObject + less_ = &NamedArgVar{ + Name: "less", + Accept: func(v Object) error { + var ok bool + if less, ok = v.(CallerObject); !ok { + return NewArgumentTypeError("less", "CallerObject", v.Type().Name()) + } + return nil + }, + } + ) + if err := c.NamedArgs.Get(less_); err != nil { + return Nil, err + } + + vm := c.VM + v := c.Args.Get(0) + + ret, err = fn(vm, v, less) + return + } +} diff --git a/compiler.go b/compiler.go index cf330c0..b0eeb2b 100644 --- a/compiler.go +++ b/compiler.go @@ -494,6 +494,8 @@ func (c *Compiler) Compile(nd ast.Node) error { return c.compileReturnStmt(nt) case *node.CallExpr: return c.compileCallExpr(nt) + case *node.PipeExpr: + return c.compilePipeExpr(nt) case *node.ImportExpr: return c.compileImportExpr(nt) case *node.CondExpr: diff --git a/compiler_nodes.go b/compiler_nodes.go index ee85f49..58fc974 100644 --- a/compiler_nodes.go +++ b/compiler_nodes.go @@ -1319,6 +1319,20 @@ func (c *Compiler) compileSliceExpr(nd *node.SliceExpr) error { return nil } +func (c *Compiler) compilePipeExpr(nd *node.PipeExpr) error { + var call node.CallExpr + switch t := nd.Dst.(type) { + case *node.CallExpr: + call = *t + default: + call = node.CallExpr{ + Func: t, + } + } + call.CallArgs.Args.Values = append([]node.Expr{nd.Src}, call.CallArgs.Args.Values...) + return c.Compile(&call) +} + func (c *Compiler) compileCallExpr(nd *node.CallExpr) error { var ( selExpr *node.SelectorExpr @@ -1333,18 +1347,6 @@ func (c *Compiler) compileCallExpr(nd *node.CallExpr) error { selExpr, isSelector = nd.Func.(*node.SelectorExpr) } if isSelector { - switch t := selExpr.Sel.(type) { - case *node.StringLit: - if t.CanIdent() && t.Value[0] == '!' { - cp := *nd - cp.Func = &node.Ident{ - NamePos: t.ValuePos, - Name: t.Value[1:], - } - cp.CallArgs.Args.Values = append([]node.Expr{selExpr.Expr}, cp.CallArgs.Args.Values...) - return c.compileCallExpr(&cp) - } - } if err := c.Compile(selExpr.Expr); err != nil { return err } diff --git a/compiler_test.go b/compiler_test.go index 79c110c..5e8be78 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -115,8 +115,31 @@ func concatInsts(insts ...[]byte) []byte { return out } -func TestCompiler_CompileNameCallerToIdentCall(t *testing.T) { - expectCompile(t, `global (a, x); a.!filter(x)`, bytecode( +func TestCompiler_CompilePipe(t *testing.T) { + expectCompile(t, `"a".|filter`, bytecode( + Array{String("a")}, + compFunc(concatInsts( + makeInst(OpGetBuiltin, int(BuiltinFilter)), + makeInst(OpConstant, 0), + makeInst(OpCall, 1, 0), + makeInst(OpPop), + makeInst(OpReturn, 0), + )), + )) + expectCompile(t, `var a; a.|filter`, bytecode( + Array{}, + compFunc(concatInsts( + makeInst(OpNull), + makeInst(OpDefineLocal, 0), + makeInst(OpGetBuiltin, int(BuiltinFilter)), + makeInst(OpGetLocal, 0), + makeInst(OpCall, 1, 0), + makeInst(OpPop), + makeInst(OpReturn, 0), + ), + withLocals(1)), + )) + expectCompile(t, `global (a, x); a.|filter(x)`, bytecode( Array{String("a"), String("x")}, compFunc(concatInsts( makeInst(OpGetBuiltin, int(BuiltinFilter)), @@ -128,7 +151,7 @@ func TestCompiler_CompileNameCallerToIdentCall(t *testing.T) { ), withLocals(0)), )) - expectCompile(t, `global (a, x); a.!map(x)`, bytecode( + expectCompile(t, `global (a, x); a.|map(x)`, bytecode( Array{String("a"), String("x")}, compFunc(concatInsts( makeInst(OpGetBuiltin, int(BuiltinMap)), @@ -140,7 +163,7 @@ func TestCompiler_CompileNameCallerToIdentCall(t *testing.T) { ), withLocals(0)), )) - expectCompile(t, `global (a, x); a.!reduce(x)`, bytecode( + expectCompile(t, `global (a, x); a.|reduce(x)`, bytecode( Array{String("a"), String("x")}, compFunc(concatInsts( makeInst(OpGetBuiltin, int(BuiltinReduce)), @@ -152,7 +175,7 @@ func TestCompiler_CompileNameCallerToIdentCall(t *testing.T) { ), withLocals(0)), )) - expectCompile(t, `var x; [].!x()`, bytecode( + expectCompile(t, `var x; [].|x()`, bytecode( Array{}, compFunc(concatInsts( makeInst(OpNull), diff --git a/converter.go b/converter.go new file mode 100644 index 0000000..adaabd3 --- /dev/null +++ b/converter.go @@ -0,0 +1,53 @@ +package gad + +import "reflect" + +type ObjectConverters struct { + ToGoHandlers map[ObjectType]func(vm *VM, v Object) any + ToObjectHandlers map[reflect.Type]func(vm *VM, v any) (Object, error) +} + +func NewObjectConverters() *ObjectConverters { + return &ObjectConverters{ + ToGoHandlers: make(map[ObjectType]func(vm *VM, v Object) any), + ToObjectHandlers: make(map[reflect.Type]func(vm *VM, v any) (Object, error)), + } +} + +func (oc *ObjectConverters) Register(objType ObjectType, togo func(vm *VM, v Object) any, goType reflect.Type, toObject func(vm *VM, v any) (Object, error)) *ObjectConverters { + if objType != nil { + oc.ToGoHandlers[objType] = togo + } + if goType != nil { + oc.ToObjectHandlers[goType] = toObject + } + return oc +} + +func (oc *ObjectConverters) ToObject(vm *VM, v any) (Object, error) { + typ := reflect.TypeOf(v) + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + + if h := oc.ToObjectHandlers[typ]; h != nil { + return h(vm, v) + } + + return ToObject(v) +} + +func (oc *ObjectConverters) ToInterface(vm *VM, v Object) any { + if h := oc.ToGoHandlers[v.Type()]; h != nil { + return h(vm, v) + } + return ToInterface(v) +} + +func (vm *VM) ToObject(v any) (Object, error) { + return vm.ObjectConverters.ToObject(vm, v) +} + +func (vm *VM) ToInterface(v Object) any { + return vm.ObjectConverters.ToInterface(vm, v) +} diff --git a/eval.go b/eval.go index e4f21dc..d860fdc 100644 --- a/eval.go +++ b/eval.go @@ -18,7 +18,6 @@ type Eval struct { Opts CompilerOptions VM *VM ModulesCache []Object - firstRun bool } // NewEval returns new Eval object. @@ -40,10 +39,9 @@ func NewEval(opts CompilerOptions, runOpts ...*RunOpts) *Eval { } return &Eval{ - RunOpts: runopts, - Opts: opts, - VM: NewVM(nil).SetRecover(true), - firstRun: true, + RunOpts: runopts, + Opts: opts, + VM: NewVM(nil).SetRecover(true), } } @@ -89,11 +87,6 @@ func (r *Eval) run(ctx context.Context) (ret Object, err error) { defer close(doneCh) copy(r.VM.stack[:], r.Locals) ret, err = r.VM.RunOpts(r.RunOpts) - - if r.firstRun { - r.RunOpts.Builtins = r.VM.builtins - r.firstRun = false - } }() select { diff --git a/eval_test.go b/eval_test.go index 5be5b8c..f40d290 100644 --- a/eval_test.go +++ b/eval_test.go @@ -234,6 +234,6 @@ func TestEval(t *testing.T) { require.Nil(t, ret) require.Nil(t, bc) require.Contains(t, err.Error(), - `Parse Error: expected statement, found '...'`) + `Parse Error: expected statement, found '.'`) }) } diff --git a/methods_test.go b/methods_test.go index 816335b..aca0c9a 100644 --- a/methods_test.go +++ b/methods_test.go @@ -1,7 +1,6 @@ package gad import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -24,8 +23,4 @@ func TestMethodArgs(t *testing.T) { assert.Nil(t, args.GetMethod([]ObjectType{TString, TInt, TFloat, TText})) assert.NotNil(t, args.GetMethod([]ObjectType{TString})) assert.Nil(t, args.GetMethod([]ObjectType{})) - args.WalkSorted(func(m *CallerMethod) any { - fmt.Println(m) - return nil - }) } diff --git a/objects.go b/objects.go index 536b3f4..b2b72db 100644 --- a/objects.go +++ b/objects.go @@ -891,20 +891,38 @@ func (o Array) Items() (arr KeyValueArray) { return arr } -func (o Array) Sort() (_ Object, err error) { - sort.Slice(o, func(i, j int) bool { - if bo, _ := o[i].(BinaryOperatorHandler); bo != nil { - v, e := bo.BinaryOp(token.Less, o[j]) - if e != nil && err == nil { - err = e - return false - } - if v != nil { - return !v.IsFalsy() +func (o Array) Sort(vm *VM, less CallerObject) (_ Object, err error) { + if less == nil { + sort.Slice(o, func(i, j int) bool { + if bo, _ := o[i].(BinaryOperatorHandler); bo != nil { + v, e := bo.BinaryOp(token.Less, o[j]) + if e != nil && err == nil { + err = e + return false + } + if v != nil { + return !v.IsFalsy() + } } + return false + }) + } else { + var ( + args = Array{Nil, Nil} + caller VMCaller + ) + + if caller, err = NewInvoker(vm, less).Caller(Args{args}, nil); err != nil { + return } - return false - }) + + sort.Slice(o, func(i, j int) bool { + args[0] = o[i] + args[1] = o[j] + ret, _ := caller.Call() + return !ret.IsFalsy() + }) + } return o, err } @@ -1179,7 +1197,7 @@ func (o Dict) Keys() Array { func (o Dict) SortedKeys() Array { keys := o.Keys() - keys.Sort() + keys.Sort(nil, nil) return keys } @@ -1688,3 +1706,71 @@ func (CallWrapper) IsFalsy() bool { func (CallWrapper) Equal(Object) bool { return false } + +type IndexGetProxy struct { + GetIndex func(vm *VM, index Object) (value Object, err error) + ToStr func() string + It func(vm *VM) Iterator +} + +func (i *IndexGetProxy) Iterate(vm *VM) Iterator { + return i.It(vm) +} + +func (i *IndexGetProxy) CanIterate() bool { + return i.It != nil +} + +func (i *IndexGetProxy) Type() ObjectType { + return TIndexGetProxy +} + +func (i *IndexGetProxy) ToString() string { + if i.ToStr != nil { + return i.ToStr() + } + return "" +} + +func (i *IndexGetProxy) IsFalsy() bool { + return false +} + +func (i *IndexGetProxy) Equal(right Object) bool { + if ri, _ := right.(*IndexGetProxy); ri != nil { + return &ri == &i + } + return false +} + +func (i IndexGetProxy) IndexGet(vm *VM, index Object) (value Object, err error) { + return i.GetIndex(vm, index) +} + +func StringIndexGetProxy(handler func(vm *VM, index string) (value Object, err error)) *IndexGetProxy { + return &IndexGetProxy{GetIndex: func(vm *VM, index Object) (value Object, err error) { + if s, ok := index.(String); !ok { + return nil, ErrInvalidIndex + } else { + return handler(vm, string(s)) + } + }} +} + +type DynamicIterator struct { + NextF func() bool + KeyF func() Object + ValueF func() (Object, error) +} + +func (d *DynamicIterator) Next() bool { + return d.NextF() +} + +func (d *DynamicIterator) Key() Object { + return d.KeyF() +} + +func (d *DynamicIterator) Value() (Object, error) { + return d.ValueF() +} diff --git a/objects_api.go b/objects_api.go index 5e57403..0b9715c 100644 --- a/objects_api.go +++ b/objects_api.go @@ -188,7 +188,7 @@ type Sorter interface { Object // Sort sorts object. if `update`, sort self and return then, other else sorts a self copy object. - Sort() (Object, error) + Sort(vm *VM, less CallerObject) (Object, error) } // ReverseSorter is an interface for return reverse sorted values. @@ -254,7 +254,7 @@ type Niler interface { type Appender interface { Object - Append(arr ...Object) (Object, error) + Append(vm *VM, arr ...Object) (Object, error) } // BytesConverter is to bytes converter diff --git a/objects_namedargs.go b/objects_namedargs.go index 760ea78..7bb8ead 100644 --- a/objects_namedargs.go +++ b/objects_namedargs.go @@ -451,11 +451,29 @@ func (o KeyValueArray) BinaryOp(tok token.Token, right Object) (Object, error) { right.Type().Name()) } -func (o KeyValueArray) Sort() (Object, error) { - sort.Slice(o, func(i, j int) bool { - return o[i].IsLess(o[j]) - }) - return o, nil +func (o KeyValueArray) Sort(vm *VM, less CallerObject) (_ Object, err error) { + if less == nil { + sort.Slice(o, func(i, j int) bool { + return o[i].IsLess(o[j]) + }) + } else { + var ( + args = Array{Nil, Nil} + caller VMCaller + ) + + if caller, err = NewInvoker(vm, less).Caller(Args{args}, nil); err != nil { + return + } + + sort.Slice(o, func(i, j int) bool { + args[0] = o[i] + args[1] = o[j] + ret, _ := caller.Call() + return !ret.IsFalsy() + }) + } + return o, err } func (o KeyValueArray) SortReverse() (Object, error) { @@ -571,7 +589,7 @@ func (o KeyValueArray) CallName(name string, c Call) (_ Object, err error) { ) } } - return o.Sort() + return o.Sort(c.VM, nil) case "sortReverse": if err = c.Args.CheckMaxLen(1); err != nil { return diff --git a/objects_reflect.go b/objects_reflect.go index 7b3fa64..c786ca2 100644 --- a/objects_reflect.go +++ b/objects_reflect.go @@ -279,7 +279,7 @@ func (r *ReflectType) New(vm *VM, m Dict) (Object, error) { case reflect.Map: rv = reflect.MakeMap(r.typ) for k, v := range m { - rv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(ToInterface(v))) + rv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(vm.ToInterface(v))) } } return &ReflectValue{typ: r, v: rv}, nil @@ -299,6 +299,10 @@ type ReflectValuer interface { } func NewReflectValue(v any) (ReflectValuer, error) { + if rv, _ := v.(ReflectValuer); rv != nil { + return rv, nil + } + var ( rv = reflect.ValueOf(v) isnill bool @@ -331,7 +335,7 @@ func NewReflectValue(v any) (ReflectValuer, error) { orv := ReflectValue{typ: t, v: rv, ptr: ptr, nil: isnill} switch rv.Kind() { case reflect.Struct: - return &ReflectStruct{orv}, nil + return &ReflectStruct{ReflectValue: orv}, nil case reflect.Map: return &ReflectMap{orv}, nil case reflect.Slice: @@ -517,7 +521,7 @@ func (r *ReflectFunc) Call(c Call) (_ Object, err error) { } dddType := typ.In(numIn - 1).Elem() argv = make([]reflect.Value, argc) - if err = reflectCallArgsToValues(typ, dddType, numIn, c.Args, argv); err != nil { + if err = reflectCallArgsToValues(c.VM, typ, dddType, numIn, c.Args, argv); err != nil { return } } else if numIn == 1 && reflectCallTypeToArgv(typ.In(0), &c, &argv) { @@ -526,7 +530,7 @@ func (r *ReflectFunc) Call(c Call) (_ Object, err error) { return nil, ErrType.NewError(fmt.Sprintf("wrong number of args: got %d want %d", argc, numIn)) } else { argv = make([]reflect.Value, argc) - if err = reflectCallArgsToValues(typ, nil, numIn, c.Args, argv); err != nil { + if err = reflectCallArgsToValues(c.VM, typ, nil, numIn, c.Args, argv); err != nil { return } } @@ -539,21 +543,21 @@ func (r *ReflectFunc) Call(c Call) (_ Object, err error) { ret, err = safeCall(r.v, argv) if err == nil { if ret.IsValid() { - return ToObject(ret.Interface()) + return c.VM.ToObject(ret.Interface()) } return Nil, nil } return } -func reflectCallArgsToValues(typ, dddType reflect.Type, numIn int, args Args, argv []reflect.Value) (err error) { +func reflectCallArgsToValues(vm *VM, typ, dddType reflect.Type, numIn int, args Args, argv []reflect.Value) (err error) { args.Walk(func(i int, arg Object) any { var rarg reflect.Value switch t := arg.(type) { case *ReflectValue: rarg = t.v default: - rarg = reflect.ValueOf(ToInterface(arg)) + rarg = reflect.ValueOf(vm.ToInterface(arg)) } rarg = indirectInterface(rarg) @@ -600,7 +604,7 @@ func (o *ReflectArray) IsFalsy() bool { return o.v.Len() == 0 } -func (o *ReflectArray) IndexGet(_ *VM, index Object) (value Object, err error) { +func (o *ReflectArray) IndexGet(vm *VM, index Object) (value Object, err error) { var ix int switch t := index.(type) { case Int: @@ -621,10 +625,10 @@ func (o *ReflectArray) IndexGet(_ *VM, index Object) (value Object, err error) { return nil, ErrIndexOutOfBounds } - return ToObject(o.v.Index(ix).Interface()) + return vm.ToObject(o.v.Index(ix).Interface()) } -func (o *ReflectArray) IndexSet(_ *VM, index, value Object) (err error) { +func (o *ReflectArray) IndexSet(vm *VM, index, value Object) (err error) { var ix int switch t := index.(type) { case Int: @@ -645,7 +649,7 @@ func (o *ReflectArray) IndexSet(_ *VM, index, value Object) (err error) { if rv, _ := value.(*ReflectValue); rv != nil { v = rv.v } else { - v = reflect.ValueOf(ToInterface(value)) + v = reflect.ValueOf(vm.ToInterface(value)) } if ix >= o.v.Len() { @@ -691,7 +695,7 @@ var ( _ Slicer = (*ReflectSlice)(nil) ) -func (o *ReflectSlice) Append(items ...Object) (_ Object, err error) { +func (o *ReflectSlice) Append(vm *VM, items ...Object) (_ Object, err error) { var ( itemType = o.typ.typ.Elem() values = reflect.MakeSlice(o.typ.typ, len(items), len(items)) @@ -699,7 +703,7 @@ func (o *ReflectSlice) Append(items ...Object) (_ Object, err error) { ) for i, item := range items { - if itemV, err = prepareArg(reflect.ValueOf(ToInterface(item)), itemType); err != nil { + if itemV, err = prepareArg(reflect.ValueOf(vm.ToInterface(item)), itemType); err != nil { return } values.Index(i).Set(itemV) @@ -764,28 +768,28 @@ func (o *ReflectMap) ToString() string { return fmt.Sprintf("%s", o) } -func (o *ReflectMap) IndexDelete(_ *VM, index Object) (err error) { - o.v.SetMapIndex(reflect.ValueOf(ToInterface(index)), reflect.Value{}) +func (o *ReflectMap) IndexDelete(vm *VM, index Object) (err error) { + o.v.SetMapIndex(reflect.ValueOf(vm.ToInterface(index)), reflect.Value{}) return nil } -func (o *ReflectMap) IndexGet(_ *VM, index Object) (value Object, err error) { - v := o.v.MapIndex(reflect.ValueOf(ToInterface(index))) +func (o *ReflectMap) IndexGet(vm *VM, index Object) (value Object, err error) { + v := o.v.MapIndex(reflect.ValueOf(vm.ToInterface(index))) if !v.IsValid() || v.IsNil() { return Nil, nil } - return ToObject(v.Interface()) + return vm.ToObject(v.Interface()) } -func (o *ReflectMap) IndexSet(_ *VM, index, value Object) (err error) { +func (o *ReflectMap) IndexSet(vm *VM, index, value Object) (err error) { var v reflect.Value if rv, _ := value.(*ReflectValue); rv != nil { v = rv.v } else { - v = reflect.ValueOf(ToInterface(value)) + v = reflect.ValueOf(vm.ToInterface(value)) } - o.v.SetMapIndex(reflect.ValueOf(ToInterface(index)), v) + o.v.SetMapIndex(reflect.ValueOf(vm.ToInterface(index)), v) return } @@ -810,6 +814,8 @@ func (o *ReflectMap) Copy() (obj Object) { type ReflectStruct struct { ReflectValue + fieldHandler func(vm *VM, s *ReflectStruct, name string, v any) any + fallbackIndexHandler func(vm *VM, s *ReflectStruct, name string) (handled bool, value Object, err error) } func (r *ReflectStruct) Iterate(vm *VM) Iterator { @@ -822,16 +828,37 @@ var ( _ IndexGetSetter = (*ReflectStruct)(nil) ) +func (r *ReflectStruct) FieldHandler(handler func(vm *VM, s *ReflectStruct, name string, v any) any) *ReflectStruct { + r.fieldHandler = handler + return r +} + +func (r *ReflectStruct) FalbackIndexHandler(handler func(vm *VM, s *ReflectStruct, name string) (handled bool, value Object, err error)) *ReflectStruct { + r.fallbackIndexHandler = handler + return r +} + func (r *ReflectStruct) IndexGet(vm *VM, index Object) (value Object, err error) { return r.IndexGetS(vm, index.ToString()) } func (r *ReflectStruct) IndexGetS(vm *VM, index string) (value Object, err error) { if field := r.typ.fields[index]; field != nil { - value, err = ToObject(r.v.FieldByIndex(field.f.Index).Interface()) + v := r.v.FieldByIndex(field.f.Index).Interface() + if r.fieldHandler != nil { + v = r.fieldHandler(vm, r, index, v) + } + return vm.ToObject(v) } else if m := r.typ.methods[index]; m != nil && m.m.Type.NumIn() == 1 { _, err = r.callName(m, Call{VM: vm}) } else { + if r.fallbackIndexHandler != nil { + if handled, val, err := r.fallbackIndexHandler(vm, r, index); err != nil { + return nil, err + } else if handled { + return vm.ToObject(val) + } + } err = ErrInvalidIndex.NewError(index) } return @@ -875,7 +902,7 @@ func (r *ReflectStruct) SetFieldValue(vm *VM, df *ReflectField, value Object) (e if rv, _ := value.(*ReflectValue); rv != nil { v = rv.v } else { - v = reflect.ValueOf(ToInterface(value)) + v = reflect.ValueOf(vm.ToInterface(value)) } if df.ptr { diff --git a/objects_reflect_test.go b/objects_reflect_test.go index 6e35caf..7cb0345 100644 --- a/objects_reflect_test.go +++ b/objects_reflect_test.go @@ -28,6 +28,7 @@ func TestReflectFunction_Call(t *testing.T) { return i }, Array{Int(1), Int(2)}, int64(3), nil}, } + vm := (&VM{}).Setup(SetupOpts{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r, err := NewReflectValue(tt.fn) @@ -35,7 +36,7 @@ func TestReflectFunction_Call(t *testing.T) { return } - c := Call{Args: Args{tt.args}} + c := Call{VM: vm, Args: Args{tt.args}} got, err := r.(*ReflectFunc).Call(c) if !checkError(t, fmt.Sprintf("Call(%T)", c), tt.wantErr, err) { return @@ -82,6 +83,7 @@ func TestReflectStruct_IndexGet(t *testing.T) { {"6", &b{V2: &a{}}, "V2.X", Int(0), nil}, {"7", &b{V2: &a{X: 3}}, "V2.X", Int(3), nil}, } + vm := (&VM{}).Setup(SetupOpts{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r, err := NewReflectValue(tt.obj) @@ -94,7 +96,7 @@ func TestReflectStruct_IndexGet(t *testing.T) { ) obj = r for _, key := range strings.Split(tt.key, ".") { - if got, err = obj.(*ReflectStruct).IndexGet(nil, String(key)); err == nil { + if got, err = obj.(*ReflectStruct).IndexGet(vm, String(key)); err == nil { obj = got } else if !checkError(t, fmt.Sprintf("IndexGet(%v)", key), tt.wantErr, err) { return @@ -167,6 +169,7 @@ func TestReflectMap_IndexGet(t *testing.T) { {"1", map[string]any{"X": 1}, "X", Int(1), nil}, {"2", map[string]any{"x": map[string]any{"y": 1}}, "x.y", Int(1), nil}, } + vm := (&VM{}).Setup(SetupOpts{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r, err := NewReflectValue(tt.obj) @@ -179,7 +182,7 @@ func TestReflectMap_IndexGet(t *testing.T) { ) obj = r for _, key := range strings.Split(tt.key, ".") { - if got, err = obj.(*ReflectMap).IndexGet(nil, String(key)); err == nil { + if got, err = obj.(*ReflectMap).IndexGet(vm, String(key)); err == nil { obj = got } else if !checkError(t, fmt.Sprintf("IndexGet(%v)", key), tt.wantErr, err) { return @@ -200,6 +203,7 @@ func TestReflectSlice_IndexGet(t *testing.T) { }{ {"1", []string{"a"}, Int(0), String("a"), nil}, } + vm := (&VM{}).Setup(SetupOpts{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r, err := NewReflectValue(tt.obj) @@ -207,7 +211,7 @@ func TestReflectSlice_IndexGet(t *testing.T) { return } - got, err := r.(IndexGetter).IndexGet(nil, tt.key) + got, err := r.(IndexGetter).IndexGet(vm, tt.key) if !checkError(t, fmt.Sprintf("IndexGet(%T)", tt.obj), tt.wantErr, err) { return } @@ -240,6 +244,7 @@ func TestReflectStruct_IndexSet(t *testing.T) { {"4", &b{}, String("V2"), MustToObject(f), nil}, {"5", &b{}, String("V2"), MustToObject(nil), nil}, } + vm := (&VM{}).Setup(SetupOpts{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r, err := NewReflectValue(tt.obj) @@ -248,11 +253,11 @@ func TestReflectStruct_IndexSet(t *testing.T) { } obj := r.(*ReflectStruct) - err = obj.IndexSet(nil, tt.key, tt.value) + err = obj.IndexSet(vm, tt.key, tt.value) if !checkError(t, fmt.Sprintf("IndexSet(%T)", tt.obj), tt.wantErr, err) { return } - got, err := obj.IndexGet(nil, tt.key) + got, err := obj.IndexGet(vm, tt.key) if !checkError(t, fmt.Sprintf("IndexGet(%T)", tt.obj), tt.wantErr, err) { return } @@ -271,6 +276,7 @@ func TestReflectMap_IndexSet(t *testing.T) { }{ {"6", map[string]any{}, String("a"), String("b"), nil}, } + vm := (&VM{}).Setup(SetupOpts{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r, err := NewReflectValue(tt.obj) @@ -279,11 +285,11 @@ func TestReflectMap_IndexSet(t *testing.T) { } obj := r.(*ReflectMap) - err = obj.IndexSet(nil, tt.key, tt.value) + err = obj.IndexSet(vm, tt.key, tt.value) if !checkError(t, fmt.Sprintf("IndexSet(%T)", tt.obj), tt.wantErr, err) { return } - got, err := obj.IndexGet(nil, tt.key) + got, err := obj.IndexGet(vm, tt.key) if !checkError(t, fmt.Sprintf("IndexGet(%T)", tt.obj), tt.wantErr, err) { return } @@ -302,6 +308,7 @@ func TestReflectSlice_IndexSet(t *testing.T) { }{ {"7", []string{""}, Int(0), String("a"), nil}, } + vm := (&VM{}).Setup(SetupOpts{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r, err := NewReflectValue(tt.obj) @@ -310,11 +317,11 @@ func TestReflectSlice_IndexSet(t *testing.T) { } obj := r.(*ReflectSlice) - err = obj.IndexSet(nil, tt.key, tt.value) + err = obj.IndexSet(vm, tt.key, tt.value) if !checkError(t, fmt.Sprintf("IndexSet(%T)", tt.obj), tt.wantErr, err) { return } - got, err := obj.IndexGet(nil, tt.key) + got, err := obj.IndexGet(vm, tt.key) if !checkError(t, fmt.Sprintf("IndexGet(%T)", tt.obj), tt.wantErr, err) { return } diff --git a/objectwriter.go b/objectwriter.go index 78633a4..125f932 100644 --- a/objectwriter.go +++ b/objectwriter.go @@ -1,7 +1,6 @@ package gad import ( - "fmt" "io" ) @@ -19,8 +18,12 @@ var DefaultObjectToWrite ObjectToWriterFunc = func(vm *VM, w io.Writer, obj Obje if ToWritable(obj) { n, err = obj.(ToWriter).WriteTo(vm, w) } else { + var s Object + if s, err = vm.Builtins[BuiltinString].(CallerObject).Call(Call{VM: vm, Args: Args{Array{obj}}}); err != nil { + return false, 0, err + } var n32 int - n32, err = fmt.Fprint(w, obj) + n32, err = w.Write([]byte(s.(String))) n += int64(n32) } handled = true diff --git a/parser/node/expr.go b/parser/node/expr.go index b1792eb..fb7cd75 100644 --- a/parser/node/expr.go +++ b/parser/node/expr.go @@ -1347,3 +1347,24 @@ func (e *NamedArgVarLit) End() source.Pos { func (e *NamedArgVarLit) String() string { return "**" + e.Value.String() } + +type PipeExpr struct { + TokenPos source.Pos + Src Expr + Dst Expr +} + +func (p *PipeExpr) Pos() source.Pos { + return p.Src.Pos() +} + +func (p *PipeExpr) End() source.Pos { + return p.Dst.End() +} + +func (p *PipeExpr) String() string { + return p.Src.String() + ".|" + p.Dst.String() +} + +func (p *PipeExpr) ExprNode() { +} diff --git a/parser/parser.go b/parser/parser.go index fed6fdf..6b03857 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -420,6 +420,8 @@ L: x = p.ParseIndexOrSlice(x) case token.LParen: x = p.ParseCall(x) + case token.Pipe: + x = p.ParsePipe(x) default: break L } @@ -438,6 +440,19 @@ func (p *Parser) ParseCall(x node.Expr) *node.CallExpr { } } +func (p *Parser) ParsePipe(x node.Expr) *node.PipeExpr { + if p.Trace { + defer untracep(tracep(p, "Pipe")) + } + + pos := p.Expect(token.Pipe) + return &node.PipeExpr{ + TokenPos: pos, + Src: x, + Dst: p.ParseExpr(), + } +} + func (p *Parser) CallArgsOf(lparen, rparen source.Pos, exprs ...node.Expr) (params *node.CallArgs) { params = &node.CallArgs{ LParen: lparen, @@ -514,6 +529,9 @@ func (p *Parser) ParseCallArgs(start, end token.Token) *node.CallArgs { } return p.CallArgsOf(t.LBrace, t.RBrace, exprs...) default: + if t == nil { + return &node.CallArgs{} + } return &node.CallArgs{ LParen: t.Pos(), RParen: t.End(), diff --git a/parser/parser_test.go b/parser/parser_test.go index 50faf46..382be80 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -231,9 +231,9 @@ func TestParserErrorList(t *testing.T) { list.Error()) } -func TestParseSpecialField(t *testing.T) { - expectParseString(t, "a.!x", "a.!x") - expectParseString(t, "a.!x()", "a.!x()") +func TestParsePipe(t *testing.T) { + expectParseString(t, "a.|x", "a.|x") + expectParseString(t, "a.|x()", "a.|x()") } func TestParseDecl(t *testing.T) { diff --git a/parser/scanner.go b/parser/scanner.go index 7caedf8..f58a4f1 100644 --- a/parser/scanner.go +++ b/parser/scanner.go @@ -477,16 +477,14 @@ do: case ':': t.Token = s.Switch2(token.Colon, token.Define) case '.': - if '0' <= s.Ch && s.Ch <= '9' { + if s.Ch == '|' { + s.Next() + t.Token = token.Pipe + } else if '0' <= s.Ch && s.Ch <= '9' { insertSemi = true t.Token, t.Literal = s.ScanNumber(true) } else { t.Token = token.Period - if s.Ch == '.' && s.Peek() == '.' { - s.Next() - s.Next() // consume last '.' - t.Token = token.Ellipsis - } } case ',': t.Token = token.Comma @@ -613,16 +611,7 @@ do: case '=': t.Token = s.Switch3(token.Assign, token.Equal, '>', token.Lambda) case '!': - if s.Offset >= 2 && s.Src[s.Offset-2] == '.' && (utils.IsLetter(s.Ch) || utils.IsLetter(rune(s.PeekNoSpace()))) { - if !utils.IsLetter(s.Ch) { - s.NextNoSpace() - } - t.Literal = "!" + s.ScanIdentifier() - t.Token = token.Ident - insertSemi = true - } else { - t.Token = s.Switch2(token.Not, token.NotEqual) - } + t.Token = s.Switch2(token.Not, token.NotEqual) case '&': if s.Ch == '^' { s.Next() diff --git a/parser/scanner_test.go b/parser/scanner_test.go index a2eac4c..316d700 100644 --- a/parser/scanner_test.go +++ b/parser/scanner_test.go @@ -135,7 +135,7 @@ func TestScanner_Scan(t *testing.T) { {token.LessEq, "<="}, {token.GreaterEq, ">="}, {token.Define, ":="}, - {token.Ellipsis, "..."}, + {token.Pipe, ".|"}, {token.LParen, "("}, {token.LBrack, "["}, {token.LBrace, "{"}, diff --git a/token/token.go b/token/token.go index f4cc4eb..537e1f4 100644 --- a/token/token.go +++ b/token/token.go @@ -74,7 +74,7 @@ const ( LessEq // <= GreaterEq // >= Define // := - Ellipsis // ... + Pipe // .| LParen // ( RParen // ) LBrack // [ @@ -183,7 +183,7 @@ var tokens = [...]string{ LessEq: "<=", GreaterEq: ">=", Define: ":=", - Ellipsis: "...", + Pipe: ".|", LParen: "(", LBrack: "[", LBrace: "{", diff --git a/vm.go b/vm.go index 19c4a16..249bf84 100644 --- a/vm.go +++ b/vm.go @@ -42,11 +42,11 @@ type VM struct { err error noPanic bool - builtins map[BuiltinType]Object - StdOut, StdErr *StackWriter StdIn *StackReader ObjectToWriter ObjectToWriter + + *SetupOpts } // NewVM creates a VM object. @@ -138,9 +138,7 @@ func (vm *VM) init(opts *RunOpts) error { return errors.New("invalid Bytecode") } - if vm.StdIn == nil { - vm.StdIn, vm.StdOut, vm.StdErr = NewStackReader(os.Stdin), NewStackWriter(os.Stdout), NewStackWriter(os.Stderr) - } + vm.Setup(SetupOpts{}) if opts.StdIn != nil { if s, _ := opts.StdIn.(*StackReader); s != nil { @@ -164,10 +162,8 @@ func (vm *VM) init(opts *RunOpts) error { } } - if opts.Builtins != nil { - vm.builtins = opts.Builtins - } else { - vm.builtins = BuiltinObjects.Build() + if opts.ObjectToWriter != nil { + vm.ObjectToWriter = opts.ObjectToWriter } // Resize modules cache or create it if not exists. @@ -191,21 +187,46 @@ func (vm *VM) resetState(args Args, namedArgs *NamedArgs) { vm.frameIndex = 1 } +func (vm *VM) Setup(opts SetupOpts) *VM { + if vm.SetupOpts != nil { + return vm + } + + if vm.StdIn == nil { + vm.StdIn, vm.StdOut, vm.StdErr = NewStackReader(os.Stdin), NewStackWriter(os.Stdout), NewStackWriter(os.Stderr) + } + + vm.SetupOpts = &opts + + if vm.Builtins == nil { + vm.Builtins = BuiltinObjects + } + vm.Builtins = vm.Builtins.Build() + + if vm.ObjectConverters == nil { + vm.ObjectConverters = NewObjectConverters() + } + + if vm.ObjectToWriter == nil { + vm.ObjectToWriter = DefaultObjectToWrite + } + + return vm +} + func (vm *VM) initAndRun(opts *RunOpts) (Object, error) { if vm.bytecode == nil || vm.bytecode.Main == nil { return nil, errors.New("invalid Bytecode") } + vm.Setup(SetupOpts{}) + vm.err = nil atomic.StoreInt64(&vm.abort, 0) vm.initGlobals(opts.Globals) vm.initCurrentFrame(opts.Args, opts.NamedArgs) vm.frameIndex = 1 - if vm.StdIn == nil { - vm.StdIn, vm.StdOut, vm.StdErr = NewStackReader(os.Stdin), NewStackWriter(os.Stdout), NewStackWriter(os.Stderr) - } - if opts.StdIn != nil { if s, _ := opts.StdIn.(*StackReader); s != nil { vm.StdIn = s @@ -227,16 +248,9 @@ func (vm *VM) initAndRun(opts *RunOpts) (Object, error) { vm.StdErr = NewStackWriter(opts.StdErr) } } - if opts.Builtins != nil { - vm.builtins = opts.Builtins - } else { - vm.builtins = BuiltinObjects.Build() - } if opts.ObjectToWriter != nil { vm.ObjectToWriter = opts.ObjectToWriter - } else { - vm.ObjectToWriter = DefaultObjectToWrite } // Resize modules cache or create it if not exists. @@ -412,7 +426,7 @@ func (vm *VM) GetSymbolValue(symbol *Symbol) (value Object, err error) { value = *v.Value } case ScopeBuiltin: - value = vm.builtins[BuiltinType(symbol.Index)] + value = vm.Builtins[BuiltinType(symbol.Index)] case ScopeFree: value = *vm.curFrame.freeVars[symbol.Index].Value } @@ -781,7 +795,7 @@ func (vm *VM) xOpCallCompiled(cfunc *CompiledFunction, numArgs int, flags OpCall vargsArr = arr } else { var items Object - if items, err = vm.builtins[BuiltinValues].(CallerObject).Call(Call{VM: vm, Args: Args{{vargs}}}); err != nil { + if items, err = vm.Builtins[BuiltinValues].(CallerObject).Call(Call{VM: vm, Args: Args{{vargs}}}); err != nil { return } vargsArr = items.(Array) @@ -969,7 +983,7 @@ func (vm *VM) xOpCallObject(co_ Object, numArgs int, flags OpCallFlag) (err erro vargs = arr } else { var items Object - if items, err = vm.builtins[BuiltinValues].(CallerObject).Call(Call{VM: vm, Args: Args{{vm.stack[basePointer+numArgs-1]}}}); err != nil { + if items, err = vm.Builtins[BuiltinValues].(CallerObject).Call(Call{VM: vm, Args: Args{{vm.stack[basePointer+numArgs-1]}}}); err != nil { return } vargs = items.(Array) @@ -1378,7 +1392,8 @@ func (v *vmPool) _acquire(vm *VM, cf *CompiledFunction) *VM { root: v.root, } vm.noPanic = v.root.noPanic - vm.builtins = v.root.builtins + vm.SetupOpts = v.root.SetupOpts + vm.ObjectToWriter = v.root.ObjectToWriter if v.vms == nil { v.vms = make(map[*VM]struct{}) diff --git a/vm_loop.go b/vm_loop.go index b4c22ff..dbf57ef 100644 --- a/vm_loop.go +++ b/vm_loop.go @@ -193,7 +193,7 @@ VMLoop: vm.curInsts = vm.curFrame.fn.Instructions case OpGetBuiltin: builtinIndex := BuiltinType(int(vm.curInsts[vm.ip+2]) | int(vm.curInsts[vm.ip+1])<<8) - vm.stack[vm.sp] = vm.builtins[builtinIndex] + vm.stack[vm.sp] = vm.Builtins[builtinIndex] vm.sp++ vm.ip += 2 case OpClosure: diff --git a/vm_run.go b/vm_run.go index f2bb86a..aecec25 100644 --- a/vm_run.go +++ b/vm_run.go @@ -5,6 +5,11 @@ import ( "io" ) +type SetupOpts struct { + ObjectConverters *ObjectConverters + Builtins BuiltinObjectsMap +} + type RunOpts struct { Globals IndexGetSetter Args Args @@ -12,7 +17,6 @@ type RunOpts struct { StdIn io.Reader StdOut io.Writer StdErr io.Writer - Builtins map[BuiltinType]Object ObjectToWriter ObjectToWriter } diff --git a/vm_test.go b/vm_test.go index d8df1ea..ac76b66 100644 --- a/vm_test.go +++ b/vm_test.go @@ -3759,15 +3759,28 @@ func TestVMCallCompiledFunction(t *testing.T) { return f(1,2,3,na0=4,na1=5)`, nil, Array{Int(1), Int(2), Array{Int(3)}, Int(4), Dict{"na1": Int(5)}}) + expectRun(t, ` + f := (v) => v*2 + return (10).|f`, nil, + Int(20)) + expectRun(t, ` first := (arr) => arr[0] - return [10].!first()`, nil, + return [10].|first()`, nil, Int(10)) expectRun(t, ` first := (arr, v) => arr[0] + v - return [10].!first(2)`, nil, + return [10].|first(2)`, nil, + Int(12)) + + expectRun(t, ` + return [10].|{a:{b:(arr, v) => arr[0] + v}}.a.b(2)`, nil, Int(12)) + + expectRun(t, ` + return (10).|{a:{b:(v) => v*2}}.a.b`, nil, + Int(20)) } func TestVMCallWithNamedArgs(t *testing.T) { @@ -4387,10 +4400,12 @@ func expectRun(t *testing.T, script string, opts *testopts, expect Object) { panic(r) } }() + vm.Setup(SetupOpts{ + Builtins: builtinObjects, + }) ropts := &RunOpts{ Globals: opts.globals, Args: Args{opts.args}, - Builtins: builtinObjects.Build(), ObjectToWriter: opts.objectToWriter, } if opts.namedArgs != nil {