pkg/list: adding range function as a builtin

this commit adds a range function which allows to generate a sequence
of numbers from a start point adding steps till a limit is reached.

For instance:

   Range(0, 5, 2)

results in

   [0, 2, 4]

Change-Id: I29eddca25c4c5206c226d518d617b7b69ab8852e
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3483
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index 0610c90..a9838bb 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -189,6 +189,30 @@
 		test("list", `list.Product("foo")`),
 		`_|_(cannot use "foo" (type string) as list in argument 1 to list.Product)`,
 	}, {
+		test("list", `list.Range(0, 5, 0)`),
+		`_|_(error in call to list.Range: step must be non zero)`,
+	}, {
+		test("list", `list.Range(5, 0, 1)`),
+		`_|_(error in call to list.Range: end must be greater than start when step is positive)`,
+	}, {
+		test("list", `list.Range(0, 5, -1)`),
+		`_|_(error in call to list.Range: end must be less than start when step is negative)`,
+	}, {
+		test("list", `list.Range(0, 5, 1)`),
+		`[0,1,2,3,4]`,
+	}, {
+		test("list", `list.Range(0, 1, 1)`),
+		`[0]`,
+	}, {
+		test("list", `list.Range(0, 5, 2)`),
+		`[0,2,4]`,
+	}, {
+		test("list", `list.Range(5, 0, -1)`),
+		`[5,4,3,2,1]`,
+	}, {
+		test("list", `list.Range(0, 5, 0.5)`),
+		`[0,0.5,1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5]`,
+	}, {
 		test("list", `list.Slice([1, 2, 3, 4], 1, 3)`),
 		`[2,3]`,
 	}, {
diff --git a/cue/builtins.go b/cue/builtins.go
index 037fa33..7337780 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -904,6 +904,47 @@
 				}
 			},
 		}, {
+			Name:   "Range",
+			Params: []kind{numKind, numKind, numKind},
+			Result: listKind,
+			Func: func(c *callCtxt) {
+				start, limit, step := c.decimal(0), c.decimal(1), c.decimal(2)
+				c.ret, c.err = func() (interface{}, error) {
+					if step.IsZero() {
+						return nil, fmt.Errorf("step must be non zero")
+					}
+
+					if !step.Negative && +1 == start.Cmp(limit) {
+						return nil, fmt.Errorf("end must be greater than start when step is positive")
+					}
+
+					if step.Negative && -1 == start.Cmp(limit) {
+						return nil, fmt.Errorf("end must be less than start when step is negative")
+					}
+
+					var vals []*internal.Decimal
+					num := start
+					for {
+						if !step.Negative && -1 != num.Cmp(limit) {
+							break
+						}
+
+						if step.Negative && +1 != num.Cmp(limit) {
+							break
+						}
+
+						vals = append(vals, num)
+						d := apd.New(0, 0)
+						_, err := internal.BaseContext.Add(d, step, num)
+						if err != nil {
+							return nil, err
+						}
+						num = d
+					}
+					return vals, nil
+				}()
+			},
+		}, {
 			Name:   "Sum",
 			Params: []kind{listKind},
 			Result: numKind,
diff --git a/pkg/list/math.go b/pkg/list/math.go
index 4ce4ba3..d91adb5 100644
--- a/pkg/list/math.go
+++ b/pkg/list/math.go
@@ -86,6 +86,52 @@
 	return d, nil
 }
 
+// Range generates a list of numbers using a start value, a limit value, and a
+// step value.
+//
+// For instance:
+//
+//    Range(0, 5, 2)
+//
+// results in
+//
+//    [0, 2, 4]
+//
+func Range(start, limit, step *internal.Decimal) ([]*internal.Decimal, error) {
+	if step.IsZero() {
+		return nil, fmt.Errorf("step must be non zero")
+	}
+
+	if !step.Negative && +1 == start.Cmp(limit) {
+		return nil, fmt.Errorf("end must be greater than start when step is positive")
+	}
+
+	if step.Negative && -1 == start.Cmp(limit) {
+		return nil, fmt.Errorf("end must be less than start when step is negative")
+	}
+
+	var vals []*internal.Decimal
+	num := start
+	for {
+		if !step.Negative && -1 != num.Cmp(limit) {
+			break
+		}
+
+		if step.Negative && +1 != num.Cmp(limit) {
+			break
+		}
+
+		vals = append(vals, num)
+		d := apd.New(0, 0)
+		_, err := internal.BaseContext.Add(d, step, num)
+		if err != nil {
+			return nil, err
+		}
+		num = d
+	}
+	return vals, nil
+}
+
 // Sum returns the sum of a list non empty xs.
 func Sum(xs []*internal.Decimal) (*internal.Decimal, error) {
 	d := apd.New(0, 0)