diff --git a/builtins.go b/builtins.go index b954d072..b0f2112f 100644 --- a/builtins.go +++ b/builtins.go @@ -1,5 +1,7 @@ package tengo +import "fmt" + var builtinFuncs = []*BuiltinFunction{ { Name: "len", @@ -132,247 +134,116 @@ func GetAllBuiltinFunctions() []*BuiltinFunction { return append([]*BuiltinFunction{}, builtinFuncs...) } -func builtinTypeName(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } +var builtinTypeName = CheckAnyArgs(func(args ...Object) (Object, error) { return &String{Value: args[0].TypeName()}, nil -} - -func builtinIsString(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*String); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsInt(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Int); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsFloat(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Float); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsBool(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Bool); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsChar(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Char); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsBytes(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Bytes); ok { - return TrueValue, nil - } - return FalseValue, nil -} +}, 1) -func builtinIsArray(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Array); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsImmutableArray(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*ImmutableArray); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsMap(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Map); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsImmutableMap(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*ImmutableMap); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsTime(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Time); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsError(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if _, ok := args[0].(*Error); ok { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsUndefined(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if args[0] == UndefinedValue { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsFunction(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - switch args[0].(type) { - case *CompiledFunction: - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsCallable(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if args[0].CanCall() { - return TrueValue, nil - } - return FalseValue, nil -} - -func builtinIsIterable(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - if args[0].CanIterate() { - return TrueValue, nil - } - return FalseValue, nil -} - -// len(obj object) => int -func builtinLen(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - switch arg := args[0].(type) { - case *Array: - return &Int{Value: int64(len(arg.Value))}, nil - case *ImmutableArray: - return &Int{Value: int64(len(arg.Value))}, nil - case *String: - return &Int{Value: int64(len(arg.Value))}, nil - case *Bytes: - return &Int{Value: int64(len(arg.Value))}, nil - case *Map: - return &Int{Value: int64(len(arg.Value))}, nil - case *ImmutableMap: - return &Int{Value: int64(len(arg.Value))}, nil - default: - return nil, ErrInvalidArgumentType{ - Name: "first", - Expected: "array/string/bytes/map", - Found: arg.TypeName(), - } - } -} - -//range(start, stop[, step]) -func builtinRange(args ...Object) (Object, error) { - numArgs := len(args) - if numArgs < 2 || numArgs > 3 { - return nil, ErrWrongNumArguments - } - var start, stop, step *Int - - for i, arg := range args { - v, ok := args[i].(*Int) - if !ok { - var name string - switch i { - case 0: - name = "start" - case 1: - name = "stop" - case 2: - name = "step" - } - - return nil, ErrInvalidArgumentType{ - Name: name, - Expected: "int", - Found: arg.TypeName(), - } +func builtinIsType(typeCheck func(arg Object) bool, args ...Object) CallableFunc { + return CheckAnyArgs(func(args ...Object) (Object, error) { + if typeCheck(args[0]) { + return TrueValue, nil } - if i == 2 && v.Value <= 0 { + return FalseValue, nil + }, 1) +} + +var builtinIsString = builtinIsType(func(arg Object) bool { + _, ok := arg.(*String) + return ok +}) + +var builtinIsInt = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Int) + return ok +}) + +var builtinIsFloat = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Float) + return ok +}) + +var builtinIsBool = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Bool) + return ok +}) + +var builtinIsChar = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Char) + return ok +}) + +var builtinIsBytes = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Bytes) + return ok +}) + +var builtinIsArray = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Array) + return ok +}) + +var builtinIsImmutableArray = builtinIsType(func(arg Object) bool { + _, ok := arg.(*ImmutableArray) + return ok +}) + +var builtinIsMap = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Map) + return ok +}) + +var builtinIsImmutableMap = builtinIsType(func(arg Object) bool { + _, ok := arg.(*ImmutableMap) + return ok +}) + +var builtinIsTime = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Time) + return ok +}) + +var builtinIsError = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Error) + return ok +}) + +var builtinIsUndefined = builtinIsType(func(arg Object) bool { + _, ok := arg.(*Undefined) + return ok +}) + +var builtinIsFunction = builtinIsType(func(arg Object) bool { + _, ok := arg.(*CompiledFunction) + return ok +}) + +var builtinIsCallable = builtinIsType(func(arg Object) bool { + return arg.CanCall() +}) + +var builtinIsIterable = builtinIsType(func(arg Object) bool { + return arg.CanIterate() +}) + +var builtinLen = CheckAnyArgs(func(args ...Object) (Object, error) { + if !args[0].HasLen() { + return nil, fmt.Errorf("arg type %s does not have a length value", args[0].TypeName()) + } + return &Int{Value: int64(args[0].Len())}, nil +}, 1) + +var builtinRange = CheckOptArgs(func(args ...Object) (Object, error) { + start := args[0].(*Int) + stop := args[1].(*Int) + step := &Int{Value: int64(1)} + if len(args) == 3 { + step = args[2].(*Int) + if step.Value <= 0 { return nil, ErrInvalidRangeStep } - switch i { - case 0: - start = v - case 1: - stop = v - case 2: - step = v - } } - - if step == nil { - step = &Int{Value: int64(1)} - } - return buildRange(start.Value, stop.Value, step.Value), nil -} +}, 2, 3, IntTN, IntTN, IntTN) func buildRange(start, stop, step int64) *Array { array := &Array{} @@ -392,20 +263,9 @@ func buildRange(start, stop, step int64) *Array { return array } -func builtinFormat(args ...Object) (Object, error) { - numArgs := len(args) - if numArgs == 0 { - return nil, ErrWrongNumArguments - } - format, ok := args[0].(*String) - if !ok { - return nil, ErrInvalidArgumentType{ - Name: "format", - Expected: "string", - Found: args[0].TypeName(), - } - } - if numArgs == 1 { +var builtinFormat = CheckOptArgs(func(args ...Object) (Object, error) { + format := args[0].(*String) + if len(args) == 1 { // okay to return 'format' directly as String is immutable return format, nil } @@ -414,113 +274,83 @@ func builtinFormat(args ...Object) (Object, error) { return nil, err } return &String{Value: s}, nil -} +}, 1, -1, StringTN, AnyTN) -func builtinCopy(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } +var builtinCopy = CheckAnyArgs(func(args ...Object) (Object, error) { return args[0].Copy(), nil -} +}, 1) -func builtinString(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } +var builtinString = CheckAnyArgs(func(args ...Object) (Object, error) { if _, ok := args[0].(*String); ok { return args[0], nil } - v, ok := ToString(args[0]) - if ok { + v, err := ToString(0, args...) + if err == nil { if len(v) > MaxStringLen { return nil, ErrStringLimit } return &String{Value: v}, nil } - if argsLen == 2 { + if len(args) == 2 { return args[1], nil } return UndefinedValue, nil -} +}, 1, 2) -func builtinInt(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } +var builtinInt = CheckAnyArgs(func(args ...Object) (Object, error) { if _, ok := args[0].(*Int); ok { return args[0], nil } - v, ok := ToInt64(args[0]) - if ok { + v, err := ToInt64(0, args...) + if err == nil { return &Int{Value: v}, nil } - if argsLen == 2 { + if len(args) == 2 { return args[1], nil } return UndefinedValue, nil -} +}, 1, 2) -func builtinFloat(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } +var builtinFloat = CheckAnyArgs(func(args ...Object) (Object, error) { if _, ok := args[0].(*Float); ok { return args[0], nil } - v, ok := ToFloat64(args[0]) - if ok { + v, err := ToFloat64(0, args...) + if err == nil { return &Float{Value: v}, nil } - if argsLen == 2 { + if len(args) == 2 { return args[1], nil } return UndefinedValue, nil -} +}, 1, 2) -func builtinBool(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } +var builtinBool = CheckAnyArgs(func(args ...Object) (Object, error) { if _, ok := args[0].(*Bool); ok { return args[0], nil } - v, ok := ToBool(args[0]) - if ok { - if v { - return TrueValue, nil - } - return FalseValue, nil + v := ToBool(0, args...) + if v { + return TrueValue, nil } - return UndefinedValue, nil -} + return FalseValue, nil +}, 1) -func builtinChar(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } +var builtinChar = CheckAnyArgs(func(args ...Object) (Object, error) { if _, ok := args[0].(*Char); ok { return args[0], nil } - v, ok := ToRune(args[0]) - if ok { + v, err := ToRune(0, args...) + if err == nil { return &Char{Value: v}, nil } - if argsLen == 2 { + if len(args) == 2 { return args[1], nil } return UndefinedValue, nil -} - -func builtinBytes(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } +}, 1, 2) +var builtinBytes = CheckAnyArgs(func(args ...Object) (Object, error) { // bytes(N) => create a new bytes with given size N if n, ok := args[0].(*Int); ok { if n.Value > int64(MaxBytesLen) { @@ -528,113 +358,64 @@ func builtinBytes(args ...Object) (Object, error) { } return &Bytes{Value: make([]byte, int(n.Value))}, nil } - v, ok := ToByteSlice(args[0]) - if ok { + v, err := ToByteSlice(0, args...) + if err == nil { if len(v) > MaxBytesLen { return nil, ErrBytesLimit } return &Bytes{Value: v}, nil } - if argsLen == 2 { + if len(args) == 2 { return args[1], nil } return UndefinedValue, nil -} +}, 1, 2) -func builtinTime(args ...Object) (Object, error) { - argsLen := len(args) - if !(argsLen == 1 || argsLen == 2) { - return nil, ErrWrongNumArguments - } +var builtinTime = CheckAnyArgs(func(args ...Object) (Object, error) { if _, ok := args[0].(*Time); ok { return args[0], nil } - v, ok := ToTime(args[0]) - if ok { + v, err := ToTime(0, args...) + if err == nil { return &Time{Value: v}, nil } - if argsLen == 2 { + if len(args) == 2 { return args[1], nil } return UndefinedValue, nil -} +}, 1, 2) // append(arr, items...) -func builtinAppend(args ...Object) (Object, error) { - if len(args) < 2 { - return nil, ErrWrongNumArguments - } +var builtinAppend = CheckArgs(func(args ...Object) (Object, error) { switch arg := args[0].(type) { case *Array: return &Array{Value: append(arg.Value, args[1:]...)}, nil case *ImmutableArray: return &Array{Value: append(arg.Value, args[1:]...)}, nil default: - return nil, ErrInvalidArgumentType{ - Name: "first", - Expected: "array", - Found: arg.TypeName(), - } + panic("impossible") } -} +}, 2, -1, TNs{ArrayTN, ImmutableArrayTN}, nil) // builtinDelete deletes Map keys // usage: delete(map, "key") // key must be a string -func builtinDelete(args ...Object) (Object, error) { - argsLen := len(args) - if argsLen != 2 { - return nil, ErrWrongNumArguments - } - switch arg := args[0].(type) { - case *Map: - if key, ok := args[1].(*String); ok { - delete(arg.Value, key.Value) - return UndefinedValue, nil - } - return nil, ErrInvalidArgumentType{ - Name: "second", - Expected: "string", - Found: args[1].TypeName(), - } - default: - return nil, ErrInvalidArgumentType{ - Name: "first", - Expected: "map", - Found: arg.TypeName(), - } - } -} +var builtinDelete = CheckStrictArgs(func(args ...Object) (Object, error) { + delete(args[0].(*Map).Value, args[1].(*String).Value) + return UndefinedValue, nil +}, MapTN, StringTN) // builtinSplice deletes and changes given Array, returns deleted items. // usage: // deleted_items := splice(array[,start[,delete_count[,item1[,item2[,...]]]]) -func builtinSplice(args ...Object) (Object, error) { +var builtinSplice = CheckOptArgs(func(args ...Object) (Object, error) { argsLen := len(args) - if argsLen == 0 { - return nil, ErrWrongNumArguments - } - - array, ok := args[0].(*Array) - if !ok { - return nil, ErrInvalidArgumentType{ - Name: "first", - Expected: "array", - Found: args[0].TypeName(), - } - } + array := args[0].(*Array) arrayLen := len(array.Value) var startIdx int if argsLen > 1 { - arg1, ok := args[1].(*Int) - if !ok { - return nil, ErrInvalidArgumentType{ - Name: "second", - Expected: "int", - Found: args[1].TypeName(), - } - } + arg1 := args[1].(*Int) startIdx = int(arg1.Value) if startIdx < 0 || startIdx > arrayLen { return nil, ErrIndexOutOfBounds @@ -643,14 +424,7 @@ func builtinSplice(args ...Object) (Object, error) { delCount := len(array.Value) if argsLen > 2 { - arg2, ok := args[2].(*Int) - if !ok { - return nil, ErrInvalidArgumentType{ - Name: "third", - Expected: "int", - Found: args[2].TypeName(), - } - } + arg2 := args[2].(*Int) delCount = int(arg2.Value) if delCount < 0 { return nil, ErrIndexOutOfBounds @@ -677,4 +451,4 @@ func builtinSplice(args ...Object) (Object, error) { // return deleted items return &Array{Value: deleted}, nil -} +}, 1, -1, ArrayTN, IntTN, IntTN, AnyTN) diff --git a/builtins_test.go b/builtins_test.go index eca2d5bb..a20a725a 100644 --- a/builtins_test.go +++ b/builtins_test.go @@ -33,32 +33,48 @@ func Test_builtinDelete(t *testing.T) { {name: "invalid-arg", args: args{[]tengo.Object{&tengo.String{}, &tengo.String{}}}, wantErr: true, wantedErr: tengo.ErrInvalidArgumentType{ - Name: "first", + Index: 0, Expected: "map", - Found: "string"}, + Actual: "string"}, }, - {name: "no-args", - wantErr: true, wantedErr: tengo.ErrWrongNumArguments}, - {name: "empty-args", args: args{[]tengo.Object{}}, wantErr: true, - wantedErr: tengo.ErrWrongNumArguments, + {name: "no-args", wantErr: true, + wantedErr: tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, }, - {name: "3-args", args: args{[]tengo.Object{ - (*tengo.Map)(nil), (*tengo.String)(nil), (*tengo.String)(nil)}}, - wantErr: true, wantedErr: tengo.ErrWrongNumArguments, + { + name: "empty-args", + args: args{[]tengo.Object{}}, + wantErr: true, + wantedErr: tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, + }, + { + name: "3-args", + args: args{[]tengo.Object{ + (*tengo.Map)(nil), (*tengo.String)(nil), (*tengo.String)(nil)}, + }, + wantErr: true, + wantedErr: tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 3}, + }, + {name: "nil-map-empty-key-int", + args: args{[]tengo.Object{&tengo.Map{}, &tengo.String{}, &tengo.Int{}}}, + wantErr: true, + wantedErr: tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 3}, }, {name: "nil-map-empty-key", args: args{[]tengo.Object{&tengo.Map{}, &tengo.String{}}}, want: tengo.UndefinedValue, }, - {name: "nil-map-nonstr-key", + { + name: "nil-map-nonstr-key", args: args{[]tengo.Object{ - &tengo.Map{}, &tengo.Int{}}}, wantErr: true, - wantedErr: tengo.ErrInvalidArgumentType{ - Name: "second", Expected: "string", Found: "int"}, + &tengo.Map{}, &tengo.Int{}}}, + wantErr: true, + wantedErr: tengo.ErrInvalidArgumentType{Index: 1, Expected: tengo.StringTN, Actual: tengo.IntTN}, }, - {name: "nil-map-no-key", - args: args{[]tengo.Object{&tengo.Map{}}}, wantErr: true, - wantedErr: tengo.ErrWrongNumArguments, + { + name: "nil-map-no-key", + args: args{[]tengo.Object{&tengo.Map{}}}, + wantErr: true, + wantedErr: tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, }, {name: "map-missing-key", args: args{ @@ -152,18 +168,20 @@ func Test_builtinSplice(t *testing.T) { wantedErr error }{ {name: "no args", args: []tengo.Object{}, wantErr: true, - wantedErr: tengo.ErrWrongNumArguments, + wantedErr: tengo.ErrInvalidArgumentCount{ + Min: 1, Max: -1, Actual: 0, + }, }, {name: "invalid args", args: []tengo.Object{&tengo.Map{}}, wantErr: true, wantedErr: tengo.ErrInvalidArgumentType{ - Name: "first", Expected: "array", Found: "map"}, + Index: 0, Expected: tengo.ArrayTN, Actual: tengo.MapTN}, }, {name: "invalid args", args: []tengo.Object{&tengo.Array{}, &tengo.String{}}, wantErr: true, wantedErr: tengo.ErrInvalidArgumentType{ - Name: "second", Expected: "int", Found: "string"}, + Index: 1, Expected: tengo.IntTN, Actual: tengo.StringTN}, }, {name: "negative index", args: []tengo.Object{&tengo.Array{}, &tengo.Int{Value: -1}}, @@ -175,7 +193,7 @@ func Test_builtinSplice(t *testing.T) { &tengo.String{Value: ""}}, wantErr: true, wantedErr: tengo.ErrInvalidArgumentType{ - Name: "third", Expected: "int", Found: "string"}, + Index: 2, Expected: tengo.IntTN, Actual: tengo.StringTN}, }, {name: "negative count", args: []tengo.Object{ @@ -371,33 +389,39 @@ func Test_builtinRange(t *testing.T) { wantedErr error }{ {name: "no args", args: []tengo.Object{}, wantErr: true, - wantedErr: tengo.ErrWrongNumArguments, + wantedErr: tengo.ErrInvalidArgumentCount{ + Min: 2, Max: 3, Actual: 0, + }, }, {name: "single args", args: []tengo.Object{&tengo.Map{}}, - wantErr: true, - wantedErr: tengo.ErrWrongNumArguments, + wantErr: true, + wantedErr: tengo.ErrInvalidArgumentCount{ + Min: 2, Max: 3, Actual: 1, + }, }, {name: "4 args", args: []tengo.Object{&tengo.Map{}, &tengo.String{}, &tengo.String{}, &tengo.String{}}, - wantErr: true, - wantedErr: tengo.ErrWrongNumArguments, + wantErr: true, + wantedErr: tengo.ErrInvalidArgumentCount{ + Min: 2, Max: 3, Actual: 4, + }, }, {name: "invalid start", args: []tengo.Object{&tengo.String{}, &tengo.String{}}, wantErr: true, wantedErr: tengo.ErrInvalidArgumentType{ - Name: "start", Expected: "int", Found: "string"}, + Index: 0, Expected: tengo.IntTN, Actual: tengo.StringTN}, }, {name: "invalid stop", args: []tengo.Object{&tengo.Int{}, &tengo.String{}}, wantErr: true, wantedErr: tengo.ErrInvalidArgumentType{ - Name: "stop", Expected: "int", Found: "string"}, + Index: 1, Expected: tengo.IntTN, Actual: tengo.StringTN}, }, {name: "invalid step", args: []tengo.Object{&tengo.Int{}, &tengo.Int{}, &tengo.String{}}, wantErr: true, wantedErr: tengo.ErrInvalidArgumentType{ - Name: "step", Expected: "int", Found: "string"}, + Index: 2, Expected: tengo.IntTN, Actual: tengo.StringTN}, }, {name: "zero step", args: []tengo.Object{&tengo.Int{}, &tengo.Int{}, &tengo.Int{}}, //must greate than 0 diff --git a/cmd/tengo/main.go b/cmd/tengo/main.go index a8e14987..e7052c81 100644 --- a/cmd/tengo/main.go +++ b/cmd/tengo/main.go @@ -99,10 +99,10 @@ func CompileOnly( modules *tengo.ModuleMap, data []byte, inputFile, outputFile string, -) (err error) { +) error { bytecode, err := compileSrc(modules, data, inputFile) if err != nil { - return + return err } if outputFile == "" { @@ -111,7 +111,7 @@ func CompileOnly( out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, os.ModePerm) if err != nil { - return + return err } defer func() { if err != nil { @@ -123,10 +123,10 @@ func CompileOnly( err = bytecode.Encode(out) if err != nil { - return + return err } fmt.Println(outputFile) - return + return err } // CompileAndRun compiles the source code and executes it. @@ -134,28 +134,26 @@ func CompileAndRun( modules *tengo.ModuleMap, data []byte, inputFile string, -) (err error) { +) error { bytecode, err := compileSrc(modules, data, inputFile) if err != nil { - return + return err } machine := tengo.NewVM(bytecode, nil, -1) - err = machine.Run() - return + return machine.Run() } // RunCompiled reads the compiled binary from file and executes it. -func RunCompiled(modules *tengo.ModuleMap, data []byte) (err error) { +func RunCompiled(modules *tengo.ModuleMap, data []byte) error { bytecode := &tengo.Bytecode{} - err = bytecode.Decode(bytes.NewReader(data), modules) + err := bytecode.Decode(bytes.NewReader(data), modules) if err != nil { - return + return err } machine := tengo.NewVM(bytecode, nil, -1) - err = machine.Run() - return + return machine.Run() } // RunREPL starts REPL. @@ -172,19 +170,19 @@ func RunREPL(modules *tengo.ModuleMap, in io.Reader, out io.Writer) { symbol := symbolTable.Define("__repl_println__") globals[symbol.Index] = &tengo.UserFunction{ Name: "println", - Value: func(args ...tengo.Object) (ret tengo.Object, err error) { + Value: func(args ...tengo.Object) (tengo.Object, error) { var printArgs []interface{} - for _, arg := range args { - if _, isUndefined := arg.(*tengo.Undefined); isUndefined { + for i := range args { + if _, isUndefined := args[i].(*tengo.Undefined); isUndefined { printArgs = append(printArgs, "") } else { - s, _ := tengo.ToString(arg) + s, _ := tengo.ToString(i, args...) printArgs = append(printArgs, s) } } printArgs = append(printArgs, "\n") _, _ = fmt.Print(printArgs...) - return + return nil, nil }, } diff --git a/compiler_test.go b/compiler_test.go index 0fe93121..892bc5eb 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -1296,7 +1296,7 @@ type compileTracer struct { Out []string } -func (o *compileTracer) Write(p []byte) (n int, err error) { +func (o *compileTracer) Write(p []byte) (int, error) { o.Out = append(o.Out, string(p)) return len(p), nil } @@ -1304,7 +1304,7 @@ func (o *compileTracer) Write(p []byte) (n int, err error) { func traceCompile( input string, symbols map[string]tengo.Object, -) (res *tengo.Bytecode, trace []string, err error) { +) (*tengo.Bytecode, []string, error) { fileSet := parser.NewFileSet() file := fileSet.AddFile("test", -1, len(input)) @@ -1322,11 +1322,12 @@ func traceCompile( c := tengo.NewCompiler(file, symTable, nil, nil, tr) parsed, err := p.ParseFile() if err != nil { - return + return nil, nil, err } err = c.Compile(parsed) - res = c.Bytecode() + var trace []string + res := c.Bytecode() res.RemoveDuplicates() { trace = append(trace, fmt.Sprintf("Compiler Trace:\n%s", @@ -1336,10 +1337,7 @@ func traceCompile( trace = append(trace, fmt.Sprintf("Compiled Instructions:\n%s\n", strings.Join(res.FormatInstructions(), "\n"))) } - if err != nil { - return - } - return + return res, trace, err } func objectsArray(o ...tengo.Object) []tengo.Object { diff --git a/docs/objects.md b/docs/objects.md index 4b150601..463ba3d0 100644 --- a/docs/objects.md +++ b/docs/objects.md @@ -238,6 +238,12 @@ type StringArray struct { Value []string } +const StringArrayTN = "string-array" + +func (o *StringArray) TypeName() string { + return StringArrayTN +} + func (o *StringArray) String() string { return strings.Join(o.Value, ", ") } @@ -283,10 +289,6 @@ func (o *StringArray) Copy() tengo.Object { Value: append([]string{}, o.Value...), } } - -func (o *StringArray) TypeName() string { - return "string-array" -} ``` You can use a user type via either @@ -359,27 +361,16 @@ func (o *StringArray) CanCall() bool { return true } -func (o *StringArray) Call(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string", - Found: args[0].TypeName(), - } - } - - for i, v := range o.Value { - if v == s1 { - return &tengo.Int{Value: int64(i)}, nil +func (o *StringArray) Call(args ...tengo.Object) (tengo.Object, error) { + return tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error){ + s1, _ := tengo.ToString(0, args...) + for i, v := range o.Value { + if v == s1 { + return &tengo.Int{Value: int64(i)}, nil + } } - } - - return tengo.UndefinedValue, nil + return tengo.UndefinedValue, nil + }, tengo.StringTN)(args...) } ``` @@ -414,8 +405,10 @@ type StringArrayIterator struct { idx int } +const StringArrayIteratorTN = "string-array-iterator" + func (i *StringArrayIterator) TypeName() string { - return "string-array-iterator" + return StringArrayIteratorTN } func (i *StringArrayIterator) Next() bool { diff --git a/errors.go b/errors.go index 8ef610a3..563e8207 100644 --- a/errors.go +++ b/errors.go @@ -3,6 +3,7 @@ package tengo import ( "errors" "fmt" + "strings" ) var ( @@ -28,9 +29,6 @@ var ( // ErrInvalidOperator represents an error for invalid operator usage. ErrInvalidOperator = errors.New("invalid operator") - // ErrWrongNumArguments represents a wrong number of arguments error. - ErrWrongNumArguments = errors.New("wrong number of arguments") - // ErrBytesLimit represents an error where the size of bytes value exceeds // the limit. ErrBytesLimit = errors.New("exceeding bytes size limit") @@ -54,14 +52,137 @@ var ( ErrInvalidRangeStep = errors.New("range step must be greater than 0") ) -// ErrInvalidArgumentType represents an invalid argument value type error. +// ErrInvalidReturnValueCount represents an invalid return value count error. +type ErrInvalidReturnValueCount struct { + Expected int + Actual int +} + +func (e ErrInvalidReturnValueCount) Error() string { + return fmt.Sprintf("invalid return value count, expected %d, actual %d", + e.Expected, e.Actual) +} + +// ErrInvalidArgumentCount represents an invalid argument count error. +type ErrInvalidArgumentCount struct { + Min int + Max int + Actual int +} + +func (e ErrInvalidArgumentCount) Error() string { + if e.Max < 0 { + return fmt.Sprintf("invalid variadic argument count, expected at least %d, actual %d", + e.Min, e.Actual) + } + if e.Min == e.Max { + return fmt.Sprintf("invalid argument count, expected %d, actual %d", + e.Min, e.Actual) + } + return fmt.Sprintf("invalid argument count, expected between %d and %d, actual %d", + e.Min, e.Max, e.Actual) +} + +// ErrInvalidArgumentType represents an invalid argument type error. type ErrInvalidArgumentType struct { - Name string + Index int Expected string - Found string + Actual string } +// TNs is a shorthand alias for typenames +type TNs = []string + +// AnyTN is a typename when any type can be accepted +const AnyTN = "any" + func (e ErrInvalidArgumentType) Error() string { - return fmt.Sprintf("invalid type for argument '%s': expected %s, found %s", - e.Name, e.Expected, e.Found) + return fmt.Sprintf("invalid type for argument at index %d: expected %s, actual %s", + e.Index, e.Expected, e.Actual) +} + +// CheckArgs wraps a callable variadic function with flexible argument type checking logic +// when an argument might have several allowable types. +func CheckArgs(fn CallableFunc, min, max int, expected ...TNs) CallableFunc { + if min < 0 || (max >= 0 && min > max) { + panic("invalid min max arg count values") + } + if max < 0 && len(expected) < min { + // variadic func + panic("invalid expected len, must be at least min when max < 0") + } + if max >= 0 && (len(expected) < min || len(expected) > max) { + panic("invalid expected len, must be between min and max") + } + return func(actual ...Object) (Object, error) { + if (max >= 0 && len(actual) > max) || + (len(actual) < min) { + return nil, ErrInvalidArgumentCount{ + Min: min, + Max: max, + Actual: len(actual), + } + } + for i := range actual { + var expectedTypes []string + if i > len(expected)-1 { + // variadic args + expectedTypes = expected[len(expected)-1] + } else { + // required args + expectedTypes = expected[i] + } + if len(expectedTypes) == 0 || (len(expectedTypes) == 1 && expectedTypes[0] == AnyTN) { + // any type allowed + continue + } + for j := range expectedTypes { + if expectedTypes[j] == actual[i].TypeName() { + break + } else if j+1 == len(expectedTypes) { + return nil, ErrInvalidArgumentType{ + Index: i, + Expected: strings.Join(expectedTypes, "/"), + Actual: actual[i].TypeName(), + } + } + } + } + return fn(actual...) + } +} + +// CheckOptArgs wraps a callable function with strict argument type checking for optional arguments +func CheckOptArgs(fn CallableFunc, min, max int, expected ...string) CallableFunc { + flexExpected := make([][]string, 0, len(expected)) + for _, tn := range expected { + flexExpected = append(flexExpected, []string{tn}) + } + return CheckArgs(fn, min, max, flexExpected...) +} + +// CheckAnyArgs wraps a callable function with argument count checking, +// this is only useful to functions that use tengo.ToSomeType(i int, args ...Object) (type, error) +// functions as they do more complex type conversions with their own type checking internally. +func CheckAnyArgs(fn CallableFunc, minMax ...int) CallableFunc { + min := 0 + if len(minMax) > 0 { + min = minMax[0] + } + max := min + if len(minMax) > 1 { + max = minMax[1] + } + var expected [][]string + if max < 0 { + expected = make([][]string, min+1) + } else { + expected = make([][]string, max) + } + return CheckArgs(fn, min, max, expected...) +} + +// CheckStrictArgs wraps a callable function with strict argument type checking logic +func CheckStrictArgs(fn CallableFunc, expected ...string) CallableFunc { + return CheckOptArgs(fn, len(expected), len(expected), expected...) } diff --git a/examples/interoperability/main.go b/examples/interoperability/main.go index b62e2bff..8a4880c2 100644 --- a/examples/interoperability/main.go +++ b/examples/interoperability/main.go @@ -48,9 +48,12 @@ type GoProxy struct { mtx sync.Mutex } +// GoProxyTN is the go proxy type name +const GoProxyTN = "go-proxy" + // TypeName returns type name. func (mod *GoProxy) TypeName() string { - return "GoProxy" + return GoProxyTN } func (mod *GoProxy) String() string { @@ -84,72 +87,70 @@ func (mod *GoProxy) next(args ...tengo.Object) (tengo.Object, error) { } func (mod *GoProxy) register(args ...tengo.Object) (tengo.Object, error) { - if len(args) == 0 { - return nil, tengo.ErrWrongNumArguments - } - mod.mtx.Lock() - defer mod.mtx.Unlock() - - switch v := args[0].(type) { - case *tengo.Map: - mod.callbacks = v.Value - case *tengo.ImmutableMap: - mod.callbacks = v.Value - default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "map", - Found: args[0].TypeName(), + return tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { + mod.mtx.Lock() + defer mod.mtx.Unlock() + switch v := args[0].(type) { + case *tengo.Map: + mod.callbacks = v.Value + case *tengo.ImmutableMap: + mod.callbacks = v.Value } - } - return tengo.UndefinedValue, nil + return tengo.UndefinedValue, nil + }, 1, 1, tengo.TNs{tengo.MapTN, tengo.ImmutableMapTN})(args...) } func (mod *GoProxy) args(args ...tengo.Object) (tengo.Object, error) { - mod.mtx.Lock() - defer mod.mtx.Unlock() + return tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error) { + mod.mtx.Lock() + defer mod.mtx.Unlock() - if mod.tasks.Len() == 0 { - return tengo.UndefinedValue, nil - } - el := mod.tasks.Front() - callArgs, ok := el.Value.(*CallArgs) - if !ok || callArgs == nil { - return nil, errors.New("invalid call arguments") - } - mod.tasks.Remove(el) - f, ok := mod.callbacks[callArgs.Func] - if !ok { - return tengo.UndefinedValue, nil - } - compiledFunc, ok := f.(*tengo.CompiledFunction) - if !ok { - return tengo.UndefinedValue, nil - } - params := callArgs.Params - if params == nil { - params = make([]tengo.Object, 0) - } - // callable.VarArgs implementation is omitted. - return &tengo.ImmutableMap{ - Value: map[string]tengo.Object{ - "result": &tengo.UserFunction{ - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) > 0 { - callArgs.Result <- args[0] + if mod.tasks.Len() == 0 { + return tengo.UndefinedValue, nil + } + el := mod.tasks.Front() + callArgs, ok := el.Value.(*CallArgs) + if !ok || callArgs == nil { + return nil, errors.New("invalid call arguments") + } + mod.tasks.Remove(el) + f, ok := mod.callbacks[callArgs.Func] + if !ok { + return tengo.UndefinedValue, nil + } + compiledFunc, ok := f.(*tengo.CompiledFunction) + if !ok { + return tengo.UndefinedValue, nil + } + params := callArgs.Params + if params == nil { + params = make([]tengo.Object, 0) + } + // callable.VarArgs implementation is omitted. + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "result": &tengo.UserFunction{ + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) > 0 { + callArgs.Result <- args[0] + return tengo.UndefinedValue, nil + } + callArgs.Result <- &tengo.Error{ + Value: &tengo.String{ + Value: tengo.ErrInvalidArgumentCount{ + Min: 1, + Max: -1, + Actual: 0, + }.Error()}, + } return tengo.UndefinedValue, nil - } - callArgs.Result <- &tengo.Error{ - Value: &tengo.String{ - Value: tengo.ErrWrongNumArguments.Error()}, - } - return tengo.UndefinedValue, nil - }}, - "num_params": &tengo.Int{Value: int64(compiledFunc.NumParameters)}, - "callable": compiledFunc, - "params": &tengo.Array{Value: params}, - }, - }, nil + }}, + "num_params": &tengo.Int{Value: int64(compiledFunc.NumParameters)}, + "callable": compiledFunc, + "params": &tengo.Array{Value: params}, + }, + }, nil + })(args...) } // ProxySource is a tengo script to handle bidirectional arguments flow between diff --git a/formatter.go b/formatter.go index 0dbf71c6..6b78bc1a 100644 --- a/formatter.go +++ b/formatter.go @@ -740,24 +740,24 @@ func (p *pp) Flag(b int) bool { // Implement Write so we can call Fprintf on a pp (through State), for // recursive use in custom verbs. -func (p *pp) Write(b []byte) (ret int, err error) { +func (p *pp) Write(b []byte) (int, error) { p.buf.Write(b) return len(b), nil } // Implement WriteString so that we can call io.WriteString // on a pp (through state), for efficiency. -func (p *pp) WriteString(s string) (ret int, err error) { +func (p *pp) WriteString(s string) (int, error) { p.buf.WriteString(s) return len(s), nil } -func (p *pp) WriteRune(r rune) (ret int, err error) { +func (p *pp) WriteRune(r rune) (int, error) { p.buf.WriteRune(r) return utf8.RuneLen(r), nil } -func (p *pp) WriteSingleByte(c byte) (ret int, err error) { +func (p *pp) WriteSingleByte(c byte) (int, error) { p.buf.WriteSingleByte(c) return 1, nil } @@ -968,19 +968,21 @@ func (p *pp) printArg(arg Object, verb rune) { // intFromArg gets the argNumth element of a. On return, isInt reports whether // the argument has integer type. -func intFromArg(a []Object, argNum int) (num int, isInt bool, newArgNum int) { - newArgNum = argNum +func intFromArg(a []Object, argNum int) (int, bool, int) { + newArgNum := argNum if argNum < len(a) { var num64 int64 - num64, isInt = ToInt64(a[argNum]) - num = int(num64) + num64, err := ToInt64(argNum, a...) + isInt := err == nil + num := int(num64) newArgNum = argNum + 1 if tooLarge(num) { num = 0 isInt = false } + return num, isInt, newArgNum } - return + return 0, false, newArgNum } // parseArgNumber returns the value of the bracketed number, minus 1 diff --git a/iterator.go b/iterator.go index 13adbbab..a158b242 100644 --- a/iterator.go +++ b/iterator.go @@ -22,13 +22,16 @@ type ArrayIterator struct { l int } +// ArrayIteratorTN is the array iterator type name +const ArrayIteratorTN = "array-iterator" + // TypeName returns the name of the type. func (i *ArrayIterator) TypeName() string { - return "array-iterator" + return ArrayIteratorTN } func (i *ArrayIterator) String() string { - return "" + return TypeString(ArrayIteratorTN) } // IsFalsy returns true if the value of the type is falsy. @@ -63,6 +66,16 @@ func (i *ArrayIterator) Value() Object { return i.v[i.i-1] } +// HasLen returns whether the Object has a length value. +func (i *ArrayIterator) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (i *ArrayIterator) Len() int { + return i.l +} + // BytesIterator represents an iterator for a string. type BytesIterator struct { ObjectImpl @@ -71,13 +84,16 @@ type BytesIterator struct { l int } +// BytesIteratorTN is the bytes iterator type name +const BytesIteratorTN = "bytes-iterator" + // TypeName returns the name of the type. func (i *BytesIterator) TypeName() string { - return "bytes-iterator" + return BytesIteratorTN } func (i *BytesIterator) String() string { - return "" + return TypeString(BytesIteratorTN) } // Equals returns true if the value of the type is equal to the value of @@ -107,6 +123,16 @@ func (i *BytesIterator) Value() Object { return &Int{Value: int64(i.v[i.i-1])} } +// HasLen returns whether the Object has a length value. +func (i *BytesIterator) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (i *BytesIterator) Len() int { + return i.l +} + // MapIterator represents an iterator for the map. type MapIterator struct { ObjectImpl @@ -116,13 +142,16 @@ type MapIterator struct { l int } +// MapIteratorTN is the map iterator type name +const MapIteratorTN = "map-iterator" + // TypeName returns the name of the type. func (i *MapIterator) TypeName() string { - return "map-iterator" + return MapIteratorTN } func (i *MapIterator) String() string { - return "" + return TypeString(MapIteratorTN) } // IsFalsy returns true if the value of the type is falsy. @@ -159,6 +188,16 @@ func (i *MapIterator) Value() Object { return i.v[k] } +// HasLen returns whether the Object has a length value. +func (i *MapIterator) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (i *MapIterator) Len() int { + return i.l +} + // StringIterator represents an iterator for a string. type StringIterator struct { ObjectImpl @@ -167,13 +206,16 @@ type StringIterator struct { l int } +// StringIteratorTN is the string iterator type name +const StringIteratorTN = "string-iterator" + // TypeName returns the name of the type. func (i *StringIterator) TypeName() string { - return "string-iterator" + return StringIteratorTN } func (i *StringIterator) String() string { - return "" + return TypeString(StringIteratorTN) } // IsFalsy returns true if the value of the type is falsy. @@ -207,3 +249,13 @@ func (i *StringIterator) Key() Object { func (i *StringIterator) Value() Object { return &Char{Value: i.v[i.i-1]} } + +// HasLen returns whether the Object has a length value. +func (i *StringIterator) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (i *StringIterator) Len() int { + return i.l +} diff --git a/objects.go b/objects.go index 30913db5..625c1634 100644 --- a/objects.go +++ b/objects.go @@ -77,6 +77,18 @@ type Object interface { // CanCall should return whether the Object can be Called. CanCall() bool + + // HasLen returns whether the object has a length value. + HasLen() bool + + // Len returns the length value of the object. + Len() int +} + +// TypeString converts a type name into the standard +// type string be wrapping it in angle brackets. +func TypeString(typeStr string) string { + return fmt.Sprintf("<%s>", typeStr) } // ObjectImpl represents a default Object Implementation. To defined a new @@ -118,12 +130,12 @@ func (o *ObjectImpl) Equals(x Object) bool { } // IndexGet returns an element at a given index. -func (o *ObjectImpl) IndexGet(_ Object) (res Object, err error) { +func (o *ObjectImpl) IndexGet(_ Object) (Object, error) { return nil, ErrNotIndexable } // IndexSet sets an element at a given index. -func (o *ObjectImpl) IndexSet(_, _ Object) (err error) { +func (o *ObjectImpl) IndexSet(_, _ Object) error { return ErrNotIndexAssignable } @@ -139,7 +151,7 @@ func (o *ObjectImpl) CanIterate() bool { // Call takes an arbitrary number of arguments and returns a return value // and/or an error. -func (o *ObjectImpl) Call(_ ...Object) (ret Object, err error) { +func (o *ObjectImpl) Call(_ ...Object) (Object, error) { return nil, nil } @@ -148,15 +160,28 @@ func (o *ObjectImpl) CanCall() bool { return false } +// HasLen returns whether the object has a length value. +func (o *ObjectImpl) HasLen() bool { + return false +} + +// Len returns the length value of the object. +func (o *ObjectImpl) Len() int { + return 0 +} + // Array represents an array of objects. type Array struct { ObjectImpl Value []Object } +// ArrayTN is the array type name +const ArrayTN = "array" + // TypeName returns the name of the type. func (o *Array) TypeName() string { - return "array" + return ArrayTN } func (o *Array) String() string { @@ -220,31 +245,26 @@ func (o *Array) Equals(x Object) bool { } // IndexGet returns an element at a given index. -func (o *Array) IndexGet(index Object) (res Object, err error) { +func (o *Array) IndexGet(index Object) (Object, error) { intIdx, ok := index.(*Int) if !ok { - err = ErrInvalidIndexType - return + return nil, ErrInvalidIndexType } idxVal := int(intIdx.Value) if idxVal < 0 || idxVal >= len(o.Value) { - res = UndefinedValue - return + return UndefinedValue, nil } - res = o.Value[idxVal] - return + return o.Value[idxVal], nil } // IndexSet sets an element at a given index. -func (o *Array) IndexSet(index, value Object) (err error) { - intIdx, ok := ToInt(index) - if !ok { - err = ErrInvalidIndexType - return +func (o *Array) IndexSet(index, value Object) error { + intIdx, err := ToInt(0, index) + if err != nil { + return err } if intIdx < 0 || intIdx >= len(o.Value) { - err = ErrIndexOutOfBounds - return + return ErrIndexOutOfBounds } o.Value[intIdx] = value return nil @@ -263,6 +283,16 @@ func (o *Array) CanIterate() bool { return true } +// HasLen returns whether the Object has a length value. +func (o *Array) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (o *Array) Len() int { + return len(o.Value) +} + // Bool represents a boolean value. type Bool struct { ObjectImpl @@ -272,6 +302,14 @@ type Bool struct { value bool } +// BoolTN is the bool type name +const BoolTN = "bool" + +// TypeName returns the name of the type. +func (o *Bool) TypeName() string { + return BoolTN +} + func (o *Bool) String() string { if o.value { return "true" @@ -280,11 +318,6 @@ func (o *Bool) String() string { return "false" } -// TypeName returns the name of the type. -func (o *Bool) TypeName() string { - return "bool" -} - // Copy returns a copy of the type. func (o *Bool) Copy() Object { return o @@ -302,19 +335,17 @@ func (o *Bool) Equals(x Object) bool { } // GobDecode decodes bool value from input bytes. -func (o *Bool) GobDecode(b []byte) (err error) { +func (o *Bool) GobDecode(b []byte) error { o.value = b[0] == 1 - return + return nil } // GobEncode encodes bool values into bytes. -func (o *Bool) GobEncode() (b []byte, err error) { +func (o *Bool) GobEncode() ([]byte, error) { if o.value { - b = []byte{1} - } else { - b = []byte{0} + return []byte{1}, nil } - return + return []byte{0}, nil } // BuiltinFunction represents a builtin function. @@ -330,7 +361,7 @@ func (o *BuiltinFunction) TypeName() string { } func (o *BuiltinFunction) String() string { - return "" + return TypeString(o.TypeName()) } // Copy returns a copy of the type. @@ -380,13 +411,16 @@ type Bytes struct { Value []byte } -func (o *Bytes) String() string { - return string(o.Value) -} +// BytesTN is the bytes type name +const BytesTN = "bytes" // TypeName returns the name of the type. func (o *Bytes) TypeName() string { - return "bytes" + return BytesTN +} + +func (o *Bytes) String() string { + return string(o.Value) } // BinaryOp returns another object that is the result of a given binary @@ -426,19 +460,16 @@ func (o *Bytes) Equals(x Object) bool { } // IndexGet returns an element (as Int) at a given index. -func (o *Bytes) IndexGet(index Object) (res Object, err error) { +func (o *Bytes) IndexGet(index Object) (Object, error) { intIdx, ok := index.(*Int) if !ok { - err = ErrInvalidIndexType - return + return nil, ErrInvalidIndexType } idxVal := int(intIdx.Value) if idxVal < 0 || idxVal >= len(o.Value) { - res = UndefinedValue - return + return UndefinedValue, nil } - res = &Int{Value: int64(o.Value[idxVal])} - return + return &Int{Value: int64(o.Value[idxVal])}, nil } // Iterate creates a bytes iterator. @@ -454,19 +485,32 @@ func (o *Bytes) CanIterate() bool { return true } +// HasLen returns whether the Object has a length value. +func (o *Bytes) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (o *Bytes) Len() int { + return len(o.Value) +} + // Char represents a character value. type Char struct { ObjectImpl Value rune } -func (o *Char) String() string { - return string(o.Value) -} +// CharTN is the char type name +const CharTN = "char" // TypeName returns the name of the type. func (o *Char) TypeName() string { - return "char" + return CharTN +} + +func (o *Char) String() string { + return string(o.Value) } // BinaryOp returns another object that is the result of a given binary @@ -578,13 +622,16 @@ type CompiledFunction struct { Free []*ObjectPtr } +// CompiledFunctionTN is the compiled function type name +const CompiledFunctionTN = "compiled-function" + // TypeName returns the name of the type. func (o *CompiledFunction) TypeName() string { - return "compiled-function" + return CompiledFunctionTN } func (o *CompiledFunction) String() string { - return "" + return TypeString(CompiledFunctionTN) } // Copy returns a copy of the type. @@ -626,9 +673,12 @@ type Error struct { Value Object } +// ErrorTN is the error type name +const ErrorTN = "error" + // TypeName returns the name of the type. func (o *Error) TypeName() string { - return "error" + return ErrorTN } func (o *Error) String() string { @@ -655,13 +705,11 @@ func (o *Error) Equals(x Object) bool { } // IndexGet returns an element at a given index. -func (o *Error) IndexGet(index Object) (res Object, err error) { - if strIdx, _ := ToString(index); strIdx != "value" { - err = ErrInvalidIndexOnError - return +func (o *Error) IndexGet(index Object) (Object, error) { + if strIdx, _ := ToString(0, index); strIdx != "value" { + return nil, ErrInvalidIndexOnError } - res = o.Value - return + return o.Value, nil } // Float represents a floating point number value. @@ -670,13 +718,16 @@ type Float struct { Value float64 } -func (o *Float) String() string { - return strconv.FormatFloat(o.Value, 'f', -1, 64) -} +// FloatTN is the float type name +const FloatTN = "float" // TypeName returns the name of the type. func (o *Float) TypeName() string { - return "float" + return FloatTN +} + +func (o *Float) String() string { + return strconv.FormatFloat(o.Value, 'f', -1, 64) } // BinaryOp returns another object that is the result of a given binary @@ -807,9 +858,12 @@ type ImmutableArray struct { Value []Object } +// ImmutableArrayTN is the immutable array type name +const ImmutableArrayTN = "immutable-array" + // TypeName returns the name of the type. func (o *ImmutableArray) TypeName() string { - return "immutable-array" + return ImmutableArrayTN } func (o *ImmutableArray) String() string { @@ -870,19 +924,16 @@ func (o *ImmutableArray) Equals(x Object) bool { } // IndexGet returns an element at a given index. -func (o *ImmutableArray) IndexGet(index Object) (res Object, err error) { +func (o *ImmutableArray) IndexGet(index Object) (Object, error) { intIdx, ok := index.(*Int) if !ok { - err = ErrInvalidIndexType - return + return nil, ErrInvalidIndexType } idxVal := int(intIdx.Value) if idxVal < 0 || idxVal >= len(o.Value) { - res = UndefinedValue - return + return UndefinedValue, nil } - res = o.Value[idxVal] - return + return o.Value[idxVal], nil } // Iterate creates an array iterator. @@ -898,15 +949,28 @@ func (o *ImmutableArray) CanIterate() bool { return true } +// HasLen returns whether the Object has a length value. +func (o *ImmutableArray) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (o *ImmutableArray) Len() int { + return len(o.Value) +} + // ImmutableMap represents an immutable map object. type ImmutableMap struct { ObjectImpl Value map[string]Object } +// ImmutableMapTN is the immutable map type name +const ImmutableMapTN = "immutable-map" + // TypeName returns the name of the type. func (o *ImmutableMap) TypeName() string { - return "immutable-map" + return ImmutableMapTN } func (o *ImmutableMap) String() string { @@ -932,17 +996,16 @@ func (o *ImmutableMap) IsFalsy() bool { } // IndexGet returns the value for the given key. -func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) { - strIdx, ok := ToString(index) - if !ok { - err = ErrInvalidIndexType - return +func (o *ImmutableMap) IndexGet(index Object) (Object, error) { + strIdx, err := ToString(0, index) + if err != nil { + return nil, err } - res, ok = o.Value[strIdx] + res, ok := o.Value[strIdx] if !ok { - res = UndefinedValue + return UndefinedValue, nil } - return + return res, nil } // Equals returns true if the value of the type is equal to the value of @@ -987,19 +1050,32 @@ func (o *ImmutableMap) CanIterate() bool { return true } +// HasLen returns whether the Object has a length value. +func (o *ImmutableMap) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (o *ImmutableMap) Len() int { + return len(o.Value) +} + // Int represents an integer value. type Int struct { ObjectImpl Value int64 } +// IntTN is the int type name +const IntTN = "int" + func (o *Int) String() string { return strconv.FormatInt(o.Value, 10) } // TypeName returns the name of the type. func (o *Int) TypeName() string { - return "int" + return IntTN } // BinaryOp returns another object that is the result of a given binary @@ -1183,9 +1259,12 @@ type Map struct { Value map[string]Object } +// MapTN is the map type name +const MapTN = "map" + // TypeName returns the name of the type. func (o *Map) TypeName() string { - return "map" + return MapTN } func (o *Map) String() string { @@ -1235,25 +1314,23 @@ func (o *Map) Equals(x Object) bool { } // IndexGet returns the value for the given key. -func (o *Map) IndexGet(index Object) (res Object, err error) { - strIdx, ok := ToString(index) - if !ok { - err = ErrInvalidIndexType - return +func (o *Map) IndexGet(index Object) (Object, error) { + strIdx, err := ToString(0, index) + if err != nil { + return nil, err } - res, ok = o.Value[strIdx] + res, ok := o.Value[strIdx] if !ok { - res = UndefinedValue + return UndefinedValue, nil } - return + return res, nil } // IndexSet sets the value for the given key. -func (o *Map) IndexSet(index, value Object) (err error) { - strIdx, ok := ToString(index) - if !ok { - err = ErrInvalidIndexType - return +func (o *Map) IndexSet(index, value Object) error { + strIdx, err := ToString(0, index) + if err != nil { + return err } o.Value[strIdx] = value return nil @@ -1277,19 +1354,32 @@ func (o *Map) CanIterate() bool { return true } +// HasLen returns whether the Object has a length value. +func (o *Map) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (o *Map) Len() int { + return len(o.Value) +} + // ObjectPtr represents a free variable. type ObjectPtr struct { ObjectImpl Value *Object } -func (o *ObjectPtr) String() string { - return "free-var" -} +// ObjectPtrTN is the object ptr type name +const ObjectPtrTN = "free-var" // TypeName returns the name of the type. func (o *ObjectPtr) TypeName() string { - return "" + return ObjectPtrTN +} + +func (o *ObjectPtr) String() string { + return TypeString(ObjectPtrTN) } // Copy returns a copy of the type. @@ -1315,9 +1405,12 @@ type String struct { runeStr []rune } +// StringTN is the string type name +const StringTN = "string" + // TypeName returns the name of the type. func (o *String) TypeName() string { - return "string" + return StringTN } func (o *String) String() string { @@ -1399,22 +1492,19 @@ func (o *String) Equals(x Object) bool { } // IndexGet returns a character at a given index. -func (o *String) IndexGet(index Object) (res Object, err error) { +func (o *String) IndexGet(index Object) (Object, error) { intIdx, ok := index.(*Int) if !ok { - err = ErrInvalidIndexType - return + return nil, ErrInvalidIndexType } idxVal := int(intIdx.Value) if o.runeStr == nil { o.runeStr = []rune(o.Value) } if idxVal < 0 || idxVal >= len(o.runeStr) { - res = UndefinedValue - return + return UndefinedValue, nil } - res = &Char{Value: o.runeStr[idxVal]} - return + return &Char{Value: o.runeStr[idxVal]}, nil } // Iterate creates a string iterator. @@ -1433,19 +1523,32 @@ func (o *String) CanIterate() bool { return true } +// HasLen returns whether the Object has a length value. +func (o *String) HasLen() bool { + return true +} + +// Len returns the Objects length value. +func (o *String) Len() int { + return len(o.Value) +} + // Time represents a time value. type Time struct { ObjectImpl Value time.Time } -func (o *Time) String() string { - return o.Value.String() -} +// TimeTN is the time type name +const TimeTN = "time" // TypeName returns the name of the type. func (o *Time) TypeName() string { - return "time" + return TimeTN +} + +func (o *Time) String() string { + return o.Value.String() } // BinaryOp returns another object that is the result of a given binary @@ -1519,13 +1622,16 @@ type Undefined struct { ObjectImpl } +// UndefinedTN is the undefined type name +const UndefinedTN = "undefined" + // TypeName returns the name of the type. func (o *Undefined) TypeName() string { - return "undefined" + return UndefinedTN } func (o *Undefined) String() string { - return "" + return TypeString(UndefinedTN) } // Copy returns a copy of the type. @@ -1582,13 +1688,16 @@ type UserFunction struct { EncodingID string } +// UserFunctionTN is the user function type name. +const UserFunctionTN = "user-function" + // TypeName returns the name of the type. func (o *UserFunction) TypeName() string { - return "user-function:" + o.Name + return UserFunctionTN } func (o *UserFunction) String() string { - return "" + return TypeString(o.TypeName() + ": " + o.Name) } // Copy returns a copy of the type. diff --git a/objects_test.go b/objects_test.go index 29a0b7d3..f8b85c68 100644 --- a/objects_test.go +++ b/objects_test.go @@ -32,7 +32,7 @@ func TestObject_TypeName(t *testing.T) { o = &tengo.BuiltinFunction{Name: "fn"} require.Equal(t, "builtin-function:fn", o.TypeName()) o = &tengo.UserFunction{Name: "fn"} - require.Equal(t, "user-function:fn", o.TypeName()) + require.Equal(t, "user-function", o.TypeName()) o = &tengo.CompiledFunction{} require.Equal(t, "compiled-function", o.TypeName()) o = &tengo.Undefined{} diff --git a/parser/parser_test.go b/parser/parser_test.go index 4ee6ad7a..34309270 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1512,7 +1512,7 @@ type parseTracer struct { out []string } -func (o *parseTracer) Write(p []byte) (n int, err error) { +func (o *parseTracer) Write(p []byte) (int, error) { o.out = append(o.out, string(p)) return len(p), nil } @@ -2082,7 +2082,7 @@ func parseSource( filename string, src []byte, trace io.Writer, -) (res *File, err error) { +) (*File, error) { fileSet := NewFileSet() file := fileSet.AddFile(filename, -1, len(src)) diff --git a/script.go b/script.go index 46e48029..299ecd9f 100644 --- a/script.go +++ b/script.go @@ -143,25 +143,25 @@ func (s *Script) Compile() (*Compiled, error) { // Run compiles and runs the scripts. Use returned compiled object to access // global variables. -func (s *Script) Run() (compiled *Compiled, err error) { - compiled, err = s.Compile() +func (s *Script) Run() (*Compiled, error) { + compiled, err := s.Compile() if err != nil { - return + return compiled, err } err = compiled.Run() - return + return compiled, err } // RunContext is like Run but includes a context. func (s *Script) RunContext( ctx context.Context, -) (compiled *Compiled, err error) { - compiled, err = s.Compile() +) (*Compiled, error) { + compiled, err := s.Compile() if err != nil { - return + return compiled, err } err = compiled.RunContext(ctx) - return + return compiled, err } func (s *Script) prepCompile() ( @@ -212,7 +212,7 @@ func (c *Compiled) Run() error { } // RunContext is like Run but includes a context. -func (c *Compiled) RunContext(ctx context.Context) (err error) { +func (c *Compiled) RunContext(ctx context.Context) error { c.lock.Lock() defer c.lock.Unlock() @@ -226,10 +226,10 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) { case <-ctx.Done(): v.Abort() <-ch - err = ctx.Err() - case err = <-ch: + return ctx.Err() + case err := <-ch: + return err } - return } // Clone creates a new copy of Compiled. Cloned copies are safe for concurrent diff --git a/script_test.go b/script_test.go index 5e8f3630..73671c4d 100644 --- a/script_test.go +++ b/script_test.go @@ -21,7 +21,7 @@ func TestScript_Add(t *testing.T) { require.NoError(t, s.Add("b", 5)) // b = 5 require.NoError(t, s.Add("b", "foo")) // b = "foo" (re-define before compilation) require.NoError(t, s.Add("test", - func(args ...tengo.Object) (ret tengo.Object, err error) { + func(args ...tengo.Object) (tengo.Object, error) { if len(args) > 0 { switch arg := args[0].(type) { case *tengo.Int: @@ -173,12 +173,11 @@ e := mod1.double(s) mod1 := map[string]tengo.Object{ "double": &tengo.UserFunction{ Value: func(args ...tengo.Object) ( - ret tengo.Object, - err error, + tengo.Object, + error, ) { - arg0, _ := tengo.ToInt64(args[0]) - ret = &tengo.Int{Value: arg0 * 2} - return + arg0, err := tengo.ToInt64(0, args...) + return &tengo.Int{Value: arg0 * 2}, err }, }, } @@ -231,8 +230,10 @@ type Counter struct { value int64 } +const CounterTN = "counter" + func (o *Counter) TypeName() string { - return "counter" + return CounterTN } func (o *Counter) String() string { @@ -352,7 +353,7 @@ func TestScriptSourceModule(t *testing.T) { "title": &tengo.UserFunction{ Name: "title", Value: func(args ...tengo.Object) (tengo.Object, error) { - s, _ := tengo.ToString(args[0]) + s, _ := tengo.ToString(0, args...) return &tengo.String{Value: strings.Title(s)}, nil }}, }) diff --git a/stdlib/fmt.go b/stdlib/fmt.go index 9945277f..624b3236 100644 --- a/stdlib/fmt.go +++ b/stdlib/fmt.go @@ -13,7 +13,7 @@ var fmtModule = map[string]tengo.Object{ "sprintf": &tengo.UserFunction{Name: "sprintf", Value: fmtSprintf}, } -func fmtPrint(args ...tengo.Object) (ret tengo.Object, err error) { +func fmtPrint(args ...tengo.Object) (tengo.Object, error) { printArgs, err := getPrintArgs(args...) if err != nil { return nil, err @@ -22,20 +22,10 @@ func fmtPrint(args ...tengo.Object) (ret tengo.Object, err error) { return nil, nil } -func fmtPrintf(args ...tengo.Object) (ret tengo.Object, err error) { +var fmtPrintf = tengo.CheckOptArgs(func(args ...tengo.Object) (tengo.Object, error) { numArgs := len(args) - if numArgs == 0 { - return nil, tengo.ErrWrongNumArguments - } - format, ok := args[0].(*tengo.String) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "format", - Expected: "string", - Found: args[0].TypeName(), - } - } + format := args[0].(*tengo.String) if numArgs == 1 { fmt.Print(format) return nil, nil @@ -47,9 +37,9 @@ func fmtPrintf(args ...tengo.Object) (ret tengo.Object, err error) { } fmt.Print(s) return nil, nil -} +}, 1, -1, tengo.StringTN, tengo.AnyTN) -func fmtPrintln(args ...tengo.Object) (ret tengo.Object, err error) { +func fmtPrintln(args ...tengo.Object) (tengo.Object, error) { printArgs, err := getPrintArgs(args...) if err != nil { return nil, err @@ -59,20 +49,11 @@ func fmtPrintln(args ...tengo.Object) (ret tengo.Object, err error) { return nil, nil } -func fmtSprintf(args ...tengo.Object) (ret tengo.Object, err error) { +var fmtSprintf = tengo.CheckOptArgs(func(args ...tengo.Object) (tengo.Object, error) { numArgs := len(args) - if numArgs == 0 { - return nil, tengo.ErrWrongNumArguments - } - format, ok := args[0].(*tengo.String) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "format", - Expected: "string", - Found: args[0].TypeName(), - } - } + format := args[0].(*tengo.String) + if numArgs == 1 { // okay to return 'format' directly as String is immutable return format, nil @@ -82,13 +63,13 @@ func fmtSprintf(args ...tengo.Object) (ret tengo.Object, err error) { return nil, err } return &tengo.String{Value: s}, nil -} +}, 1, -1, tengo.StringTN, tengo.AnyTN) func getPrintArgs(args ...tengo.Object) ([]interface{}, error) { var printArgs []interface{} l := 0 - for _, arg := range args { - s, _ := tengo.ToString(arg) + for i := range args { + s, _ := tengo.ToString(i, args...) slen := len(s) // make sure length does not exceed the limit if l+slen > tengo.MaxStringLen { diff --git a/stdlib/func_typedefs.go b/stdlib/func_typedefs.go index fdac933c..2213ec9a 100644 --- a/stdlib/func_typedefs.go +++ b/stdlib/func_typedefs.go @@ -2,1048 +2,566 @@ package stdlib import ( "fmt" + "regexp" + "strings" "github.com/d5/tengo/v2" ) +var typeCodeSplitter = regexp.MustCompile(`[A-Z][^A-Z]*`) + +type converter struct { + arg func(i int, args ...tengo.Object) (interface{}, error) + ret func(interface{}) (tengo.Object, error) +} + +var typeCodeConverters = map[string]*converter{ + // string + "S": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToString(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + if len(i.(string)) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.String{Value: i.(string)}, nil + }, + }, + "Ss": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToStringSlice(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + arr := &tengo.Array{} + for _, elem := range i.([]string) { + if len(elem) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + arr.Value = append(arr.Value, &tengo.String{Value: elem}) + } + return arr, nil + }, + }, + // int + "I": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToInt(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + return &tengo.Int{Value: int64(i.(int))}, nil + }, + }, + "Is": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToIntSlice(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + arr := &tengo.Array{} + for _, elem := range i.([]int) { + arr.Value = append(arr.Value, &tengo.Int{Value: int64(elem)}) + } + return arr, nil + }, + }, + // int64 + "I64": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToInt64(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + return &tengo.Int{Value: i.(int64)}, nil + }, + }, + "I64s": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToInt64Slice(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + arr := &tengo.Array{} + for _, elem := range i.([]int64) { + arr.Value = append(arr.Value, &tengo.Int{Value: elem}) + } + return arr, nil + }, + }, + // float64 + "F": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToFloat64(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + return &tengo.Float{Value: i.(float64)}, nil + }, + }, + "Fs": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToFloat64Slice(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + arr := &tengo.Array{} + for _, elem := range i.([]float64) { + arr.Value = append(arr.Value, &tengo.Float{Value: elem}) + } + return arr, nil + }, + }, + // bool + "B": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToBool(i, args...), nil + }, + ret: func(i interface{}) (tengo.Object, error) { + if i.(bool) { + return tengo.TrueValue, nil + } + return tengo.FalseValue, nil + }, + }, + // byte + "Y": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + return tengo.ToByteSlice(i, args...) + }, + ret: func(i interface{}) (tengo.Object, error) { + if len(i.([]byte)) > tengo.MaxBytesLen { + return nil, tengo.ErrBytesLimit + } + return &tengo.Bytes{Value: i.([]byte)}, nil + }, + }, + // error + "E": { + arg: func(i int, args ...tengo.Object) (interface{}, error) { + panic("not implemented") + }, + ret: func(i interface{}) (tengo.Object, error) { + if i == nil { + return wrapError(nil), nil + } + return wrapError(i.(error)), nil + }, + }, +} + +func base( + fnApplier func(args ...interface{}) []interface{}, fnSig string) tengo.CallableFunc { + + sigParts := strings.Split(fnSig, "-") + if len(sigParts) != 2 { + panic("invalid function signature, missing '-' arg values. return values splitter token") + } + + argTypeCodes := typeCodeSplitter.FindAllString(sigParts[0], -1) + argCount := len(argTypeCodes) + for _, tc := range argTypeCodes { + if _, exists := typeCodeConverters[tc]; !exists { + panic(fmt.Errorf("invalid function signature, unexpected arg type code %s", tc)) + } + } + + retTypeCodes := typeCodeSplitter.FindAllString(sigParts[1], -1) + retCount := len(retTypeCodes) + if retCount > 2 { + panic("invalid function signature") + } + for _, tc := range retTypeCodes { + if _, exists := typeCodeConverters[tc]; !exists { + panic(fmt.Errorf("invalid function signature, unexpected return type code %s", tc)) + } + } + + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != argCount { + return nil, tengo.ErrInvalidArgumentCount{Min: argCount, Max: argCount, Actual: len(args)} + } + // convert tengo objects to raw type values + rawArgs := make([]interface{}, 0, len(argTypeCodes)) + for i, tc := range argTypeCodes { + v, err := typeCodeConverters[tc].arg(i, args...) + if err != nil { + return nil, err + } + rawArgs = append(rawArgs, v) + } + rets := fnApplier(rawArgs...) + if len(rets) != len(retTypeCodes) { + return nil, tengo.ErrInvalidReturnValueCount{Expected: retCount, Actual: len(rets)} + } + // convert raw type value to tengo objects + if len(retTypeCodes) == 0 { + // special case for no return value, return undefined + return tengo.UndefinedValue, nil + } + if retTypeCodes[retCount-1] == "E" && rets[retCount-1] != nil { + // special case for returning an error + return wrapError(rets[retCount-1].(error)), nil + } + return typeCodeConverters[retTypeCodes[0]].ret(rets[0]) + } +} + // FuncAR transform a function of 'func()' signature into CallableFunc type. func FuncAR(fn func()) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } + return base(func(_ ...interface{}) []interface{} { fn() - return tengo.UndefinedValue, nil - } + return nil + }, "-") } // FuncARI transform a function of 'func() int' signature into CallableFunc // type. func FuncARI(fn func() int) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - return &tengo.Int{Value: int64(fn())}, nil - } + return base(func(_ ...interface{}) []interface{} { + return []interface{}{fn()} + }, "-I") } // FuncARI64 transform a function of 'func() int64' signature into CallableFunc // type. func FuncARI64(fn func() int64) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - return &tengo.Int{Value: fn()}, nil - } + return base(func(_ ...interface{}) []interface{} { + return []interface{}{fn()} + }, "-I64") } // FuncAI64RI64 transform a function of 'func(int64) int64' signature into // CallableFunc type. func FuncAI64RI64(fn func(int64) int64) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - return &tengo.Int{Value: fn(i1)}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(int64))} + }, "I64-I64") } // FuncAI64R transform a function of 'func(int64)' signature into CallableFunc // type. func FuncAI64R(fn func(int64)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - fn(i1) - return tengo.UndefinedValue, nil - } + return base(func(args ...interface{}) []interface{} { + fn(args[0].(int64)) + return nil + }, "I64-") } // FuncARB transform a function of 'func() bool' signature into CallableFunc // type. func FuncARB(fn func() bool) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - if fn() { - return tengo.TrueValue, nil - } - return tengo.FalseValue, nil - } + return base(func(_ ...interface{}) []interface{} { + return []interface{}{fn()} + }, "-B") } // FuncARE transform a function of 'func() error' signature into CallableFunc // type. func FuncARE(fn func() error) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - return wrapError(fn()), nil - } + return base(func(_ ...interface{}) []interface{} { + return []interface{}{fn()} + }, "-E") } // FuncARS transform a function of 'func() string' signature into CallableFunc // type. func FuncARS(fn func() string) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - s := fn() - if len(s) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - return &tengo.String{Value: s}, nil - } + return base(func(_ ...interface{}) []interface{} { + return []interface{}{fn()} + }, "-S") } // FuncARSE transform a function of 'func() (string, error)' signature into // CallableFunc type. func FuncARSE(fn func() (string, error)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - res, err := fn() - if err != nil { - return wrapError(err), nil - } - if len(res) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - return &tengo.String{Value: res}, nil - } + return base(func(_ ...interface{}) []interface{} { + v, e := fn() + return []interface{}{v, e} + }, "-SE") } // FuncARYE transform a function of 'func() ([]byte, error)' signature into // CallableFunc type. func FuncARYE(fn func() ([]byte, error)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - res, err := fn() - if err != nil { - return wrapError(err), nil - } - if len(res) > tengo.MaxBytesLen { - return nil, tengo.ErrBytesLimit - } - return &tengo.Bytes{Value: res}, nil - } + return base(func(_ ...interface{}) []interface{} { + v, e := fn() + return []interface{}{v, e} + }, "-YE") } // FuncARF transform a function of 'func() float64' signature into CallableFunc // type. func FuncARF(fn func() float64) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - return &tengo.Float{Value: fn()}, nil - } + return base(func(_ ...interface{}) []interface{} { + return []interface{}{fn()} + }, "-F") } // FuncARSs transform a function of 'func() []string' signature into // CallableFunc type. func FuncARSs(fn func() []string) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - arr := &tengo.Array{} - for _, elem := range fn() { - if len(elem) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - arr.Value = append(arr.Value, &tengo.String{Value: elem}) - } - return arr, nil - } + return base(func(_ ...interface{}) []interface{} { + return []interface{}{fn()} + }, "-Ss") } // FuncARIsE transform a function of 'func() ([]int, error)' signature into // CallableFunc type. func FuncARIsE(fn func() ([]int, error)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } - res, err := fn() - if err != nil { - return wrapError(err), nil - } - arr := &tengo.Array{} - for _, v := range res { - arr.Value = append(arr.Value, &tengo.Int{Value: int64(v)}) - } - return arr, nil - } + return base(func(_ ...interface{}) []interface{} { + v, e := fn() + return []interface{}{v, e} + }, "-IsE") } // FuncAIRIs transform a function of 'func(int) []int' signature into // CallableFunc type. func FuncAIRIs(fn func(int) []int) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - res := fn(i1) - arr := &tengo.Array{} - for _, v := range res { - arr.Value = append(arr.Value, &tengo.Int{Value: int64(v)}) - } - return arr, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(int))} + }, "I-Is") } // FuncAFRF transform a function of 'func(float64) float64' signature into // CallableFunc type. func FuncAFRF(fn func(float64) float64) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - f1, ok := tengo.ToFloat64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "float(compatible)", - Found: args[0].TypeName(), - } - } - return &tengo.Float{Value: fn(f1)}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(float64))} + }, "F-F") } // FuncAIR transform a function of 'func(int)' signature into CallableFunc type. func FuncAIR(fn func(int)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - fn(i1) - return tengo.UndefinedValue, nil - } + return base(func(args ...interface{}) []interface{} { + fn(args[0].(int)) + return nil + }, "I-") } // FuncAIRF transform a function of 'func(int) float64' signature into // CallableFunc type. func FuncAIRF(fn func(int) float64) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - return &tengo.Float{Value: fn(i1)}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(int))} + }, "I-F") } // FuncAFRI transform a function of 'func(float64) int' signature into // CallableFunc type. func FuncAFRI(fn func(float64) int) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - f1, ok := tengo.ToFloat64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "float(compatible)", - Found: args[0].TypeName(), - } - } - return &tengo.Int{Value: int64(fn(f1))}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(float64))} + }, "F-I") } // FuncAFFRF transform a function of 'func(float64, float64) float64' signature // into CallableFunc type. func FuncAFFRF(fn func(float64, float64) float64) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - f1, ok := tengo.ToFloat64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "float(compatible)", - Found: args[0].TypeName(), - } - } - f2, ok := tengo.ToFloat64(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "float(compatible)", - Found: args[1].TypeName(), - } - } - return &tengo.Float{Value: fn(f1, f2)}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(float64), args[1].(float64))} + }, "FF-F") } // FuncAIFRF transform a function of 'func(int, float64) float64' signature // into CallableFunc type. func FuncAIFRF(fn func(int, float64) float64) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - f2, ok := tengo.ToFloat64(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "float(compatible)", - Found: args[1].TypeName(), - } - } - return &tengo.Float{Value: fn(i1, f2)}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(int), args[1].(float64))} + }, "IF-F") } // FuncAFIRF transform a function of 'func(float64, int) float64' signature // into CallableFunc type. func FuncAFIRF(fn func(float64, int) float64) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - f1, ok := tengo.ToFloat64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "float(compatible)", - Found: args[0].TypeName(), - } - } - i2, ok := tengo.ToInt(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - } - return &tengo.Float{Value: fn(f1, i2)}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(float64), args[1].(int))} + }, "FI-F") } // FuncAFIRB transform a function of 'func(float64, int) bool' signature // into CallableFunc type. func FuncAFIRB(fn func(float64, int) bool) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - f1, ok := tengo.ToFloat64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "float(compatible)", - Found: args[0].TypeName(), - } - } - i2, ok := tengo.ToInt(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - } - if fn(f1, i2) { - return tengo.TrueValue, nil - } - return tengo.FalseValue, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(float64), args[1].(int))} + }, "FI-B") } // FuncAFRB transform a function of 'func(float64) bool' signature // into CallableFunc type. func FuncAFRB(fn func(float64) bool) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - f1, ok := tengo.ToFloat64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "float(compatible)", - Found: args[0].TypeName(), - } - } - if fn(f1) { - return tengo.TrueValue, nil - } - return tengo.FalseValue, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(float64))} + }, "F-B") } // FuncASRS transform a function of 'func(string) string' signature into // CallableFunc type. User function will return 'true' if underlying native // function returns nil. func FuncASRS(fn func(string) string) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - s := fn(s1) - if len(s) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - return &tengo.String{Value: s}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string))} + }, "S-S") } // FuncASRSs transform a function of 'func(string) []string' signature into // CallableFunc type. func FuncASRSs(fn func(string) []string) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - res := fn(s1) - arr := &tengo.Array{} - for _, elem := range res { - if len(elem) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - arr.Value = append(arr.Value, &tengo.String{Value: elem}) - } - return arr, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string))} + }, "S-Ss") } // FuncASRSE transform a function of 'func(string) (string, error)' signature // into CallableFunc type. User function will return 'true' if underlying // native function returns nil. func FuncASRSE(fn func(string) (string, error)) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - res, err := fn(s1) - if err != nil { - return wrapError(err), nil - } - if len(res) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - return &tengo.String{Value: res}, nil - } + return base(func(args ...interface{}) []interface{} { + v, e := fn(args[0].(string)) + return []interface{}{v, e} + }, "S-SE") } // FuncASRE transform a function of 'func(string) error' signature into // CallableFunc type. User function will return 'true' if underlying native // function returns nil. func FuncASRE(fn func(string) error) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - return wrapError(fn(s1)), nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string))} + }, "S-E") } // FuncASSRE transform a function of 'func(string, string) error' signature // into CallableFunc type. User function will return 'true' if underlying // native function returns nil. func FuncASSRE(fn func(string, string) error) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - s2, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - } - return wrapError(fn(s1, s2)), nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(string))} + }, "SS-E") } // FuncASSRSs transform a function of 'func(string, string) []string' // signature into CallableFunc type. func FuncASSRSs(fn func(string, string) []string) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - s2, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - } - arr := &tengo.Array{} - for _, res := range fn(s1, s2) { - if len(res) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - arr.Value = append(arr.Value, &tengo.String{Value: res}) - } - return arr, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(string))} + }, "SS-Ss") } // FuncASSIRSs transform a function of 'func(string, string, int) []string' // signature into CallableFunc type. func FuncASSIRSs(fn func(string, string, int) []string) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 3 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - s2, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - } - i3, ok := tengo.ToInt(args[2]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - } - arr := &tengo.Array{} - for _, res := range fn(s1, s2, i3) { - if len(res) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - arr.Value = append(arr.Value, &tengo.String{Value: res}) - } - return arr, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(string), args[2].(int))} + }, "SSI-Ss") } // FuncASSRI transform a function of 'func(string, string) int' signature into // CallableFunc type. func FuncASSRI(fn func(string, string) int) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - s2, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - return &tengo.Int{Value: int64(fn(s1, s2))}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(string))} + }, "SS-I") } // FuncASSRS transform a function of 'func(string, string) string' signature // into CallableFunc type. func FuncASSRS(fn func(string, string) string) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - s2, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - } - s := fn(s1, s2) - if len(s) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - return &tengo.String{Value: s}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(string))} + }, "SS-S") } // FuncASSRB transform a function of 'func(string, string) bool' signature // into CallableFunc type. func FuncASSRB(fn func(string, string) bool) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - s2, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - } - if fn(s1, s2) { - return tengo.TrueValue, nil - } - return tengo.FalseValue, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(string))} + }, "SS-B") } // FuncASsSRS transform a function of 'func([]string, string) string' signature // into CallableFunc type. func FuncASsSRS(fn func([]string, string) string) tengo.CallableFunc { - return func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - var ss1 []string - switch arg0 := args[0].(type) { - case *tengo.Array: - for idx, a := range arg0.Value { - as, ok := tengo.ToString(a) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: fmt.Sprintf("first[%d]", idx), - Expected: "string(compatible)", - Found: a.TypeName(), - } - } - ss1 = append(ss1, as) - } - case *tengo.ImmutableArray: - for idx, a := range arg0.Value { - as, ok := tengo.ToString(a) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: fmt.Sprintf("first[%d]", idx), - Expected: "string(compatible)", - Found: a.TypeName(), - } - } - ss1 = append(ss1, as) - } - default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "array", - Found: args[0].TypeName(), - } - } - s2, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - } - s := fn(ss1, s2) - if len(s) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - return &tengo.String{Value: s}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].([]string), args[1].(string))} + }, "SsS-S") } // FuncASI64RE transform a function of 'func(string, int64) error' signature // into CallableFunc type. func FuncASI64RE(fn func(string, int64) error) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - i2, ok := tengo.ToInt64(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - } - return wrapError(fn(s1, i2)), nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(int64))} + }, "SI64-E") } // FuncAIIRE transform a function of 'func(int, int) error' signature // into CallableFunc type. func FuncAIIRE(fn func(int, int) error) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - i2, ok := tengo.ToInt(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - } - return wrapError(fn(i1, i2)), nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(int), args[1].(int))} + }, "II-E") } // FuncASIRS transform a function of 'func(string, int) string' signature // into CallableFunc type. func FuncASIRS(fn func(string, int) string) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - i2, ok := tengo.ToInt(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - } - s := fn(s1, i2) - if len(s) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - return &tengo.String{Value: s}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(int))} + }, "SI-S") } // FuncASIIRE transform a function of 'func(string, int, int) error' signature // into CallableFunc type. func FuncASIIRE(fn func(string, int, int) error) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 3 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - i2, ok := tengo.ToInt(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - } - i3, ok := tengo.ToInt(args[2]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - } - return wrapError(fn(s1, i2, i3)), nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(string), args[1].(int), args[2].(int))} + }, "SII-E") } // FuncAYRIE transform a function of 'func([]byte) (int, error)' signature // into CallableFunc type. func FuncAYRIE(fn func([]byte) (int, error)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - y1, ok := tengo.ToByteSlice(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes(compatible)", - Found: args[0].TypeName(), - } - } - res, err := fn(y1) - if err != nil { - return wrapError(err), nil - } - return &tengo.Int{Value: int64(res)}, nil - } + return base(func(args ...interface{}) []interface{} { + v, e := fn(args[0].([]byte)) + return []interface{}{v, e} + }, "Y-IE") } // FuncAYRS transform a function of 'func([]byte) string' signature into // CallableFunc type. func FuncAYRS(fn func([]byte) string) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - y1, ok := tengo.ToByteSlice(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes(compatible)", - Found: args[0].TypeName(), - } - } - res := fn(y1) - return &tengo.String{Value: res}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].([]byte))} + }, "Y-S") } // FuncASRIE transform a function of 'func(string) (int, error)' signature // into CallableFunc type. func FuncASRIE(fn func(string) (int, error)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - res, err := fn(s1) - if err != nil { - return wrapError(err), nil - } - return &tengo.Int{Value: int64(res)}, nil - } + return base(func(args ...interface{}) []interface{} { + v, e := fn(args[0].(string)) + return []interface{}{v, e} + }, "S-IE") } // FuncASRYE transform a function of 'func(string) ([]byte, error)' signature // into CallableFunc type. func FuncASRYE(fn func(string) ([]byte, error)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - res, err := fn(s1) - if err != nil { - return wrapError(err), nil - } - if len(res) > tengo.MaxBytesLen { - return nil, tengo.ErrBytesLimit - } - return &tengo.Bytes{Value: res}, nil - } + return base(func(args ...interface{}) []interface{} { + v, e := fn(args[0].(string)) + return []interface{}{v, e} + }, "S-YE") } // FuncAIRSsE transform a function of 'func(int) ([]string, error)' signature // into CallableFunc type. func FuncAIRSsE(fn func(int) ([]string, error)) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - res, err := fn(i1) - if err != nil { - return wrapError(err), nil - } - arr := &tengo.Array{} - for _, r := range res { - if len(r) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - arr.Value = append(arr.Value, &tengo.String{Value: r}) - } - return arr, nil - } + return base(func(args ...interface{}) []interface{} { + v, e := fn(args[0].(int)) + return []interface{}{v, e} + }, "I-SsE") } // FuncAIRS transform a function of 'func(int) string' signature into // CallableFunc type. func FuncAIRS(fn func(int) string) tengo.CallableFunc { - return func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - } - s := fn(i1) - if len(s) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit - } - return &tengo.String{Value: s}, nil - } + return base(func(args ...interface{}) []interface{} { + return []interface{}{fn(args[0].(int))} + }, "I-S") } diff --git a/stdlib/func_typedefs_test.go b/stdlib/func_typedefs_test.go index 091c5c47..893345fb 100644 --- a/stdlib/func_typedefs_test.go +++ b/stdlib/func_typedefs_test.go @@ -17,7 +17,7 @@ func TestFuncAIR(t *testing.T) { require.NoError(t, err) require.Equal(t, tengo.UndefinedValue, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncAR(t *testing.T) { @@ -26,7 +26,7 @@ func TestFuncAR(t *testing.T) { require.NoError(t, err) require.Equal(t, tengo.UndefinedValue, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncARI(t *testing.T) { @@ -35,7 +35,7 @@ func TestFuncARI(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Int{Value: 10}, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncARE(t *testing.T) { @@ -50,7 +50,7 @@ func TestFuncARE(t *testing.T) { Value: &tengo.String{Value: "some error"}, }, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncARIsE(t *testing.T) { @@ -70,7 +70,7 @@ func TestFuncARIsE(t *testing.T) { Value: &tengo.String{Value: "some error"}, }, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncARS(t *testing.T) { @@ -79,7 +79,7 @@ func TestFuncARS(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.String{Value: "foo"}, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncARSE(t *testing.T) { @@ -96,7 +96,7 @@ func TestFuncARSE(t *testing.T) { Value: &tengo.String{Value: "some error"}, }, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncARSs(t *testing.T) { @@ -106,7 +106,7 @@ func TestFuncARSs(t *testing.T) { require.Equal(t, array(&tengo.String{Value: "foo"}, &tengo.String{Value: "bar"}), ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncASRE(t *testing.T) { @@ -123,7 +123,7 @@ func TestFuncASRE(t *testing.T) { Value: &tengo.String{Value: "some error"}, }, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncASRS(t *testing.T) { @@ -132,7 +132,7 @@ func TestFuncASRS(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.String{Value: "foo"}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncASRSs(t *testing.T) { @@ -141,7 +141,7 @@ func TestFuncASRSs(t *testing.T) { require.NoError(t, err) require.Equal(t, array(&tengo.String{Value: "foo"}), ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncASI64RE(t *testing.T) { @@ -157,7 +157,7 @@ func TestFuncASI64RE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) } func TestFuncAIIRE(t *testing.T) { @@ -173,7 +173,7 @@ func TestFuncAIIRE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) } func TestFuncASIIRE(t *testing.T) { @@ -191,7 +191,7 @@ func TestFuncASIIRE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 3, Max: 3, Actual: 0}, err) } func TestFuncASRSE(t *testing.T) { @@ -207,7 +207,7 @@ func TestFuncASRSE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncASSRE(t *testing.T) { @@ -225,7 +225,7 @@ func TestFuncASSRE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf, &tengo.String{Value: "foo"}) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncASsRS(t *testing.T) { @@ -237,7 +237,7 @@ func TestFuncASsRS(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.String{Value: "foo bar"}, ret) _, err = funcCall(uf, &tengo.String{Value: "foo"}) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncARF(t *testing.T) { @@ -246,7 +246,7 @@ func TestFuncARF(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Float{Value: 10.0}, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncAFRF(t *testing.T) { @@ -255,9 +255,9 @@ func TestFuncAFRF(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Float{Value: 10.0}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 2}, err) } func TestFuncAIRF(t *testing.T) { @@ -268,9 +268,9 @@ func TestFuncAIRF(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Float{Value: 10.0}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 2}, err) } func TestFuncAFRI(t *testing.T) { @@ -281,9 +281,9 @@ func TestFuncAFRI(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Int{Value: 10}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 2}, err) } func TestFuncAFRB(t *testing.T) { @@ -294,9 +294,9 @@ func TestFuncAFRB(t *testing.T) { require.NoError(t, err) require.Equal(t, tengo.TrueValue, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 2}, err) } func TestFuncAFFRF(t *testing.T) { @@ -308,9 +308,9 @@ func TestFuncAFFRF(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Float{Value: 30.0}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncASIRS(t *testing.T) { @@ -321,9 +321,9 @@ func TestFuncASIRS(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.String{Value: "abab"}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncAIFRF(t *testing.T) { @@ -334,9 +334,9 @@ func TestFuncAIFRF(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Float{Value: 30.0}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncAFIRF(t *testing.T) { @@ -347,9 +347,9 @@ func TestFuncAFIRF(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Float{Value: 30.0}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncAFIRB(t *testing.T) { @@ -360,9 +360,9 @@ func TestFuncAFIRB(t *testing.T) { require.NoError(t, err) require.Equal(t, tengo.TrueValue, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncAIRSsE(t *testing.T) { @@ -381,7 +381,7 @@ func TestFuncAIRSsE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncASSRSs(t *testing.T) { @@ -394,7 +394,7 @@ func TestFuncASSRSs(t *testing.T) { require.Equal(t, array(&tengo.String{Value: "foo"}, &tengo.String{Value: "bar"}), ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) } func TestFuncASSIRSs(t *testing.T) { @@ -407,7 +407,7 @@ func TestFuncASSIRSs(t *testing.T) { require.Equal(t, array(&tengo.String{Value: "foo"}, &tengo.String{Value: "bar"}, &tengo.String{Value: "5"}), ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 3, Max: 3, Actual: 0}, err) } func TestFuncARB(t *testing.T) { @@ -416,7 +416,7 @@ func TestFuncARB(t *testing.T) { require.NoError(t, err) require.Equal(t, tengo.TrueValue, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncARYE(t *testing.T) { @@ -434,7 +434,7 @@ func TestFuncARYE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf, tengo.TrueValue) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncASRIE(t *testing.T) { @@ -450,7 +450,7 @@ func TestFuncASRIE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncAYRIE(t *testing.T) { @@ -466,7 +466,7 @@ func TestFuncAYRIE(t *testing.T) { require.Equal(t, &tengo.Error{Value: &tengo.String{Value: "some error"}}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncASSRI(t *testing.T) { @@ -476,7 +476,7 @@ func TestFuncASSRI(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Int{Value: 6}, ret) _, err = funcCall(uf, &tengo.String{Value: "foo"}) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncASSRS(t *testing.T) { @@ -486,7 +486,7 @@ func TestFuncASSRS(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.String{Value: "foobar"}, ret) _, err = funcCall(uf, &tengo.String{Value: "foo"}) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncASSRB(t *testing.T) { @@ -496,7 +496,7 @@ func TestFuncASSRB(t *testing.T) { require.NoError(t, err) require.Equal(t, tengo.TrueValue, ret) _, err = funcCall(uf, &tengo.String{Value: "foo"}) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 1}, err) } func TestFuncAIRS(t *testing.T) { @@ -505,7 +505,7 @@ func TestFuncAIRS(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.String{Value: "55"}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncAIRIs(t *testing.T) { @@ -514,7 +514,7 @@ func TestFuncAIRIs(t *testing.T) { require.NoError(t, err) require.Equal(t, array(&tengo.Int{Value: 55}, &tengo.Int{Value: 55}), ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncAI64R(t *testing.T) { @@ -523,7 +523,7 @@ func TestFuncAI64R(t *testing.T) { require.NoError(t, err) require.Equal(t, tengo.UndefinedValue, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func TestFuncARI64(t *testing.T) { @@ -532,7 +532,7 @@ func TestFuncARI64(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Int{Value: 55}, ret) _, err = funcCall(uf, &tengo.Int{Value: 55}) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 0, Max: 0, Actual: 1}, err) } func TestFuncASsSRS(t *testing.T) { @@ -545,7 +545,7 @@ func TestFuncASsSRS(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.String{Value: "abc-def"}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 2, Max: 2, Actual: 0}, err) } func TestFuncAI64RI64(t *testing.T) { @@ -554,7 +554,7 @@ func TestFuncAI64RI64(t *testing.T) { require.NoError(t, err) require.Equal(t, &tengo.Int{Value: 110}, ret) _, err = funcCall(uf) - require.Equal(t, tengo.ErrWrongNumArguments, err) + require.Equal(t, tengo.ErrInvalidArgumentCount{Min: 1, Max: 1, Actual: 0}, err) } func funcCall( diff --git a/stdlib/json.go b/stdlib/json.go index be2185db..705e97a8 100644 --- a/stdlib/json.go +++ b/stdlib/json.go @@ -27,11 +27,7 @@ var jsonModule = map[string]tengo.Object{ }, } -func jsonDecode(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - +var jsonDecode = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { switch o := args[0].(type) { case *tengo.Bytes: v, err := json.Decode(o.Value) @@ -50,48 +46,28 @@ func jsonDecode(args ...tengo.Object) (ret tengo.Object, err error) { } return v, nil default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes/string", - Found: args[0].TypeName(), - } - } -} - -func jsonEncode(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments + panic("impossible") } +}, 1, 1, tengo.TNs{tengo.BytesTN, tengo.StringTN}) +var jsonEncode = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { b, err := json.Encode(args[0]) if err != nil { return &tengo.Error{Value: &tengo.String{Value: err.Error()}}, nil } return &tengo.Bytes{Value: b}, nil -} +}, 1) -func jsonIndent(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 3 { - return nil, tengo.ErrWrongNumArguments - } - - prefix, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "prefix", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } +var jsonIndent = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { + prefix, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } - indent, ok := tengo.ToString(args[2]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "indent", - Expected: "string(compatible)", - Found: args[2].TypeName(), - } + indent, err := tengo.ToString(2, args...) + if err != nil { + return nil, err } switch o := args[0].(type) { @@ -114,19 +90,11 @@ func jsonIndent(args ...tengo.Object) (ret tengo.Object, err error) { } return &tengo.Bytes{Value: dst.Bytes()}, nil default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes/string", - Found: args[0].TypeName(), - } - } -} - -func jsonHTMLEscape(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments + panic("impossible") } +}, 3, 3, nil, nil, tengo.TNs{tengo.BytesTN, tengo.StringTN}) +var jsonHTMLEscape = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { switch o := args[0].(type) { case *tengo.Bytes: var dst bytes.Buffer @@ -137,10 +105,6 @@ func jsonHTMLEscape(args ...tengo.Object) (ret tengo.Object, err error) { gojson.HTMLEscape(&dst, []byte(o.Value)) return &tengo.Bytes{Value: dst.Bytes()}, nil default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes/string", - Found: args[0].TypeName(), - } + panic("impossible") } -} +}, 1, 1, tengo.TNs{tengo.BytesTN, tengo.StringTN}) diff --git a/stdlib/os.go b/stdlib/os.go index 576bc94b..8e6cbe79 100644 --- a/stdlib/os.go +++ b/stdlib/os.go @@ -1,7 +1,6 @@ package stdlib import ( - "fmt" "io" "io/ioutil" "os" @@ -200,17 +199,10 @@ var osModule = map[string]tengo.Object{ }, // readfile(name) => array(byte)/error } -func osReadFile(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - fname, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osReadFile = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + fname, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } bytes, err := ioutil.ReadFile(fname) if err != nil { @@ -220,19 +212,12 @@ func osReadFile(args ...tengo.Object) (ret tengo.Object, err error) { return nil, tengo.ErrBytesLimit } return &tengo.Bytes{Value: bytes}, nil -} +}, 1) -func osStat(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - fname, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osStat = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + fname, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } stat, err := os.Stat(fname) if err != nil { @@ -252,85 +237,53 @@ func osStat(args ...tengo.Object) (ret tengo.Object, err error) { fstat.Value["directory"] = tengo.FalseValue } return fstat, nil -} +}, 1) -func osCreate(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osCreate = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } res, err := os.Create(s1) if err != nil { return wrapError(err), nil } return makeOSFile(res), nil -} +}, 1) -func osOpen(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osOpen = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } res, err := os.Open(s1) if err != nil { return wrapError(err), nil } return makeOSFile(res), nil -} +}, 1) -func osOpenFile(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 3 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osOpenFile = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } - i3, ok := tengo.ToInt(args[2]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } + i3, err := tengo.ToInt(2, args...) + if err != nil { + return nil, err } res, err := os.OpenFile(s1, i2, os.FileMode(i3)) if err != nil { return wrapError(err), nil } return makeOSFile(res), nil -} +}, 3) -func osArgs(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } +var osArgs = tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error) { arr := &tengo.Array{} for _, osArg := range os.Args { if len(osArg) > tengo.MaxStringLen { @@ -339,7 +292,7 @@ func osArgs(args ...tengo.Object) (tengo.Object, error) { arr.Value = append(arr.Value, &tengo.String{Value: osArg}) } return arr, nil -} +}) func osFuncASFmRE( name string, @@ -347,42 +300,24 @@ func osFuncASFmRE( ) *tengo.UserFunction { return &tengo.UserFunction{ Name: name, - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - } - i2, ok := tengo.ToInt64(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } + i2, err := tengo.ToInt64(1, args...) + if err != nil { + return nil, err } return wrapError(fn(s1, os.FileMode(i2))), nil - }, + }, 2), } } -func osLookupEnv(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osLookupEnv = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } res, ok := os.LookupEnv(s1) if !ok { @@ -392,19 +327,12 @@ func osLookupEnv(args ...tengo.Object) (tengo.Object, error) { return nil, tengo.ErrStringLimit } return &tengo.String{Value: res}, nil -} +}, 1) -func osExpandEnv(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osExpandEnv = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } var vlen int var failed bool @@ -427,114 +355,76 @@ func osExpandEnv(args ...tengo.Object) (tengo.Object, error) { return nil, tengo.ErrStringLimit } return &tengo.String{Value: s}, nil -} +}, 1) -func osExec(args ...tengo.Object) (tengo.Object, error) { - if len(args) == 0 { - return nil, tengo.ErrWrongNumArguments - } - name, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osExec = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + name, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } var execArgs []string - for idx, arg := range args[1:] { - execArg, ok := tengo.ToString(arg) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: fmt.Sprintf("args[%d]", idx), - Expected: "string(compatible)", - Found: args[1+idx].TypeName(), - } + for idx := range args[1:] { + execArg, err := tengo.ToString(idx, args[1:]...) + if err != nil { + return nil, err } execArgs = append(execArgs, execArg) } return makeOSExecCommand(exec.Command(name, execArgs...)), nil -} +}, 1, -1) -func osFindProcess(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } +var osFindProcess = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + i1, err := tengo.ToInt(0, args...) + if err != nil { + return nil, err } proc, err := os.FindProcess(i1) if err != nil { return wrapError(err), nil } return makeOSProcess(proc), nil -} +}, 1) -func osStartProcess(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 4 { - return nil, tengo.ErrWrongNumArguments - } - name, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } +var osStartProcess = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { + name, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } var argv []string - var err error switch arg1 := args[1].(type) { case *tengo.Array: - argv, err = stringArray(arg1.Value, "second") + argv, err = stringArray(arg1.Value, 1) if err != nil { return nil, err } case *tengo.ImmutableArray: - argv, err = stringArray(arg1.Value, "second") + argv, err = stringArray(arg1.Value, 1) if err != nil { return nil, err } default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "array", - Found: arg1.TypeName(), - } + panic("impossible") } - dir, ok := tengo.ToString(args[2]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "string(compatible)", - Found: args[2].TypeName(), - } + dir, err := tengo.ToString(2, args...) + if err != nil { + return nil, err } var env []string switch arg3 := args[3].(type) { case *tengo.Array: - env, err = stringArray(arg3.Value, "fourth") + env, err = stringArray(arg3.Value, 3) if err != nil { return nil, err } case *tengo.ImmutableArray: - env, err = stringArray(arg3.Value, "fourth") + env, err = stringArray(arg3.Value, 3) if err != nil { return nil, err } default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "fourth", - Expected: "array", - Found: arg3.TypeName(), - } + panic("impossible") } proc, err := os.StartProcess(name, argv, &os.ProcAttr{ @@ -545,17 +435,23 @@ func osStartProcess(args ...tengo.Object) (tengo.Object, error) { return wrapError(err), nil } return makeOSProcess(proc), nil -} +}, + 4, + 4, + nil, + tengo.TNs{tengo.ArrayTN, tengo.ImmutableArrayTN}, + nil, + tengo.TNs{tengo.ArrayTN, tengo.ImmutableArrayTN}) -func stringArray(arr []tengo.Object, argName string) ([]string, error) { +func stringArray(arr []tengo.Object, index int) ([]string, error) { var sarr []string - for idx, elem := range arr { + for _, elem := range arr { str, ok := elem.(*tengo.String) if !ok { return nil, tengo.ErrInvalidArgumentType{ - Name: fmt.Sprintf("%s[%d]", argName, idx), - Expected: "string", - Found: elem.TypeName(), + Index: index, + Expected: tengo.StringTN, + Actual: elem.TypeName(), } } sarr = append(sarr, str.Value) diff --git a/stdlib/os_exec.go b/stdlib/os_exec.go index 7ee5c1cd..992b661b 100644 --- a/stdlib/os_exec.go +++ b/stdlib/os_exec.go @@ -37,82 +37,57 @@ func makeOSExecCommand(cmd *exec.Cmd) *tengo.ImmutableMap { // set_path(path string) "set_path": &tengo.UserFunction{ Name: "set_path", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } cmd.Path = s1 return tengo.UndefinedValue, nil - }, + }, 1), }, // set_dir(dir string) "set_dir": &tengo.UserFunction{ Name: "set_dir", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } cmd.Dir = s1 return tengo.UndefinedValue, nil - }, + }, 1), }, // set_env(env array(string)) "set_env": &tengo.UserFunction{ Name: "set_env", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - + Value: tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { var env []string var err error switch arg0 := args[0].(type) { case *tengo.Array: - env, err = stringArray(arg0.Value, "first") + env, err = stringArray(arg0.Value, 0) if err != nil { return nil, err } case *tengo.ImmutableArray: - env, err = stringArray(arg0.Value, "first") + env, err = stringArray(arg0.Value, 0) if err != nil { return nil, err } default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "array", - Found: arg0.TypeName(), - } + panic("impossible") } cmd.Env = env return tengo.UndefinedValue, nil - }, + }, 1, 1, tengo.TNs{tengo.ArrayTN, tengo.ImmutableArrayTN}), }, // process() => imap(process) "process": &tengo.UserFunction{ Name: "process", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } + Value: tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error) { return makeOSProcess(cmd.Process), nil - }, + }), }, }, } diff --git a/stdlib/os_file.go b/stdlib/os_file.go index 4f59b4c4..59d830f4 100644 --- a/stdlib/os_file.go +++ b/stdlib/os_file.go @@ -57,60 +57,39 @@ func makeOSFile(file *os.File) *tengo.ImmutableMap { // chmod(mode int) => error "chmod": &tengo.UserFunction{ Name: "chmod", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } return wrapError(file.Chmod(os.FileMode(i1))), nil - }, + }, 1), }, // seek(offset int, whence int) => int/error "seek": &tengo.UserFunction{ Name: "seek", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } res, err := file.Seek(i1, i2) if err != nil { return wrapError(err), nil } return &tengo.Int{Value: res}, nil - }, + }, 2), }, // stat() => imap(fileinfo)/error "stat": &tengo.UserFunction{ Name: "stat", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } + Value: tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error) { return osStat(&tengo.String{Value: file.Name()}) - }, + }), }, }, } diff --git a/stdlib/os_process.go b/stdlib/os_process.go index 7fcf27a4..4f636bb9 100644 --- a/stdlib/os_process.go +++ b/stdlib/os_process.go @@ -43,33 +43,23 @@ func makeOSProcess(proc *os.Process) *tengo.ImmutableMap { }, "signal": &tengo.UserFunction{ Name: "signal", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } return wrapError(proc.Signal(syscall.Signal(i1))), nil - }, + }, 1), }, "wait": &tengo.UserFunction{ Name: "wait", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 0 { - return nil, tengo.ErrWrongNumArguments - } + Value: tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error) { state, err := proc.Wait() if err != nil { return wrapError(err), nil } return makeOSProcessState(state), nil - }, + }), }, }, } diff --git a/stdlib/rand.go b/stdlib/rand.go index 5d21e1df..2c02e142 100644 --- a/stdlib/rand.go +++ b/stdlib/rand.go @@ -37,43 +37,25 @@ var randModule = map[string]tengo.Object{ }, "read": &tengo.UserFunction{ Name: "read", - Value: func(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - y1, ok := args[0].(*tengo.Bytes) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes", - Found: args[0].TypeName(), - } - } + Value: tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error) { + y1, _ := args[0].(*tengo.Bytes) res, err := rand.Read(y1.Value) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil } return &tengo.Int{Value: int64(res)}, nil - }, + }, tengo.BytesTN), }, "rand": &tengo.UserFunction{ Name: "rand", - Value: func(args ...tengo.Object) (tengo.Object, error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - i1, ok := tengo.ToInt64(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } src := rand.NewSource(i1) return randRand(rand.New(src)), nil - }, + }, 1), }, } @@ -110,28 +92,17 @@ func randRand(r *rand.Rand) *tengo.ImmutableMap { }, "read": &tengo.UserFunction{ Name: "read", - Value: func(args ...tengo.Object) ( - ret tengo.Object, - err error, + Value: tengo.CheckStrictArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - y1, ok := args[0].(*tengo.Bytes) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes", - Found: args[0].TypeName(), - } - } + y1, _ := args[0].(*tengo.Bytes) res, err := r.Read(y1.Value) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil } return &tengo.Int{Value: int64(res)}, nil - }, + }, tengo.BytesTN), }, }, } diff --git a/stdlib/text.go b/stdlib/text.go index d7d5d1da..652b519a 100644 --- a/stdlib/text.go +++ b/stdlib/text.go @@ -1,7 +1,6 @@ package stdlib import ( - "fmt" "regexp" "strconv" "strings" @@ -201,85 +200,50 @@ var textModule = map[string]tengo.Object{ }, // unquote(str) => string/error } -func textREMatch(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return +var textREMatch = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } matched, err := regexp.MatchString(s1, s2) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil } if matched { - ret = tengo.TrueValue - } else { - ret = tengo.FalseValue + return tengo.TrueValue, nil } + return tengo.FalseValue, nil +}, 2) - return -} - -func textREFind(args ...tengo.Object) (ret tengo.Object, err error) { +var textREFind = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { numArgs := len(args) - if numArgs != 2 && numArgs != 3 { - err = tengo.ErrWrongNumArguments - return - } - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } re, err := regexp.Compile(s1) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil } - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } if numArgs < 3 { m := re.FindStringSubmatchIndex(s2) if m == nil { - ret = tengo.UndefinedValue - return + return tengo.UndefinedValue, nil } arr := &tengo.Array{} @@ -292,24 +256,16 @@ func textREFind(args ...tengo.Object) (ret tengo.Object, err error) { }}) } - ret = &tengo.Array{Value: []tengo.Object{arr}} - - return + return &tengo.Array{Value: []tengo.Object{arr}}, nil } - i3, ok := tengo.ToInt(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - return + i3, err := tengo.ToInt(2, args...) + if err != nil { + return nil, err } m := re.FindAllStringSubmatchIndex(s2, i3) if m == nil { - ret = tengo.UndefinedValue - return + return tengo.UndefinedValue, nil } arr := &tengo.Array{} @@ -327,106 +283,61 @@ func textREFind(args ...tengo.Object) (ret tengo.Object, err error) { arr.Value = append(arr.Value, subMatch) } - ret = arr - - return -} + return arr, nil +}, 2, 3) -func textREReplace(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 3 { - err = tengo.ErrWrongNumArguments - return - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return +var textREReplace = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } - s3, ok := tengo.ToString(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "string(compatible)", - Found: args[2].TypeName(), - } - return + s3, err := tengo.ToString(2, args...) + if err != nil { + return nil, err } re, err := regexp.Compile(s1) if err != nil { - ret = wrapError(err) - } else { - s, ok := doTextRegexpReplace(re, s2, s3) - if !ok { - return nil, tengo.ErrStringLimit - } - - ret = &tengo.String{Value: s} + return wrapError(err), nil + } + s, ok := doTextRegexpReplace(re, s2, s3) + if !ok { + return nil, tengo.ErrStringLimit } - return -} + return &tengo.String{Value: s}, nil +}, 3) -func textRESplit(args ...tengo.Object) (ret tengo.Object, err error) { +var textRESplit = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { numArgs := len(args) - if numArgs != 2 && numArgs != 3 { - err = tengo.ErrWrongNumArguments - return - } - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } var i3 = -1 if numArgs > 2 { - i3, ok = tengo.ToInt(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - return + i3, err = tengo.ToInt(2, args...) + if err != nil { + return nil, err } } re, err := regexp.Compile(s1) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil } arr := &tengo.Array{} @@ -434,138 +345,76 @@ func textRESplit(args ...tengo.Object) (ret tengo.Object, err error) { arr.Value = append(arr.Value, &tengo.String{Value: s}) } - ret = arr + return arr, nil +}, 2, 3) - return -} - -func textRECompile(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return +var textRECompile = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } re, err := regexp.Compile(s1) if err != nil { - ret = wrapError(err) - } else { - ret = makeTextRegexp(re) + return wrapError(err), nil } + return makeTextRegexp(re), nil +}, 1) - return -} - -func textReplace(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 4 { - err = tengo.ErrWrongNumArguments - return - } +var textReplace = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } - s3, ok := tengo.ToString(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "string(compatible)", - Found: args[2].TypeName(), - } - return + s3, err := tengo.ToString(2, args...) + if err != nil { + return nil, err } - i4, ok := tengo.ToInt(args[3]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "fourth", - Expected: "int(compatible)", - Found: args[3].TypeName(), - } - return + i4, err := tengo.ToInt(3, args...) + if err != nil { + return nil, err } s, ok := doTextReplace(s1, s2, s3, i4) if !ok { - err = tengo.ErrStringLimit - return + return nil, tengo.ErrStringLimit } - ret = &tengo.String{Value: s} - - return -} + return &tengo.String{Value: s}, nil +}, 4) -func textSubstring(args ...tengo.Object) (ret tengo.Object, err error) { +var textSubstring = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { argslen := len(args) - if argslen != 2 && argslen != 3 { - err = tengo.ErrWrongNumArguments - return - } - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } strlen := len(s1) i3 := strlen if argslen == 3 { - i3, ok = tengo.ToInt(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - return + i3, err = tengo.ToInt(2, args...) + if err != nil { + return nil, err } } if i2 > i3 { - err = tengo.ErrInvalidIndexType - return + return nil, tengo.ErrInvalidIndexType } if i2 < 0 { @@ -580,36 +429,20 @@ func textSubstring(args ...tengo.Object) (ret tengo.Object, err error) { i3 = strlen } - ret = &tengo.String{Value: s1[i2:i3]} + return &tengo.String{Value: s1[i2:i3]}, nil +}, 2, 3) - return -} - -func textPadLeft(args ...tengo.Object) (ret tengo.Object, err error) { +var textPadLeft = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { argslen := len(args) - if argslen != 2 && argslen != 3 { - err = tengo.ErrWrongNumArguments - return - } - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } if i2 > tengo.MaxStringLen { @@ -618,61 +451,38 @@ func textPadLeft(args ...tengo.Object) (ret tengo.Object, err error) { sLen := len(s1) if sLen >= i2 { - ret = &tengo.String{Value: s1} - return + return &tengo.String{Value: s1}, nil } s3 := " " if argslen == 3 { - s3, ok = tengo.ToString(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "string(compatible)", - Found: args[2].TypeName(), - } - return + s3, err = tengo.ToString(2, args...) + if err != nil { + return nil, err } } padStrLen := len(s3) if padStrLen == 0 { - ret = &tengo.String{Value: s1} - return + return &tengo.String{Value: s1}, nil } padCount := ((i2 - padStrLen) / padStrLen) + 1 retStr := strings.Repeat(s3, padCount) + s1 - ret = &tengo.String{Value: retStr[len(retStr)-i2:]} - - return -} + return &tengo.String{Value: retStr[len(retStr)-i2:]}, nil +}, 2, 3) -func textPadRight(args ...tengo.Object) (ret tengo.Object, err error) { +var textPadRight = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { argslen := len(args) - if argslen != 2 && argslen != 3 { - err = tengo.ErrWrongNumArguments - return - } - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } if i2 > tengo.MaxStringLen { @@ -681,57 +491,37 @@ func textPadRight(args ...tengo.Object) (ret tengo.Object, err error) { sLen := len(s1) if sLen >= i2 { - ret = &tengo.String{Value: s1} - return + return &tengo.String{Value: s1}, nil } s3 := " " if argslen == 3 { - s3, ok = tengo.ToString(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "string(compatible)", - Found: args[2].TypeName(), - } - return + s3, err = tengo.ToString(2, args...) + if err != nil { + return nil, err } } padStrLen := len(s3) if padStrLen == 0 { - ret = &tengo.String{Value: s1} - return + return &tengo.String{Value: s1}, nil } padCount := ((i2 - padStrLen) / padStrLen) + 1 retStr := s1 + strings.Repeat(s3, padCount) - ret = &tengo.String{Value: retStr[:i2]} + return &tengo.String{Value: retStr[:i2]}, nil +}, 2, 3) - return -} - -func textRepeat(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } +var textRepeat = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } if len(s1)*i2 > tengo.MaxStringLen { @@ -739,57 +529,37 @@ func textRepeat(args ...tengo.Object) (ret tengo.Object, err error) { } return &tengo.String{Value: strings.Repeat(s1, i2)}, nil -} - -func textJoin(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - return nil, tengo.ErrWrongNumArguments - } +}, 2) +var textJoin = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { var slen int var ss1 []string switch arg0 := args[0].(type) { case *tengo.Array: - for idx, a := range arg0.Value { - as, ok := tengo.ToString(a) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: fmt.Sprintf("first[%d]", idx), - Expected: "string(compatible)", - Found: a.TypeName(), - } + for idx := range arg0.Value { + as, err := tengo.ToString(idx, arg0.Value...) + if err != nil { + return nil, err } slen += len(as) ss1 = append(ss1, as) } case *tengo.ImmutableArray: - for idx, a := range arg0.Value { - as, ok := tengo.ToString(a) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: fmt.Sprintf("first[%d]", idx), - Expected: "string(compatible)", - Found: a.TypeName(), - } + for idx := range arg0.Value { + as, err := tengo.ToString(idx, arg0.Value...) + if err != nil { + return nil, err } slen += len(as) ss1 = append(ss1, as) } default: - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "array", - Found: args[0].TypeName(), - } + panic("impossible") } - s2, ok := tengo.ToString(args[1]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } // make sure output length does not exceed the limit @@ -798,229 +568,114 @@ func textJoin(args ...tengo.Object) (ret tengo.Object, err error) { } return &tengo.String{Value: strings.Join(ss1, s2)}, nil -} +}, 2, 2, tengo.TNs{tengo.ArrayTN, tengo.ImmutableArrayTN}, nil) -func textFormatBool(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - b1, ok := args[0].(*tengo.Bool) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "bool", - Found: args[0].TypeName(), - } - return - } +var textFormatBool = tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error) { + b1 := args[0].(*tengo.Bool) if b1 == tengo.TrueValue { - ret = &tengo.String{Value: "true"} - } else { - ret = &tengo.String{Value: "false"} + return &tengo.String{Value: "true"}, nil } + return &tengo.String{Value: "false"}, nil +}, tengo.BoolTN) - return -} +var textFormatFloat = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { + f1 := args[0].(*tengo.Float) -func textFormatFloat(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 4 { - err = tengo.ErrWrongNumArguments - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } - f1, ok := args[0].(*tengo.Float) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "float", - Found: args[0].TypeName(), - } - return - } + i3, err := tengo.ToInt(2, args...) + if err != nil { + return nil, err - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return } - i3, ok := tengo.ToInt(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - return - } + i4, err := tengo.ToInt(3, args...) + if err != nil { + return nil, err - i4, ok := tengo.ToInt(args[3]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "fourth", - Expected: "int(compatible)", - Found: args[3].TypeName(), - } - return } - ret = &tengo.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)} + return &tengo.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)}, nil - return -} +}, + 4, + 4, + tengo.TNs{tengo.FloatTN}, + nil, + nil, + nil) -func textFormatInt(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } +var textFormatInt = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { + i1 := args[0].(*tengo.Int) - i1, ok := args[0].(*tengo.Int) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int", - Found: args[0].TypeName(), - } - return - } - - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } - ret = &tengo.String{Value: strconv.FormatInt(i1.Value, i2)} + return &tengo.String{Value: strconv.FormatInt(i1.Value, i2)}, nil - return -} +}, 2, 2, tengo.TNs{tengo.IntTN}, nil) -func textParseBool(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - s1, ok := args[0].(*tengo.String) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string", - Found: args[0].TypeName(), - } - return - } +var textParseBool = tengo.CheckStrictArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1 := args[0].(*tengo.String) parsed, err := strconv.ParseBool(s1.Value) if err != nil { - ret = wrapError(err) - return - } + return wrapError(err), nil - if parsed { - ret = tengo.TrueValue - } else { - ret = tengo.FalseValue } - return -} - -func textParseFloat(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return + if parsed { + return tengo.TrueValue, nil } + return tengo.FalseValue, nil +}, tengo.StringTN) - s1, ok := args[0].(*tengo.String) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string", - Found: args[0].TypeName(), - } - return - } +var textParseFloat = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1 := args[0].(*tengo.String) - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } parsed, err := strconv.ParseFloat(s1.Value, i2) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil + } - ret = &tengo.Float{Value: parsed} + return &tengo.Float{Value: parsed}, nil - return -} - -func textParseInt(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 3 { - err = tengo.ErrWrongNumArguments - return - } +}, 2, 2, tengo.TNs{tengo.StringTN}, nil) - s1, ok := args[0].(*tengo.String) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string", - Found: args[0].TypeName(), - } - return - } +var textParseInt = tengo.CheckArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1 := args[0].(*tengo.String) - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } - i3, ok := tengo.ToInt(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - return + i3, err := tengo.ToInt(2, args...) + if err != nil { + return nil, err } parsed, err := strconv.ParseInt(s1.Value, i2, i3) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil + } - ret = &tengo.Int{Value: parsed} + return &tengo.Int{Value: parsed}, nil - return -} +}, 3, 3, tengo.TNs{tengo.StringTN}, nil, nil) // Modified implementation of strings.Replace // to limit the maximum length of output string. diff --git a/stdlib/text_regexp.go b/stdlib/text_regexp.go index 1a7ecf07..bc21aa3f 100644 --- a/stdlib/text_regexp.go +++ b/stdlib/text_regexp.go @@ -11,63 +11,39 @@ func makeTextRegexp(re *regexp.Regexp) *tengo.ImmutableMap { Value: map[string]tengo.Object{ // match(text) => bool "match": &tengo.UserFunction{ - Value: func(args ...tengo.Object) ( - ret tengo.Object, - err error, + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } if re.MatchString(s1) { - ret = tengo.TrueValue - } else { - ret = tengo.FalseValue + return tengo.TrueValue, nil } - - return - }, + return tengo.FalseValue, nil + }, 1), }, // find(text) => array(array({text:,begin:,end:}))/undefined // find(text, maxCount) => array(array({text:,begin:,end:}))/undefined "find": &tengo.UserFunction{ - Value: func(args ...tengo.Object) ( - ret tengo.Object, - err error, + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { numArgs := len(args) - if numArgs != 1 && numArgs != 2 { - err = tengo.ErrWrongNumArguments - return - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } if numArgs == 1 { m := re.FindStringSubmatchIndex(s1) if m == nil { - ret = tengo.UndefinedValue - return + return tengo.UndefinedValue, nil } arr := &tengo.Array{} @@ -87,24 +63,16 @@ func makeTextRegexp(re *regexp.Regexp) *tengo.ImmutableMap { }}) } - ret = &tengo.Array{Value: []tengo.Object{arr}} - - return + return &tengo.Array{Value: []tengo.Object{arr}}, nil } - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } m := re.FindAllStringSubmatchIndex(s1, i2) if m == nil { - ret = tengo.UndefinedValue - return + return tengo.UndefinedValue, nil } arr := &tengo.Array{} @@ -129,41 +97,25 @@ func makeTextRegexp(re *regexp.Regexp) *tengo.ImmutableMap { arr.Value = append(arr.Value, subMatch) } - ret = arr - - return - }, + return arr, nil + }, 1, 2), }, // replace(src, repl) => string "replace": &tengo.UserFunction{ - Value: func(args ...tengo.Object) ( - ret tengo.Object, - err error, + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } s, ok := doTextRegexpReplace(re, s1, s2) @@ -171,45 +123,28 @@ func makeTextRegexp(re *regexp.Regexp) *tengo.ImmutableMap { return nil, tengo.ErrStringLimit } - ret = &tengo.String{Value: s} - - return - }, + return &tengo.String{Value: s}, nil + }, 2), }, // split(text) => array(string) // split(text, maxCount) => array(string) "split": &tengo.UserFunction{ - Value: func(args ...tengo.Object) ( - ret tengo.Object, - err error, + Value: tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { numArgs := len(args) - if numArgs != 1 && numArgs != 2 { - err = tengo.ErrWrongNumArguments - return - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } var i2 = -1 if numArgs > 1 { - i2, ok = tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err = tengo.ToInt(1, args...) + if err != nil { + return nil, err } } @@ -219,10 +154,8 @@ func makeTextRegexp(re *regexp.Regexp) *tengo.ImmutableMap { &tengo.String{Value: s}) } - ret = arr - - return - }, + return arr, nil + }, 1, 2), }, }, } diff --git a/stdlib/times.go b/stdlib/times.go index 0b6f7bd4..e1bba71b 100644 --- a/stdlib/times.go +++ b/stdlib/times.go @@ -182,954 +182,454 @@ var timesModule = map[string]tengo.Object{ }, // to_utc(time) => time } -func timesSleep(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return +var timesSleep = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } time.Sleep(time.Duration(i1)) - ret = tengo.UndefinedValue + return tengo.UndefinedValue, nil +}, 1) - return -} - -func timesParseDuration(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesParseDuration = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } dur, err := time.ParseDuration(s1) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil } - ret = &tengo.Int{Value: int64(dur)} - - return -} + return &tengo.Int{Value: int64(dur)}, nil +}, 1) -func timesSince(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesSince = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(time.Since(t1))} - - return -} + return &tengo.Int{Value: int64(time.Since(t1))}, nil +}, 1) -func timesUntil(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesUntil = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(time.Until(t1))} + return &tengo.Int{Value: int64(time.Until(t1))}, nil +}, 1) - return -} - -func timesDurationHours(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesDurationHours = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Float{Value: time.Duration(i1).Hours()} - - return -} + return &tengo.Float{Value: time.Duration(i1).Hours()}, nil +}, 1) -func timesDurationMinutes(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesDurationMinutes = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Float{Value: time.Duration(i1).Minutes()} - - return -} + return &tengo.Float{Value: time.Duration(i1).Minutes()}, nil +}, 1) -func timesDurationNanoseconds(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesDurationNanoseconds = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: time.Duration(i1).Nanoseconds()} + return &tengo.Int{Value: time.Duration(i1).Nanoseconds()}, nil +}, 1) - return -} - -func timesDurationSeconds(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesDurationSeconds = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Float{Value: time.Duration(i1).Seconds()} - - return -} + return &tengo.Float{Value: time.Duration(i1).Seconds()}, nil +}, 1) -func timesDurationString(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesDurationString = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } - ret = &tengo.String{Value: time.Duration(i1).String()} - - return -} + return &tengo.String{Value: time.Duration(i1).String()}, nil +}, 1) -func timesMonthString(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesMonthString = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - i1, ok := tengo.ToInt64(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } - ret = &tengo.String{Value: time.Month(i1).String()} + return &tengo.String{Value: time.Month(i1).String()}, nil +}, 1) - return -} - -func timesDate(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesDate = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 7 { - err = tengo.ErrWrongNumArguments - return - } - i1, ok := tengo.ToInt(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return + i1, err := tengo.ToInt(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } - i3, ok := tengo.ToInt(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - return + i3, err := tengo.ToInt(2, args...) + if err != nil { + return nil, err } - i4, ok := tengo.ToInt(args[3]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "fourth", - Expected: "int(compatible)", - Found: args[3].TypeName(), - } - return + i4, err := tengo.ToInt(3, args...) + if err != nil { + return nil, err } - i5, ok := tengo.ToInt(args[4]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "fifth", - Expected: "int(compatible)", - Found: args[4].TypeName(), - } - return + i5, err := tengo.ToInt(4, args...) + if err != nil { + return nil, err } - i6, ok := tengo.ToInt(args[5]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "sixth", - Expected: "int(compatible)", - Found: args[5].TypeName(), - } - return + i6, err := tengo.ToInt(5, args...) + if err != nil { + return nil, err } - i7, ok := tengo.ToInt(args[6]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "seventh", - Expected: "int(compatible)", - Found: args[6].TypeName(), - } - return + i7, err := tengo.ToInt(6, args...) + if err != nil { + return nil, err } - ret = &tengo.Time{ + return &tengo.Time{ Value: time.Date(i1, time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location()), - } - - return -} - -func timesNow(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 0 { - err = tengo.ErrWrongNumArguments - return - } - - ret = &tengo.Time{Value: time.Now()} - - return -} + }, nil +}, 7) -func timesParse(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } +var timesNow = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + return &tengo.Time{Value: time.Now()}, nil +}) - s1, ok := tengo.ToString(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), - } - return +var timesParse = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } parsed, err := time.Parse(s1, s2) if err != nil { - ret = wrapError(err) - return + return wrapError(err), nil } - ret = &tengo.Time{Value: parsed} - - return -} - -func timesUnix(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Time{Value: parsed}, nil +}, 2) - i1, ok := tengo.ToInt64(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "int(compatible)", - Found: args[0].TypeName(), - } - return +var timesUnix = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + i1, err := tengo.ToInt64(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt64(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt64(1, args...) + if err != nil { + return nil, err } - ret = &tengo.Time{Value: time.Unix(i1, i2)} - - return -} - -func timesAdd(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Time{Value: time.Unix(i1, i2)}, nil +}, 2) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesAdd = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt64(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt64(1, args...) + if err != nil { + return nil, err } - ret = &tengo.Time{Value: t1.Add(time.Duration(i2))} - - return -} - -func timesSub(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Time{Value: t1.Add(time.Duration(i2))}, nil +}, 2) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesSub = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - t2, ok := tengo.ToTime(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "time(compatible)", - Found: args[1].TypeName(), - } - return + t2, err := tengo.ToTime(1, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Sub(t2))} - - return -} - -func timesAddDate(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 4 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Int{Value: int64(t1.Sub(t2))}, nil +}, 2) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesAddDate = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - i2, ok := tengo.ToInt(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "int(compatible)", - Found: args[1].TypeName(), - } - return + i2, err := tengo.ToInt(1, args...) + if err != nil { + return nil, err } - i3, ok := tengo.ToInt(args[2]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "third", - Expected: "int(compatible)", - Found: args[2].TypeName(), - } - return + i3, err := tengo.ToInt(2, args...) + if err != nil { + return nil, err } - i4, ok := tengo.ToInt(args[3]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "fourth", - Expected: "int(compatible)", - Found: args[3].TypeName(), - } - return + i4, err := tengo.ToInt(3, args...) + if err != nil { + return nil, err } - ret = &tengo.Time{Value: t1.AddDate(i2, i3, i4)} + return &tengo.Time{Value: t1.AddDate(i2, i3, i4)}, nil +}, 4) - return -} - -func timesAfter(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesAfter = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - t2, ok := tengo.ToTime(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "time(compatible)", - Found: args[1].TypeName(), - } - return + t2, err := tengo.ToTime(1, args...) + if err != nil { + return nil, err } if t1.After(t2) { - ret = tengo.TrueValue - } else { - ret = tengo.FalseValue - } - - return -} - -func timesBefore(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return + return tengo.TrueValue, nil } + return tengo.FalseValue, nil +}, 2) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesBefore = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - t2, ok := tengo.ToTime(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return + t2, err := tengo.ToTime(1, args...) + if err != nil { + return nil, err } if t1.Before(t2) { - ret = tengo.TrueValue - } else { - ret = tengo.FalseValue - } - - return -} - -func timesTimeYear(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return + return tengo.TrueValue, nil } + return tengo.FalseValue, nil +}, 2) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeYear = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Year())} - - return -} - -func timesTimeMonth(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Int{Value: int64(t1.Year())}, nil +}, 1) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeMonth = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Month())} - - return -} - -func timesTimeDay(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Int{Value: int64(t1.Month())}, nil +}, 1) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeDay = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Day())} - - return -} - -func timesTimeWeekday(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Int{Value: int64(t1.Day())}, nil +}, 1) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeWeekday = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Weekday())} - - return -} - -func timesTimeHour(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Int{Value: int64(t1.Weekday())}, nil +}, 1) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeHour = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Hour())} + return &tengo.Int{Value: int64(t1.Hour())}, nil +}, 1) - return -} - -func timesTimeMinute(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeMinute = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Minute())} - - return -} - -func timesTimeSecond(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Int{Value: int64(t1.Minute())}, nil +}, 1) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeSecond = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Second())} - - return -} + return &tengo.Int{Value: int64(t1.Second())}, nil +}, 1) -func timesTimeNanosecond(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesTimeNanosecond = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: int64(t1.Nanosecond())} - - return -} - -func timesTimeUnix(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Int{Value: int64(t1.Nanosecond())}, nil +}, 1) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeUnix = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: t1.Unix()} - - return -} + return &tengo.Int{Value: t1.Unix()}, nil +}, 1) -func timesTimeUnixNano(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesTimeUnixNano = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Int{Value: t1.UnixNano()} - - return -} - -func timesTimeFormat(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 2 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.Int{Value: t1.UnixNano()}, nil +}, 1) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeFormat = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - s2, ok := tengo.ToString(args[1]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "second", - Expected: "string(compatible)", - Found: args[1].TypeName(), - } - return + s2, err := tengo.ToString(1, args...) + if err != nil { + return nil, err } s := t1.Format(s2) if len(s) > tengo.MaxStringLen { - return nil, tengo.ErrStringLimit } - ret = &tengo.String{Value: s} - - return -} - -func timesIsZero(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } + return &tengo.String{Value: s}, nil +}, 2) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesIsZero = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } if t1.IsZero() { - ret = tengo.TrueValue - } else { - ret = tengo.FalseValue - } - - return -} - -func timesToLocal(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return + return tengo.TrueValue, nil } + return tengo.FalseValue, nil +}, 1) - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesToLocal = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Time{Value: t1.Local()} + return &tengo.Time{Value: t1.Local()}, nil +}, 1) - return -} - -func timesToUTC(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesToUTC = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.Time{Value: t1.UTC()} + return &tengo.Time{Value: t1.UTC()}, nil +}, 1) - return -} - -func timesTimeLocation(args ...tengo.Object) ( - ret tengo.Object, - err error, +var timesTimeLocation = tengo.CheckAnyArgs(func(args ...tengo.Object) ( + tengo.Object, + error, ) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.String{Value: t1.Location().String()} - - return -} + return &tengo.String{Value: t1.Location().String()}, nil +}, 1) -func timesTimeString(args ...tengo.Object) (ret tengo.Object, err error) { - if len(args) != 1 { - err = tengo.ErrWrongNumArguments - return - } - - t1, ok := tengo.ToTime(args[0]) - if !ok { - err = tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "time(compatible)", - Found: args[0].TypeName(), - } - return +var timesTimeString = tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + t1, err := tengo.ToTime(0, args...) + if err != nil { + return nil, err } - ret = &tengo.String{Value: t1.String()} - - return -} + return &tengo.String{Value: t1.String()}, nil +}, 1) diff --git a/tengo.go b/tengo.go index 098a1970..ee0e017e 100644 --- a/tengo.go +++ b/tengo.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "time" ) @@ -29,13 +30,13 @@ const ( ) // CallableFunc is a function signature for the callable functions. -type CallableFunc = func(args ...Object) (ret Object, err error) +type CallableFunc = func(args ...Object) (Object, error) // CountObjects returns the number of objects that a given object o contains. // For scalar value types, it will always be 1. For compound value types, // this will include its elements and all of their elements recursively. -func CountObjects(o Object) (c int) { - c = 1 +func CountObjects(o Object) int { + c := 1 switch o := o.(type) { case *Array: for _, v := range o.Value { @@ -56,187 +57,318 @@ func CountObjects(o Object) (c int) { case *Error: c += CountObjects(o.Value) } - return + return c } -// ToString will try to convert object o to string value. -func ToString(o Object) (v string, ok bool) { +// ToString will try to convert arg at index i to string value. +func ToString(i int, args ...Object) (string, error) { + o := args[i] if o == UndefinedValue { - return + return "", &ErrInvalidArgumentType{ + Index: i, + Expected: "not undefined", + Actual: UndefinedTN, + } } - ok = true if str, isStr := o.(*String); isStr { - v = str.Value - } else { - v = o.String() + return str.Value, nil } - return + return o.String(), nil } -// ToInt will try to convert object o to int value. -func ToInt(o Object) (v int, ok bool) { - switch o := o.(type) { +// ToStringSlice will try to convert object o to string slice value. +func ToStringSlice(i int, args ...Object) ([]string, error) { + var ss []string + switch o := args[i].(type) { + case *Array: + for idx := range o.Value { + as, err := ToString(idx, o.Value...) + if err != nil { + return nil, err + } + ss = append(ss, as) + } + case *ImmutableArray: + for idx := range o.Value { + as, err := ToString(idx, o.Value...) + if err != nil { + return nil, err + } + ss = append(ss, as) + } + default: + return nil, ErrInvalidArgumentType{ + Index: i, + Expected: ArrayTN, + Actual: args[i].TypeName(), + } + } + return ss, nil +} + +// ToInt will try to convert a specific arg at index i to an int. +func ToInt(i int, args ...Object) (int, error) { + switch o := args[i].(type) { case *Int: - v = int(o.Value) - ok = true + return int(o.Value), nil case *Float: - v = int(o.Value) - ok = true + return int(o.Value), nil case *Char: - v = int(o.Value) - ok = true + return int(o.Value), nil case *Bool: if o == TrueValue { - v = 1 + return 1, nil } - ok = true + return 0, nil case *String: c, err := strconv.ParseInt(o.Value, 10, 64) if err == nil { - v = int(c) - ok = true + return int(c), nil } } - return + return 0, ErrInvalidArgumentType{ + Index: i, + Expected: strings.Join(TNs{IntTN, FloatTN, CharTN, BoolTN, StringTN}, "/"), + Actual: args[i].TypeName(), + } +} + +// ToIntSlice will try to convert object o to int slice value. +func ToIntSlice(i int, args ...Object) ([]int, error) { + var is []int + switch o := args[i].(type) { + case *Array: + for idx := range o.Value { + as, err := ToInt(idx, o.Value...) + if err != nil { + return nil, err + } + is = append(is, as) + } + case *ImmutableArray: + for idx := range o.Value { + as, err := ToInt(idx, o.Value...) + if err != nil { + return nil, err + } + is = append(is, as) + } + default: + return nil, ErrInvalidArgumentType{ + Index: i, + Expected: ArrayTN, + Actual: args[i].TypeName(), + } + } + return is, nil } // ToInt64 will try to convert object o to int64 value. -func ToInt64(o Object) (v int64, ok bool) { - switch o := o.(type) { +func ToInt64(i int, args ...Object) (int64, error) { + switch o := args[i].(type) { case *Int: - v = o.Value - ok = true + return o.Value, nil case *Float: - v = int64(o.Value) - ok = true + return int64(o.Value), nil case *Char: - v = int64(o.Value) - ok = true + return int64(o.Value), nil case *Bool: if o == TrueValue { - v = 1 + return 1, nil } - ok = true + return 0, nil case *String: c, err := strconv.ParseInt(o.Value, 10, 64) if err == nil { - v = c - ok = true + return c, nil } } - return + return 0, ErrInvalidArgumentType{ + Index: i, + Expected: strings.Join(TNs{IntTN, FloatTN, CharTN, BoolTN, StringTN}, "/"), + Actual: args[i].TypeName(), + } +} + +// ToInt64Slice will try to convert object o to int64 slice value. +func ToInt64Slice(i int, args ...Object) ([]int64, error) { + var is []int64 + switch o := args[i].(type) { + case *Array: + for idx := range o.Value { + as, err := ToInt64(idx, o.Value...) + if err != nil { + return nil, err + } + is = append(is, as) + } + case *ImmutableArray: + for idx := range o.Value { + as, err := ToInt64(idx, o.Value...) + if err != nil { + return nil, err + } + is = append(is, as) + } + default: + return nil, ErrInvalidArgumentType{ + Index: i, + Expected: ArrayTN, + Actual: args[i].TypeName(), + } + } + return is, nil } // ToFloat64 will try to convert object o to float64 value. -func ToFloat64(o Object) (v float64, ok bool) { - switch o := o.(type) { +func ToFloat64(i int, args ...Object) (float64, error) { + switch o := args[i].(type) { case *Int: - v = float64(o.Value) - ok = true + return float64(o.Value), nil case *Float: - v = o.Value - ok = true + return o.Value, nil case *String: c, err := strconv.ParseFloat(o.Value, 64) if err == nil { - v = c - ok = true + return c, nil + } + } + return 0, ErrInvalidArgumentType{ + Index: i, + Expected: strings.Join(TNs{IntTN, FloatTN, StringTN}, "/"), + Actual: args[i].TypeName(), + } +} + +// ToFloat64Slice will try to convert object o to float64 slice value. +func ToFloat64Slice(i int, args ...Object) ([]float64, error) { + var fs []float64 + switch o := args[i].(type) { + case *Array: + for idx := range o.Value { + as, err := ToFloat64(idx, o.Value...) + if err != nil { + return nil, err + } + fs = append(fs, as) + } + case *ImmutableArray: + for idx := range o.Value { + as, err := ToFloat64(idx, o.Value...) + if err != nil { + return nil, err + } + fs = append(fs, as) + } + default: + return nil, ErrInvalidArgumentType{ + Index: i, + Expected: ArrayTN, + Actual: args[i].TypeName(), } } - return + return fs, nil } // ToBool will try to convert object o to bool value. -func ToBool(o Object) (v bool, ok bool) { - ok = true - v = !o.IsFalsy() - return +func ToBool(i int, args ...Object) bool { + return !args[i].IsFalsy() } // ToRune will try to convert object o to rune value. -func ToRune(o Object) (v rune, ok bool) { - switch o := o.(type) { +func ToRune(i int, args ...Object) (rune, error) { + switch o := args[i].(type) { case *Int: - v = rune(o.Value) - ok = true + return rune(o.Value), nil case *Char: - v = o.Value - ok = true + return o.Value, nil + } + return 0, ErrInvalidArgumentType{ + Index: i, + Expected: strings.Join(TNs{IntTN, CharTN}, "/"), + Actual: args[i].TypeName(), } - return } // ToByteSlice will try to convert object o to []byte value. -func ToByteSlice(o Object) (v []byte, ok bool) { - switch o := o.(type) { +func ToByteSlice(i int, args ...Object) ([]byte, error) { + switch o := args[i].(type) { case *Bytes: - v = o.Value - ok = true + return o.Value, nil case *String: - v = []byte(o.Value) - ok = true + return []byte(o.Value), nil + } + return nil, ErrInvalidArgumentType{ + Index: i, + Expected: strings.Join(TNs{BytesTN, StringTN}, "/"), + Actual: args[i].TypeName(), } - return } // ToTime will try to convert object o to time.Time value. -func ToTime(o Object) (v time.Time, ok bool) { - switch o := o.(type) { +func ToTime(i int, args ...Object) (time.Time, error) { + switch o := args[i].(type) { case *Time: - v = o.Value - ok = true + return o.Value, nil case *Int: - v = time.Unix(o.Value, 0) - ok = true + return time.Unix(o.Value, 0), nil + } + return time.Time{}, ErrInvalidArgumentType{ + Index: i, + Expected: strings.Join(TNs{TimeTN, IntTN}, "/"), + Actual: args[i].TypeName(), } - return } // ToInterface attempts to convert an object o to an interface{} value -func ToInterface(o Object) (res interface{}) { +func ToInterface(o Object) interface{} { switch o := o.(type) { case *Int: - res = o.Value + return o.Value case *String: - res = o.Value + return o.Value case *Float: - res = o.Value + return o.Value case *Bool: - res = o == TrueValue + return o == TrueValue case *Char: - res = o.Value + return o.Value case *Bytes: - res = o.Value + return o.Value case *Array: - res = make([]interface{}, len(o.Value)) + res := make([]interface{}, len(o.Value)) for i, val := range o.Value { - res.([]interface{})[i] = ToInterface(val) + res[i] = ToInterface(val) } + return res case *ImmutableArray: - res = make([]interface{}, len(o.Value)) + res := make([]interface{}, len(o.Value)) for i, val := range o.Value { - res.([]interface{})[i] = ToInterface(val) + res[i] = ToInterface(val) } + return res case *Map: - res = make(map[string]interface{}) + res := make(map[string]interface{}) for key, v := range o.Value { - res.(map[string]interface{})[key] = ToInterface(v) + res[key] = ToInterface(v) } + return res case *ImmutableMap: - res = make(map[string]interface{}) + res := make(map[string]interface{}) for key, v := range o.Value { - res.(map[string]interface{})[key] = ToInterface(v) + res[key] = ToInterface(v) } + return res case *Time: - res = o.Value + return o.Value case *Error: - res = errors.New(o.String()) + return errors.New(o.String()) case *Undefined: - res = nil + return nil case Object: return o } - return + return nil } // FromInterface will attempt to convert an interface{} v to a Tengo Object diff --git a/variable.go b/variable.go index 481b36b8..6df44baa 100644 --- a/variable.go +++ b/variable.go @@ -40,36 +40,35 @@ func (v *Variable) ValueType() string { // Int returns int value of the variable value. // It returns 0 if the value is not convertible to int. func (v *Variable) Int() int { - c, _ := ToInt(v.value) + c, _ := ToInt(0, v.value) return c } // Int64 returns int64 value of the variable value. It returns 0 if the value // is not convertible to int64. func (v *Variable) Int64() int64 { - c, _ := ToInt64(v.value) + c, _ := ToInt64(0, v.value) return c } // Float returns float64 value of the variable value. It returns 0.0 if the // value is not convertible to float64. func (v *Variable) Float() float64 { - c, _ := ToFloat64(v.value) + c, _ := ToFloat64(0, v.value) return c } // Char returns rune value of the variable value. It returns 0 if the value is // not convertible to rune. func (v *Variable) Char() rune { - c, _ := ToRune(v.value) + c, _ := ToRune(0, v.value) return c } // Bool returns bool value of the variable value. It returns 0 if the value is // not convertible to bool. func (v *Variable) Bool() bool { - c, _ := ToBool(v.value) - return c + return ToBool(0, v.value) } // Array returns []interface value of the variable value. It returns 0 if the @@ -103,14 +102,14 @@ func (v *Variable) Map() map[string]interface{} { // String returns string value of the variable value. It returns 0 if the value // is not convertible to string. func (v *Variable) String() string { - c, _ := ToString(v.value) + c, _ := ToString(0, v.value) return c } // Bytes returns a byte slice of the variable value. It returns nil if the // value is not convertible to byte slice. func (v *Variable) Bytes() []byte { - c, _ := ToByteSlice(v.value) + c, _ := ToByteSlice(0, v.value) return c } diff --git a/vm.go b/vm.go index 811ecef9..f65d9a61 100644 --- a/vm.go +++ b/vm.go @@ -65,7 +65,7 @@ func (v *VM) Abort() { } // Run starts the execution. -func (v *VM) Run() (err error) { +func (v *VM) Run() error { // reset VM states v.sp = 0 v.curFrame = &(v.frames[0]) @@ -76,7 +76,7 @@ func (v *VM) Run() (err error) { v.run() atomic.StoreInt64(&v.aborting, 0) - err = v.err + err := v.err if err != nil { filePos := v.fileSet.Position( v.curFrame.fn.SourcePos(v.ip - 1)) @@ -635,17 +635,16 @@ func (v *VM) run() { // runtime error if e != nil { - if e == ErrWrongNumArguments { + if e, ok := e.(ErrInvalidArgumentCount); ok { v.err = fmt.Errorf( - "wrong number of arguments in call to '%s'", - value.TypeName()) + "%s: %s", + value.TypeName(), e.Error()) return } if e, ok := e.(ErrInvalidArgumentType); ok { v.err = fmt.Errorf( - "invalid type for argument '%s' in call to '%s': "+ - "expected %s, found %s", - e.Name, value.TypeName(), e.Expected, e.Found) + "%s: %s", + value.TypeName(), e.Error()) return } v.err = e diff --git a/vm_test.go b/vm_test.go index feb91f4f..424b2c5a 100644 --- a/vm_test.go +++ b/vm_test.go @@ -557,11 +557,11 @@ func TestBuiltinFunction(t *testing.T) { expectRun(t, `out = len(immutable([1, 2, 3]))`, nil, 3) expectRun(t, `out = len(immutable({}))`, nil, 0) expectRun(t, `out = len(immutable({a:1, b:2}))`, nil, 2) - expectError(t, `len(1)`, nil, "invalid type for argument") - expectError(t, `len("one", "two")`, nil, "wrong number of arguments") + expectError(t, `len(1)`, nil, "arg type int does not have a length value") + expectError(t, `len("one", "two")`, nil, "invalid argument count, expected 1, actual 2") expectRun(t, `out = copy(1)`, nil, 1) - expectError(t, `copy(1, 2)`, nil, "wrong number of arguments") + expectError(t, `copy(1, 2)`, nil, "builtin-function:copy: invalid argument count, expected 1, actual 2") expectRun(t, `out = append([1, 2, 3], 4)`, nil, ARR{1, 2, 3, 4}) expectRun(t, `out = append([1, 2, 3], 4, 5, 6)`, nil, ARR{1, 2, 3, 4, 5, 6}) @@ -728,48 +728,15 @@ func TestBuiltinFunction(t *testing.T) { tengo.MaxStringLen = 2147483647 // delete - expectError(t, `delete()`, nil, tengo.ErrWrongNumArguments.Error()) - expectError(t, `delete(1)`, nil, tengo.ErrWrongNumArguments.Error()) - expectError(t, `delete(1, 2, 3)`, nil, tengo.ErrWrongNumArguments.Error()) - expectError(t, `delete({}, "", 3)`, nil, tengo.ErrWrongNumArguments.Error()) - expectError(t, `delete(1, 1)`, nil, `invalid type for argument 'first'`) - expectError(t, `delete(1.0, 1)`, nil, `invalid type for argument 'first'`) - expectError(t, `delete("str", 1)`, nil, `invalid type for argument 'first'`) - expectError(t, `delete(bytes("str"), 1)`, nil, - `invalid type for argument 'first'`) - expectError(t, `delete(error("err"), 1)`, nil, - `invalid type for argument 'first'`) - expectError(t, `delete(true, 1)`, nil, `invalid type for argument 'first'`) - expectError(t, `delete(char('c'), 1)`, nil, - `invalid type for argument 'first'`) - expectError(t, `delete(undefined, 1)`, nil, - `invalid type for argument 'first'`) - expectError(t, `delete(time(1257894000), 1)`, nil, - `invalid type for argument 'first'`) - expectError(t, `delete(immutable({}), "key")`, nil, - `invalid type for argument 'first'`) - expectError(t, `delete(immutable([]), "")`, nil, - `invalid type for argument 'first'`) - expectError(t, `delete([], "")`, nil, `invalid type for argument 'first'`) - expectError(t, `delete({}, 1)`, nil, `invalid type for argument 'second'`) - expectError(t, `delete({}, 1.0)`, nil, `invalid type for argument 'second'`) - expectError(t, `delete({}, undefined)`, nil, - `invalid type for argument 'second'`) - expectError(t, `delete({}, [])`, nil, `invalid type for argument 'second'`) - expectError(t, `delete({}, {})`, nil, `invalid type for argument 'second'`) - expectError(t, `delete({}, error("err"))`, nil, - `invalid type for argument 'second'`) - expectError(t, `delete({}, bytes("str"))`, nil, - `invalid type for argument 'second'`) - expectError(t, `delete({}, char(35))`, nil, - `invalid type for argument 'second'`) - expectError(t, `delete({}, time(1257894000))`, nil, - `invalid type for argument 'second'`) - expectError(t, `delete({}, immutable({}))`, nil, - `invalid type for argument 'second'`) - expectError(t, `delete({}, immutable([]))`, nil, - `invalid type for argument 'second'`) - + expectError(t, `delete()`, nil, tengo.ErrInvalidArgumentCount{ + Min: 2, Max: 2, Actual: 0, + }.Error()) + expectError(t, `delete(1)`, nil, tengo.ErrInvalidArgumentCount{ + Min: 2, Max: 2, Actual: 1, + }.Error()) + expectError(t, `delete(1, 2, 3)`, nil, "builtin-function:delete: invalid argument count, expected 2, actual 3") + expectError(t, `delete(1, 1)`, nil, `builtin-function:delete: invalid type for argument at index 0: expected map, actual int`) + expectError(t, `delete({}, 1)`, nil, `builtin-function:delete: invalid type for argument at index 1: expected string, actual int`) expectRun(t, `out = delete({}, "")`, nil, tengo.UndefinedValue) expectRun(t, `out = {key1: 1}; delete(out, "key1")`, nil, MAP{}) expectRun(t, `out = {key1: 1, key2: "2"}; delete(out, "key1")`, nil, @@ -778,74 +745,16 @@ func TestBuiltinFunction(t *testing.T) { ARR{1, "2", MAP{"a": "b"}}) // splice - expectError(t, `splice()`, nil, tengo.ErrWrongNumArguments.Error()) - expectError(t, `splice(1)`, nil, `invalid type for argument 'first'`) - expectError(t, `splice(1.0)`, nil, `invalid type for argument 'first'`) - expectError(t, `splice("str")`, nil, `invalid type for argument 'first'`) - expectError(t, `splice(bytes("str"))`, nil, - `invalid type for argument 'first'`) - expectError(t, `splice(error("err"))`, nil, - `invalid type for argument 'first'`) - expectError(t, `splice(true)`, nil, `invalid type for argument 'first'`) - expectError(t, `splice(char('c'))`, nil, - `invalid type for argument 'first'`) - expectError(t, `splice(undefined)`, nil, - `invalid type for argument 'first'`) - expectError(t, `splice(time(1257894000))`, nil, - `invalid type for argument 'first'`) - expectError(t, `splice(immutable({}))`, nil, - `invalid type for argument 'first'`) - expectError(t, `splice(immutable([]))`, nil, - `invalid type for argument 'first'`) - expectError(t, `splice({})`, nil, `invalid type for argument 'first'`) + expectError(t, `splice()`, nil, tengo.ErrInvalidArgumentCount{ + Min: 1, Max: -1, Actual: 0, + }.Error()) + expectError(t, `splice(1)`, nil, `builtin-function:splice: invalid type for argument at index 0: expected array, actual int`) expectError(t, `splice([], 1.0)`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], "str")`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], bytes("str"))`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], error("error"))`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], false)`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], char('d'))`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], undefined)`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], time(0))`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], [])`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], {})`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], immutable([]))`, nil, - `invalid type for argument 'second'`) - expectError(t, `splice([], immutable({}))`, nil, - `invalid type for argument 'second'`) + `builtin-function:splice: invalid type for argument at index 1: expected int, actual float`) + expectError(t, `splice([], 0, 1.0)`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, "string")`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, bytes("string"))`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, error("string"))`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, true)`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, char('f'))`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, undefined)`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, time(0))`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, [])`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, {})`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, immutable([]))`, nil, - `invalid type for argument 'third'`) - expectError(t, `splice([], 0, immutable({}))`, nil, - `invalid type for argument 'third'`) + `builtin-function:splice: invalid type for argument at index 2: expected int, actual float`) + expectError(t, `splice([], 1)`, nil, tengo.ErrIndexOutOfBounds.Error()) expectError(t, `splice([1, 2, 3], 0, -1)`, nil, tengo.ErrIndexOutOfBounds.Error()) @@ -2098,12 +2007,14 @@ type StringDict struct { Value map[string]string } -func (o *StringDict) String() string { return "" } +const StringDictTN = "string-dict" func (o *StringDict) TypeName() string { - return "string-dict" + return StringDictTN } +func (o *StringDict) String() string { return "" } + func (o *StringDict) IndexGet(index tengo.Object) (tengo.Object, error) { strIdx, ok := index.(*tengo.String) if !ok { @@ -2125,9 +2036,9 @@ func (o *StringDict) IndexSet(index, value tengo.Object) error { return tengo.ErrInvalidIndexType } - strVal, ok := tengo.ToString(value) - if !ok { - return tengo.ErrInvalidIndexValueType + strVal, err := tengo.ToString(0, value) + if err != nil { + return err } o.Value[strings.ToLower(strIdx.Value)] = strVal @@ -2140,8 +2051,10 @@ type StringCircle struct { Value []string } +const StringCircleTN = "string-circle" + func (o *StringCircle) TypeName() string { - return "string-circle" + return StringCircleTN } func (o *StringCircle) String() string { @@ -2173,9 +2086,9 @@ func (o *StringCircle) IndexSet(index, value tengo.Object) error { r = len(o.Value) + r } - strVal, ok := tengo.ToString(value) - if !ok { - return tengo.ErrInvalidIndexValueType + strVal, err := tengo.ToString(0, value) + if err != nil { + return err } o.Value[r] = strVal @@ -2237,8 +2150,10 @@ func (o *StringArray) Copy() tengo.Object { } } +const StringArrayTN = "string-array" + func (o *StringArray) TypeName() string { - return "string-array" + return StringArrayTN } func (o *StringArray) IndexGet(index tengo.Object) (tengo.Object, error) { @@ -2266,9 +2181,9 @@ func (o *StringArray) IndexGet(index tengo.Object) (tengo.Object, error) { } func (o *StringArray) IndexSet(index, value tengo.Object) error { - strVal, ok := tengo.ToString(value) - if !ok { - return tengo.ErrInvalidIndexValueType + strVal, err := tengo.ToString(0, value) + if err != nil { + return err } intIdx, ok := index.(*tengo.Int) @@ -2286,27 +2201,21 @@ func (o *StringArray) IndexSet(index, value tengo.Object) error { func (o *StringArray) Call( args ...tengo.Object, -) (ret tengo.Object, err error) { - if len(args) != 1 { - return nil, tengo.ErrWrongNumArguments - } - - s1, ok := tengo.ToString(args[0]) - if !ok { - return nil, tengo.ErrInvalidArgumentType{ - Name: "first", - Expected: "string(compatible)", - Found: args[0].TypeName(), +) (tengo.Object, error) { + return tengo.CheckAnyArgs(func(args ...tengo.Object) (tengo.Object, error) { + s1, err := tengo.ToString(0, args...) + if err != nil { + return nil, err } - } - for i, v := range o.Value { - if v == s1 { - return &tengo.Int{Value: int64(i)}, nil + for i, v := range o.Value { + if v == s1 { + return &tengo.Int{Value: int64(i)}, nil + } } - } - return tengo.UndefinedValue, nil + return tengo.UndefinedValue, nil + }, 1)(args...) } func (o *StringArray) CanCall() bool { @@ -2428,8 +2337,10 @@ type StringArrayIterator struct { idx int } +const StringArrayIteratorTN = "string-array-iterator" + func (i *StringArrayIterator) TypeName() string { - return "string-array-iterator" + return StringArrayIteratorTN } func (i *StringArrayIterator) String() string { @@ -2572,7 +2483,7 @@ func TestBuiltin(t *testing.T) { "abs": &tengo.UserFunction{ Name: "abs", Value: func(a ...tengo.Object) (tengo.Object, error) { - v, _ := tengo.ToFloat64(a[0]) + v, _ := tengo.ToFloat64(0, a...) return &tengo.Float{Value: math.Abs(v)}, nil }, }, @@ -2778,7 +2689,7 @@ func TestModuleBlockScopes(t *testing.T) { "intn": &tengo.UserFunction{ Name: "abs", Value: func(a ...tengo.Object) (tengo.Object, error) { - v, _ := tengo.ToInt64(a[0]) + v, _ := tengo.ToInt64(0, a...) return &tengo.Int{Value: rand.Int63n(v)}, nil }, }, @@ -3092,13 +3003,13 @@ func() { expectError(t, `a := {b: {c: 1}}; a.d.c = 2`, nil, "not index-assignable") expectError(t, `a := [1, 2, 3]; a.b = 2`, - nil, "invalid index type") + nil, "invalid type for argument at index 0: expected int/float/char/bool/string, actual string") expectError(t, `a := "foo"; a.b = 2`, nil, "not index-assignable") expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, nil, "not index-assignable") expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, - nil, "invalid index type") + nil, "invalid type for argument at index 0: expected int/float/char/bool/string, actual string") expectError(t, `func() { a := "foo"; a.b = 2 }()`, nil, "not index-assignable") } @@ -3777,7 +3688,7 @@ type vmTracer struct { Out []string } -func (o *vmTracer) Write(p []byte) (n int, err error) { +func (o *vmTracer) Write(p []byte) (int, error) { o.Out = append(o.Out, string(p)) return len(p), nil }