* builtin range function #326 * change empty range logic * fix unit test error message * fix github env (#329) * fix ErrInvalidRangeStep comments * fix github env (#329) * builtin range function #326 * change empty range logic * fix unit test error message * fix ErrInvalidRangeStep comments * fix lint Co-authored-by: geseq <5458743+geseq@users.noreply.github.com>
This commit is contained in:
parent
c51d02f80e
commit
885830428b
3 changed files with 225 additions and 0 deletions
69
builtins.go
69
builtins.go
|
@ -121,6 +121,10 @@ var builtinFuncs = []*BuiltinFunction{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
Value: builtinFormat,
|
Value: builtinFormat,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "range",
|
||||||
|
Value: builtinRange,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllBuiltinFunctions returns all builtin function objects.
|
// GetAllBuiltinFunctions returns all builtin function objects.
|
||||||
|
@ -323,6 +327,71 @@ func builtinLen(args ...Object) (Object, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i == 2 && v.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
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRange(start, stop, step int64) *Array {
|
||||||
|
array := &Array{}
|
||||||
|
if start <= stop {
|
||||||
|
for i := start; i < stop; i += step {
|
||||||
|
array.Value = append(array.Value, &Int{
|
||||||
|
Value: i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := start; i > stop; i -= step {
|
||||||
|
array.Value = append(array.Value, &Int{
|
||||||
|
Value: i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
func builtinFormat(args ...Object) (Object, error) {
|
func builtinFormat(args ...Object) (Object, error) {
|
||||||
numArgs := len(args)
|
numArgs := len(args)
|
||||||
if numArgs == 0 {
|
if numArgs == 0 {
|
||||||
|
|
153
builtins_test.go
153
builtins_test.go
|
@ -351,3 +351,156 @@ func Test_builtinSplice(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_builtinRange(t *testing.T) {
|
||||||
|
var builtinRange func(args ...tengo.Object) (tengo.Object, error)
|
||||||
|
for _, f := range tengo.GetAllBuiltinFunctions() {
|
||||||
|
if f.Name == "range" {
|
||||||
|
builtinRange = f.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if builtinRange == nil {
|
||||||
|
t.Fatal("builtin range not found")
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []tengo.Object
|
||||||
|
result *tengo.Array
|
||||||
|
wantErr bool
|
||||||
|
wantedErr error
|
||||||
|
}{
|
||||||
|
{name: "no args", args: []tengo.Object{}, wantErr: true,
|
||||||
|
wantedErr: tengo.ErrWrongNumArguments,
|
||||||
|
},
|
||||||
|
{name: "single args", args: []tengo.Object{&tengo.Map{}},
|
||||||
|
wantErr: true,
|
||||||
|
wantedErr: tengo.ErrWrongNumArguments,
|
||||||
|
},
|
||||||
|
{name: "4 args", args: []tengo.Object{&tengo.Map{}, &tengo.String{}, &tengo.String{}, &tengo.String{}},
|
||||||
|
wantErr: true,
|
||||||
|
wantedErr: tengo.ErrWrongNumArguments,
|
||||||
|
},
|
||||||
|
{name: "invalid start",
|
||||||
|
args: []tengo.Object{&tengo.String{}, &tengo.String{}},
|
||||||
|
wantErr: true,
|
||||||
|
wantedErr: tengo.ErrInvalidArgumentType{
|
||||||
|
Name: "start", Expected: "int", Found: "string"},
|
||||||
|
},
|
||||||
|
{name: "invalid stop",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.String{}},
|
||||||
|
wantErr: true,
|
||||||
|
wantedErr: tengo.ErrInvalidArgumentType{
|
||||||
|
Name: "stop", Expected: "int", Found: "string"},
|
||||||
|
},
|
||||||
|
{name: "invalid step",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.Int{}, &tengo.String{}},
|
||||||
|
wantErr: true,
|
||||||
|
wantedErr: tengo.ErrInvalidArgumentType{
|
||||||
|
Name: "step", Expected: "int", Found: "string"},
|
||||||
|
},
|
||||||
|
{name: "zero step",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.Int{}, &tengo.Int{}}, //must greate than 0
|
||||||
|
wantErr: true,
|
||||||
|
wantedErr: tengo.ErrInvalidRangeStep,
|
||||||
|
},
|
||||||
|
{name: "negative step",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.Int{}, intObject(-2)}, //must greate than 0
|
||||||
|
wantErr: true,
|
||||||
|
wantedErr: tengo.ErrInvalidRangeStep,
|
||||||
|
},
|
||||||
|
{name: "same bound",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.Int{}},
|
||||||
|
wantErr: false,
|
||||||
|
result: &tengo.Array{
|
||||||
|
Value: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "positive range",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.Int{Value: 5}},
|
||||||
|
wantErr: false,
|
||||||
|
result: &tengo.Array{
|
||||||
|
Value: []tengo.Object{
|
||||||
|
intObject(0),
|
||||||
|
intObject(1),
|
||||||
|
intObject(2),
|
||||||
|
intObject(3),
|
||||||
|
intObject(4),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "negative range",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.Int{Value: -5}},
|
||||||
|
wantErr: false,
|
||||||
|
result: &tengo.Array{
|
||||||
|
Value: []tengo.Object{
|
||||||
|
intObject(0),
|
||||||
|
intObject(-1),
|
||||||
|
intObject(-2),
|
||||||
|
intObject(-3),
|
||||||
|
intObject(-4),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{name: "positive with step",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.Int{Value: 5}, &tengo.Int{Value: 2}},
|
||||||
|
wantErr: false,
|
||||||
|
result: &tengo.Array{
|
||||||
|
Value: []tengo.Object{
|
||||||
|
intObject(0),
|
||||||
|
intObject(2),
|
||||||
|
intObject(4),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{name: "negative with step",
|
||||||
|
args: []tengo.Object{&tengo.Int{}, &tengo.Int{Value: -10}, &tengo.Int{Value: 2}},
|
||||||
|
wantErr: false,
|
||||||
|
result: &tengo.Array{
|
||||||
|
Value: []tengo.Object{
|
||||||
|
intObject(0),
|
||||||
|
intObject(-2),
|
||||||
|
intObject(-4),
|
||||||
|
intObject(-6),
|
||||||
|
intObject(-8),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{name: "large range",
|
||||||
|
args: []tengo.Object{intObject(-10), intObject(10), &tengo.Int{Value: 3}},
|
||||||
|
wantErr: false,
|
||||||
|
result: &tengo.Array{
|
||||||
|
Value: []tengo.Object{
|
||||||
|
intObject(-10),
|
||||||
|
intObject(-7),
|
||||||
|
intObject(-4),
|
||||||
|
intObject(-1),
|
||||||
|
intObject(2),
|
||||||
|
intObject(5),
|
||||||
|
intObject(8),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := builtinRange(tt.args...)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("builtinRange() error = %v, wantErr %v",
|
||||||
|
err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantErr && tt.wantedErr.Error() != err.Error() {
|
||||||
|
t.Errorf("builtinRange() error = %v, wantedErr %v",
|
||||||
|
err, tt.wantedErr)
|
||||||
|
}
|
||||||
|
if tt.result != nil && !reflect.DeepEqual(tt.result, got) {
|
||||||
|
t.Errorf("builtinRange() arrays are not equal expected"+
|
||||||
|
" %s, got %s", tt.result, got.(*tengo.Array))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -49,6 +49,9 @@ var (
|
||||||
// ErrNotImplemented is an error where an Object has not implemented a
|
// ErrNotImplemented is an error where an Object has not implemented a
|
||||||
// required method.
|
// required method.
|
||||||
ErrNotImplemented = errors.New("not implemented")
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
|
||||||
|
// ErrInvalidRangeStep is an error where the step parameter is less than or equal to 0 when using builtin range function.
|
||||||
|
ErrInvalidRangeStep = errors.New("range step must be greater than 0")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrInvalidArgumentType represents an invalid argument value type error.
|
// ErrInvalidArgumentType represents an invalid argument value type error.
|
||||||
|
|
Loading…
Reference in a new issue