Skip to content

Commit

Permalink
Add range function to python and js implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
evinism committed Dec 19, 2022
1 parent db4ccbc commit aee7722
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 3 deletions.
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 @@
]
}
]
}
}

0 comments on commit aee7722

Please sign in to comment.