Skip to content

Commit c28eaf7

Browse files
authored
topdown: add numbers.range_step built-in function (#6187)
Fixes: #6186 Signed-off-by: Sebastian Spaink <[email protected]>
1 parent 519eea7 commit c28eaf7

File tree

5 files changed

+212
-16
lines changed

5 files changed

+212
-16
lines changed

ast/builtins.go

+18
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ var DefaultBuiltins = [...]*Builtin{
136136

137137
// Numbers
138138
NumbersRange,
139+
NumbersRangeStep,
139140
RandIntn,
140141

141142
// Encoding
@@ -1347,6 +1348,23 @@ var NumbersRange = &Builtin{
13471348
),
13481349
}
13491350

1351+
var NumbersRangeStep = &Builtin{
1352+
Name: "numbers.range_step",
1353+
Description: `Returns an array of numbers in the given (inclusive) range incremented by a positive step.
1354+
If "a==b", then "range == [a]"; if "a > b", then "range" is in descending order.
1355+
If the provided "step" is less then 1, an error will be thrown.
1356+
If "b" is not in the range of the provided "step", "b" won't be included in the result.
1357+
`,
1358+
Decl: types.NewFunction(
1359+
types.Args(
1360+
types.Named("a", types.N),
1361+
types.Named("b", types.N),
1362+
types.Named("step", types.N),
1363+
),
1364+
types.Named("range", types.NewArray(nil, types.N)).Description("the range between `a` and `b` in `step` increments"),
1365+
),
1366+
}
1367+
13501368
/**
13511369
* Units
13521370
*/

builtin_metadata.json

+28
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
"minus",
111111
"mul",
112112
"numbers.range",
113+
"numbers.range_step",
113114
"plus",
114115
"rand.intn",
115116
"rem",
@@ -11558,6 +11559,33 @@
1155811559
},
1155911560
"wasm": true
1156011561
},
11562+
"numbers.range_step": {
11563+
"args": [
11564+
{
11565+
"name": "a",
11566+
"type": "number"
11567+
},
11568+
{
11569+
"name": "b",
11570+
"type": "number"
11571+
},
11572+
{
11573+
"name": "step",
11574+
"type": "number"
11575+
}
11576+
],
11577+
"available": [
11578+
"edge"
11579+
],
11580+
"description": "Returns an array of numbers in the given (inclusive) range incremented by a positive step.\n\tIf \"a==b\", then \"range == [a]\"; if \"a \u003e b\", then \"range\" is in descending order.\n\tIf the provided \"step\" is less then 1, an error will be thrown.\n\tIf \"b\" is not in the range of the provided \"step\", \"b\" won't be included in the result.\n\t",
11581+
"introduced": "edge",
11582+
"result": {
11583+
"description": "the range between `a` and `b` in `step` increments",
11584+
"name": "range",
11585+
"type": "array[number]"
11586+
},
11587+
"wasm": false
11588+
},
1156111589
"object.filter": {
1156211590
"args": [
1156311591
{

capabilities.json

+23
Original file line numberDiff line numberDiff line change
@@ -2895,6 +2895,29 @@
28952895
"type": "function"
28962896
}
28972897
},
2898+
{
2899+
"name": "numbers.range_step",
2900+
"decl": {
2901+
"args": [
2902+
{
2903+
"type": "number"
2904+
},
2905+
{
2906+
"type": "number"
2907+
},
2908+
{
2909+
"type": "number"
2910+
}
2911+
],
2912+
"result": {
2913+
"dynamic": {
2914+
"type": "number"
2915+
},
2916+
"type": "array"
2917+
},
2918+
"type": "function"
2919+
}
2920+
},
28982921
{
28992922
"name": "object.filter",
29002923
"decl": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
cases:
2+
- note: numbersrangestep/ascending
3+
query: data.test.p = x
4+
modules:
5+
- |
6+
package test
7+
8+
p = num {
9+
num := numbers.range_step(0, 10, 2)
10+
}
11+
want_result:
12+
- x:
13+
- 0
14+
- 2
15+
- 4
16+
- 6
17+
- 8
18+
- 10
19+
- note: numbersrangestep/descending
20+
query: data.test.p = x
21+
modules:
22+
- |
23+
package test
24+
25+
p = num {
26+
num := numbers.range_step(0, -10, 2)
27+
}
28+
want_result:
29+
- x:
30+
- 0
31+
- -2
32+
- -4
33+
- -6
34+
- -8
35+
- -10
36+
- note: numbersrangestep/negative
37+
query: data.test.p = x
38+
modules:
39+
- |
40+
package test
41+
42+
p = num {
43+
num := numbers.range_step(0, 10, -2)
44+
}
45+
want_error: 'numbers.range_step: step must be a positive number above zero'
46+
want_error_code: eval_builtin_error
47+
strict_error: true
48+
- note: numbersrangestep/memoryexample
49+
query: data.test.p = x
50+
modules:
51+
- |
52+
package test
53+
54+
p = num {
55+
num := numbers.range_step(1024, 4096, 1024)
56+
}
57+
want_result:
58+
- x:
59+
- 1024
60+
- 2048
61+
- 3072
62+
- 4096
63+
- note: numbersrangestep/equal
64+
query: data.test.p = x
65+
modules:
66+
- |
67+
package test
68+
69+
p = num {
70+
num := numbers.range_step(2, 2, 2)
71+
}
72+
want_result:
73+
- x:
74+
- 2
75+
- note: numbersrangestep/notinrange
76+
query: data.test.p = x
77+
modules:
78+
- |
79+
package test
80+
81+
p = num {
82+
num := numbers.range_step(2, 5, 2)
83+
}
84+
want_result:
85+
- x:
86+
- 2
87+
- 4

topdown/numbers.go

+56-16
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,71 @@ func builtinNumbersRange(bctx BuiltinContext, operands []*ast.Term, iter func(*a
2828
return err
2929
}
3030

31-
result := ast.NewArray()
31+
ast, err := generateRange(bctx, x, y, one, "numbers.range")
32+
if err != nil {
33+
return err
34+
}
35+
36+
return iter(ast)
37+
}
38+
39+
func builtinNumbersRangeStep(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
40+
41+
x, err := builtins.BigIntOperand(operands[0].Value, 1)
42+
if err != nil {
43+
return err
44+
}
45+
46+
y, err := builtins.BigIntOperand(operands[1].Value, 2)
47+
if err != nil {
48+
return err
49+
}
50+
51+
step, err := builtins.BigIntOperand(operands[2].Value, 3)
52+
if err != nil {
53+
return err
54+
}
55+
56+
if step.Cmp(big.NewInt(0)) <= 0 {
57+
return fmt.Errorf("numbers.range_step: step must be a positive number above zero")
58+
}
59+
60+
ast, err := generateRange(bctx, x, y, step, "numbers.range_step")
61+
if err != nil {
62+
return err
63+
}
64+
65+
return iter(ast)
66+
}
67+
68+
func generateRange(bctx BuiltinContext, x *big.Int, y *big.Int, step *big.Int, funcName string) (*ast.Term, error) {
69+
3270
cmp := x.Cmp(y)
71+
72+
comp := func(i *big.Int, y *big.Int) bool { return i.Cmp(y) <= 0 }
73+
iter := func(i *big.Int) *big.Int { return i.Add(i, step) }
74+
75+
if cmp > 0 {
76+
comp = func(i *big.Int, y *big.Int) bool { return i.Cmp(y) >= 0 }
77+
iter = func(i *big.Int) *big.Int { return i.Sub(i, step) }
78+
}
79+
80+
result := ast.NewArray()
3381
haltErr := Halt{
3482
Err: &Error{
3583
Code: CancelErr,
36-
Message: "numbers.range: timed out before generating all numbers in range",
84+
Message: fmt.Sprintf("%s: timed out before generating all numbers in range", funcName),
3785
},
3886
}
3987

40-
if cmp <= 0 {
41-
for i := new(big.Int).Set(x); i.Cmp(y) <= 0; i = i.Add(i, one) {
42-
if bctx.Cancel != nil && bctx.Cancel.Cancelled() {
43-
return haltErr
44-
}
45-
result = result.Append(ast.NewTerm(builtins.IntToNumber(i)))
46-
}
47-
} else {
48-
for i := new(big.Int).Set(x); i.Cmp(y) >= 0; i = i.Sub(i, one) {
49-
if bctx.Cancel != nil && bctx.Cancel.Cancelled() {
50-
return haltErr
51-
}
52-
result = result.Append(ast.NewTerm(builtins.IntToNumber(i)))
88+
for i := new(big.Int).Set(x); comp(i, y); i = iter(i) {
89+
if bctx.Cancel != nil && bctx.Cancel.Cancelled() {
90+
return nil, haltErr
5391
}
92+
result = result.Append(ast.NewTerm(builtins.IntToNumber(i)))
5493
}
5594

56-
return iter(ast.NewTerm(result))
95+
return ast.NewTerm(result), nil
5796
}
5897

5998
func builtinRandIntn(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
@@ -95,5 +134,6 @@ func builtinRandIntn(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.T
95134

96135
func init() {
97136
RegisterBuiltinFunc(ast.NumbersRange.Name, builtinNumbersRange)
137+
RegisterBuiltinFunc(ast.NumbersRangeStep.Name, builtinNumbersRangeStep)
98138
RegisterBuiltinFunc(ast.RandIntn.Name, builtinRandIntn)
99139
}

0 commit comments

Comments
 (0)