Skip to content

Commit 8858304

Browse files
endinkgeseq
andauthored
PR: builtin range function #326 (#328)
* 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>
1 parent c51d02f commit 8858304

3 files changed

Lines changed: 225 additions & 0 deletions

File tree

builtins.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ var builtinFuncs = []*BuiltinFunction{
121121
Name: "format",
122122
Value: builtinFormat,
123123
},
124+
{
125+
Name: "range",
126+
Value: builtinRange,
127+
},
124128
}
125129

126130
// GetAllBuiltinFunctions returns all builtin function objects.
@@ -323,6 +327,71 @@ func builtinLen(args ...Object) (Object, error) {
323327
}
324328
}
325329

330+
//range(start, stop[, step])
331+
func builtinRange(args ...Object) (Object, error) {
332+
numArgs := len(args)
333+
if numArgs < 2 || numArgs > 3 {
334+
return nil, ErrWrongNumArguments
335+
}
336+
var start, stop, step *Int
337+
338+
for i, arg := range args {
339+
v, ok := args[i].(*Int)
340+
if !ok {
341+
var name string
342+
switch i {
343+
case 0:
344+
name = "start"
345+
case 1:
346+
name = "stop"
347+
case 2:
348+
name = "step"
349+
}
350+
351+
return nil, ErrInvalidArgumentType{
352+
Name: name,
353+
Expected: "int",
354+
Found: arg.TypeName(),
355+
}
356+
}
357+
if i == 2 && v.Value <= 0 {
358+
return nil, ErrInvalidRangeStep
359+
}
360+
switch i {
361+
case 0:
362+
start = v
363+
case 1:
364+
stop = v
365+
case 2:
366+
step = v
367+
}
368+
}
369+
370+
if step == nil {
371+
step = &Int{Value: int64(1)}
372+
}
373+
374+
return buildRange(start.Value, stop.Value, step.Value), nil
375+
}
376+
377+
func buildRange(start, stop, step int64) *Array {
378+
array := &Array{}
379+
if start <= stop {
380+
for i := start; i < stop; i += step {
381+
array.Value = append(array.Value, &Int{
382+
Value: i,
383+
})
384+
}
385+
} else {
386+
for i := start; i > stop; i -= step {
387+
array.Value = append(array.Value, &Int{
388+
Value: i,
389+
})
390+
}
391+
}
392+
return array
393+
}
394+
326395
func builtinFormat(args ...Object) (Object, error) {
327396
numArgs := len(args)
328397
if numArgs == 0 {

builtins_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,156 @@ func Test_builtinSplice(t *testing.T) {
351351
})
352352
}
353353
}
354+
355+
func Test_builtinRange(t *testing.T) {
356+
var builtinRange func(args ...tengo.Object) (tengo.Object, error)
357+
for _, f := range tengo.GetAllBuiltinFunctions() {
358+
if f.Name == "range" {
359+
builtinRange = f.Value
360+
break
361+
}
362+
}
363+
if builtinRange == nil {
364+
t.Fatal("builtin range not found")
365+
}
366+
tests := []struct {
367+
name string
368+
args []tengo.Object
369+
result *tengo.Array
370+
wantErr bool
371+
wantedErr error
372+
}{
373+
{name: "no args", args: []tengo.Object{}, wantErr: true,
374+
wantedErr: tengo.ErrWrongNumArguments,
375+
},
376+
{name: "single args", args: []tengo.Object{&tengo.Map{}},
377+
wantErr: true,
378+
wantedErr: tengo.ErrWrongNumArguments,
379+
},
380+
{name: "4 args", args: []tengo.Object{&tengo.Map{}, &tengo.String{}, &tengo.String{}, &tengo.String{}},
381+
wantErr: true,
382+
wantedErr: tengo.ErrWrongNumArguments,
383+
},
384+
{name: "invalid start",
385+
args: []tengo.Object{&tengo.String{}, &tengo.String{}},
386+
wantErr: true,
387+
wantedErr: tengo.ErrInvalidArgumentType{
388+
Name: "start", Expected: "int", Found: "string"},
389+
},
390+
{name: "invalid stop",
391+
args: []tengo.Object{&tengo.Int{}, &tengo.String{}},
392+
wantErr: true,
393+
wantedErr: tengo.ErrInvalidArgumentType{
394+
Name: "stop", Expected: "int", Found: "string"},
395+
},
396+
{name: "invalid step",
397+
args: []tengo.Object{&tengo.Int{}, &tengo.Int{}, &tengo.String{}},
398+
wantErr: true,
399+
wantedErr: tengo.ErrInvalidArgumentType{
400+
Name: "step", Expected: "int", Found: "string"},
401+
},
402+
{name: "zero step",
403+
args: []tengo.Object{&tengo.Int{}, &tengo.Int{}, &tengo.Int{}}, //must greate than 0
404+
wantErr: true,
405+
wantedErr: tengo.ErrInvalidRangeStep,
406+
},
407+
{name: "negative step",
408+
args: []tengo.Object{&tengo.Int{}, &tengo.Int{}, intObject(-2)}, //must greate than 0
409+
wantErr: true,
410+
wantedErr: tengo.ErrInvalidRangeStep,
411+
},
412+
{name: "same bound",
413+
args: []tengo.Object{&tengo.Int{}, &tengo.Int{}},
414+
wantErr: false,
415+
result: &tengo.Array{
416+
Value: nil,
417+
},
418+
},
419+
{name: "positive range",
420+
args: []tengo.Object{&tengo.Int{}, &tengo.Int{Value: 5}},
421+
wantErr: false,
422+
result: &tengo.Array{
423+
Value: []tengo.Object{
424+
intObject(0),
425+
intObject(1),
426+
intObject(2),
427+
intObject(3),
428+
intObject(4),
429+
},
430+
},
431+
},
432+
{name: "negative range",
433+
args: []tengo.Object{&tengo.Int{}, &tengo.Int{Value: -5}},
434+
wantErr: false,
435+
result: &tengo.Array{
436+
Value: []tengo.Object{
437+
intObject(0),
438+
intObject(-1),
439+
intObject(-2),
440+
intObject(-3),
441+
intObject(-4),
442+
},
443+
},
444+
},
445+
446+
{name: "positive with step",
447+
args: []tengo.Object{&tengo.Int{}, &tengo.Int{Value: 5}, &tengo.Int{Value: 2}},
448+
wantErr: false,
449+
result: &tengo.Array{
450+
Value: []tengo.Object{
451+
intObject(0),
452+
intObject(2),
453+
intObject(4),
454+
},
455+
},
456+
},
457+
458+
{name: "negative with step",
459+
args: []tengo.Object{&tengo.Int{}, &tengo.Int{Value: -10}, &tengo.Int{Value: 2}},
460+
wantErr: false,
461+
result: &tengo.Array{
462+
Value: []tengo.Object{
463+
intObject(0),
464+
intObject(-2),
465+
intObject(-4),
466+
intObject(-6),
467+
intObject(-8),
468+
},
469+
},
470+
},
471+
472+
{name: "large range",
473+
args: []tengo.Object{intObject(-10), intObject(10), &tengo.Int{Value: 3}},
474+
wantErr: false,
475+
result: &tengo.Array{
476+
Value: []tengo.Object{
477+
intObject(-10),
478+
intObject(-7),
479+
intObject(-4),
480+
intObject(-1),
481+
intObject(2),
482+
intObject(5),
483+
intObject(8),
484+
},
485+
},
486+
},
487+
}
488+
for _, tt := range tests {
489+
t.Run(tt.name, func(t *testing.T) {
490+
got, err := builtinRange(tt.args...)
491+
if (err != nil) != tt.wantErr {
492+
t.Errorf("builtinRange() error = %v, wantErr %v",
493+
err, tt.wantErr)
494+
return
495+
}
496+
if tt.wantErr && tt.wantedErr.Error() != err.Error() {
497+
t.Errorf("builtinRange() error = %v, wantedErr %v",
498+
err, tt.wantedErr)
499+
}
500+
if tt.result != nil && !reflect.DeepEqual(tt.result, got) {
501+
t.Errorf("builtinRange() arrays are not equal expected"+
502+
" %s, got %s", tt.result, got.(*tengo.Array))
503+
}
504+
})
505+
}
506+
}

errors.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ var (
4949
// ErrNotImplemented is an error where an Object has not implemented a
5050
// required method.
5151
ErrNotImplemented = errors.New("not implemented")
52+
53+
// ErrInvalidRangeStep is an error where the step parameter is less than or equal to 0 when using builtin range function.
54+
ErrInvalidRangeStep = errors.New("range step must be greater than 0")
5255
)
5356

5457
// ErrInvalidArgumentType represents an invalid argument value type error.

0 commit comments

Comments
 (0)