Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/docs/reference/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,34 @@ Result:
6
```

### `range`

| Arity | Parameter 1 Type | Parameter 2 Type (optional) | Parameter 3 Type (optional) | Return Type |
| --- | ---| --- | --- | --- |
| 1 - 3 | `number` | `number` | `number` | `array<number>` |

Takes arguments `start`, `stop`, and `step`. Returns a list of integers starting at `start`, ending at `stop` (non-inclusive), using steps of size `step`. If the sign of `step` does not match the sign of `stop - start`, then an empty list is returned.

Behavior of `range` closely matches python's `range` function.

Below is a table describing how the variables `start`, `stop`, and `step` are constructed at different arities. This corresponds to Python's `range()` method.

| Variable | Arity=1 | Arity=2 | Arity=3 |
| --- | --- | --- | --- |
| `stop` | First Arg | Second Arg | Second Arg |
| `start` | `0` | First Arg | First Arg |
| `step` | `1` | `1` | Third Arg |

Numbers provided to range must all be integers. Step must be greater than zero.

#### Example

`range 5` yields `[0, 1, 2, 3, 4]`

`range 3 8` yields `[3, 4, 5, 6, 7]`

`range 3 8 2` yields `[3, 5, 7]`

### `regex`

| Arity | Parameter 1 Type | Parameter 2 Type (optional) | Return Type |
Expand Down
4 changes: 3 additions & 1 deletion js/src/builtins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import not from "./not";
import notequal from "./notequal";
import or from "./or";
import plus from "./plus";
import range from "./range";
import reduce from "./reduce";
import regex from "./regex";
import replace from "./replace";
Expand Down Expand Up @@ -92,7 +93,7 @@ const divide = numericBinaryOperator((a, b) => {
if (b === 0) {
throw new RuntimeError("Division by zero");
}
return (a / b);
return a / b;
});

export default {
Expand All @@ -117,6 +118,7 @@ export default {
map,
mapkeys,
mapvalues,
range,
reduce,
regex,
replace,
Expand Down
45 changes: 45 additions & 0 deletions js/src/builtins/range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { pushRuntimeValueToStack } from "../stackManip";
import { BuiltinFunction, RuntimeValue } from "../types";
import { arity, validateType } from "../util";

const validateIsInteger = (value: RuntimeValue) => {
const res = validateType("number", value);
if (!Number.isInteger(value)) {
throw new Error("Arguments to range must be integers");
}
return res;
};

const range: BuiltinFunction = arity([1, 2, 3], (args, stack, exec) => {
let start = 0;
let end;
const target = [];
if (args.length > 1) {
start = validateIsInteger(exec(args[0], stack));
end = validateIsInteger(exec(args[1], stack));
} else {
end = validateIsInteger(exec(args[0], stack));
}
// Step size
let step = 1;
if (args.length > 2) {
step = validateIsInteger(exec(args[2], stack));
}
// Iteration Methods
if (step === 0) {
throw new Error("Range: Step size cannot be 0");
} else if (step > 0 && start < end) {
for (let i = start; i < end; i += step) {
target.push(i);
}
} else if (step < 0 && start > end) {
for (let i = start; i > end; i += step) {
target.push(i);
}
} else {
return [];
}
return target;
});

export default range;
22 changes: 21 additions & 1 deletion py/mistql/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from mistql.exceptions import (MistQLRuntimeError, MistQLTypeError,
OpenAnIssueIfYouGetThisError)
from mistql.expression import BaseExpression, RefExpression
from mistql.runtime_value import RuntimeValue, RuntimeValueType, assert_type
from mistql.runtime_value import RuntimeValue, RuntimeValueType, assert_type, assert_int
from mistql.stack import Stack, add_runtime_value_to_stack

Args = List[BaseExpression]
Expand Down Expand Up @@ -484,6 +484,26 @@ def match_operator(arguments: Args, stack: Stack, exec: Exec) -> RuntimeValue:
return match(arguments[::-1], stack, exec)


@builtin("range", 1, 3)
def _range(arguments: Args, stack: Stack, exec: Exec) -> RuntimeValue:
start = 0
step = 1
if len(arguments) == 1:
stop = int(assert_int(exec(arguments[0], stack)).value)
elif len(arguments) == 2:
start = int(assert_int(exec(arguments[0], stack)).value)
stop = int(assert_int(exec(arguments[1], stack)).value)
elif len(arguments) == 3:
start = int(assert_int(exec(arguments[0], stack)).value)
stop = int(assert_int(exec(arguments[1], stack)).value)
step = int(assert_int(exec(arguments[2], stack)).value)
else:
raise OpenAnIssueIfYouGetThisError(
"Unexpectedly reaching end of function in range call."
)
return RuntimeValue.of(list(range(start, stop, step)))


@builtin("replace", 3)
def replace(arguments: Args, stack: Stack, exec: Exec) -> RuntimeValue:
pattern = exec(arguments[0], stack)
Expand Down
7 changes: 7 additions & 0 deletions py/mistql/runtime_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,10 @@ def assert_type(
if value.type != expected_type:
raise MistQLTypeError(f"Expected {expected_type}, got {value.type}")
return value


def assert_int(value: RuntimeValue):
value = assert_type(value, RuntimeValueType.Number)
if value.value != int(value.value):
raise MistQLTypeError(f"Expected integer, got {value.value}")
return value
124 changes: 123 additions & 1 deletion shared/testdata.json
Original file line number Diff line number Diff line change
Expand Up @@ -3978,6 +3978,128 @@
}
]
},
{
"describe": "#range",
"cases": [
{
"it": "returns an array of numbers",
"assertions": [
{
"query": "range 3",
"data": null,
"expected": [0, 1, 2]
}
]
},
{
"it": "returns an array of numbers from a start",
"assertions": [
{
"query": "range 3 6",
"data": null,
"expected": [3, 4, 5]
}
]
},
{
"it": "returns an array of numbers from a start and step",
"assertions": [
{
"query": "range 3 6 2",
"data": null,
"expected": [3, 5]
},
{
"query": "range 3 7 2",
"data": null,
"expected": [3, 5]
},
{
"query": "range 10 100 7",
"data": null,
"expected": [
10, 17, 24, 31, 38, 45, 52, 59, 66, 73, 80, 87, 94
]
}
]
},
{
"it": "fails if any of the arguments are not integers",
"assertions": [
{
"query": "range 3 6 \"a\"",
"data": null,
"throws": true
},
{
"query": "range 3 6 2.5",
"data": null,
"throws": true
},
{
"query": "range 3.5 6 2",
"data": null,
"throws": true
},
{
"query": "range 3 6.5 2",
"data": null,
"throws": true
}
]
},
{
"it": "fails if the step is equal to 0",
"assertions": [
{
"query": "range 3 6 0",
"data": null,
"throws": true
}
]
},
{
"it": "returns an empty list if the start is greater than the end",
"assertions": [
{
"query": "range 6 3",
"data": null,
"expected": []
},
{
"query": "range 6 3 2",
"data": null,
"expected": []
}
]
},
{
"it": "returns an empty list if the step size is negative",
"assertions": [
{
"query": "range 3 6 (-2)",
"data": null,
"expected": []
}
]
},
{
"it": "counts backwards iff the step size is negative and start is greater than end",
"assertions": [
{
"query": "range 6 3 (-2)",
"data": null,
"expected": [6, 4]
},
{
"query": "range 7 3 (-1)",
"data": null,
"expected": [7, 6, 5, 4]
}
]
}
]
},
{
"describe": "#apply",
"cases": [
Expand Down Expand Up @@ -4300,4 +4422,4 @@
]
}
]
}
}