Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
HARP-9897: Add support for 'array' and 'make-array'.
Browse files Browse the repository at this point in the history
This change introduces the operator 'array' to validate the type
of arrays the operator 'make-array' to create heterogeneous arrays
from sub expressions.

Signed-off-by: Roberto Raggi <roberto.raggi@here.com>
  • Loading branch information
robertoraggi committed Apr 8, 2020
1 parent 65a0327 commit 541b5c1
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 1 deletion.
45 changes: 45 additions & 0 deletions @here/harp-datasource-protocol/StyleExpressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,51 @@ fallback that is a `string`.
["string", value, fallback...]
```

## array

Validates the type of the given array value. The `type`
must be `"boolean"`, `"number"` or `"string"`; length must be an integer.
An error is generated if `value` is not an `array` with the given type
and length.

```javascript
["array", value]
["array", type, value]
["array", type, length, value]
```

for example:

```javascript
// asserts that 'speeds' is an 'array'
["array", ["get", "speeds"]]

// asserts that 'speeds' is an 'array' of numbers
["array", "number", ["get", "speeds"]]

// asserts that 'speeds' is an 'array' of 3 numbers
["array", "number", 3, ["get", "speeds"]]
```

## make-array

Creates an array from the given elements.

```javascript
["make-array", elements...];
```

for example:

```javascript
// create the array [1,2,3]
["make-array", 1, 2, 3]

// create an array with the values of the feature properties
// 'kind' and 'kind_details'
["make-array", ["get", "kind"], ["get", "kind_details"]]
```

## coalesce

Returns the first `value` that does not evaluates to `null`.
Expand Down
71 changes: 70 additions & 1 deletion @here/harp-datasource-protocol/lib/operators/ArrayOperators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,79 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { CallExpr } from "../Expr";
import { CallExpr, Expr, JsonArray, NumberLiteralExpr, StringLiteralExpr } from "../Expr";
import { ExprEvaluatorContext, OperatorDescriptorMap } from "../ExprEvaluator";

const VALID_ELEMENT_TYPES = ["boolean", "number", "string"];

function checkElementTypes(arg: Expr, array: JsonArray) {
if (!(arg instanceof StringLiteralExpr) || !VALID_ELEMENT_TYPES.includes(arg.value)) {
throw new Error(
`expected "boolean", "number" or "string" instead of '${JSON.stringify(arg)}'`
);
}

const ty = arg.value;

array.forEach((element, index) => {
if (typeof element !== ty) {
throw new Error(`expected array element at index ${index} to have type '${ty}'`);
}
});
}

function checkArrayLength(arg: Expr, array: JsonArray) {
if (!(arg instanceof NumberLiteralExpr)) {
throw new Error(`missing expected number of elements`);
}

const length = arg.value;

if (array.length !== length) {
throw new Error(`the array must have ${length} element(s)`);
}
}

function checkArray(context: ExprEvaluatorContext, arg: Expr) {
const value = context.evaluate(arg);
if (!Array.isArray(value)) {
throw new Error(`'${value}' is not an array`);
}
return value;
}

const operators = {
array: {
call: (context: ExprEvaluatorContext, call: CallExpr) => {
switch (call.args.length) {
case 0:
throw new Error("not enough arguments");
case 1:
return checkArray(context, call.args[0]);
case 2: {
const array = checkArray(context, call.args[1]);
checkElementTypes(call.args[0], array);
return array;
}
case 3: {
const array = checkArray(context, call.args[2]);
checkArrayLength(call.args[1], array);
checkElementTypes(call.args[0], array);
return array;
}
default:
throw new Error("too many arguments");
}
}
},
"make-array": {
call: (context: ExprEvaluatorContext, call: CallExpr) => {
if (call.args.length === 0) {
throw new Error("not enough arguments");
}
return [...call.args.map(arg => context.evaluate(arg))];
}
},
at: {
call: (context: ExprEvaluatorContext, call: CallExpr) => {
const args = call.args;
Expand Down
107 changes: 107 additions & 0 deletions @here/harp-datasource-protocol/test/ExprEvaluatorTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,113 @@ describe("ExprEvaluator", function() {
});
});

describe("Operator 'array'", function() {
it("array of numbers", function() {
assert.deepStrictEqual(evaluate(["array", ["literal", [1, 2, 3]]]), [1, 2, 3]);
assert.deepStrictEqual(evaluate(["array", "number", ["literal", [1, 2, 3]]]), [
1,
2,
3
]);
assert.deepStrictEqual(evaluate(["array", "number", 3, ["literal", [1, 2, 3]]]), [
1,
2,
3
]);
});

it("array of strings", function() {
assert.deepStrictEqual(evaluate(["array", ["literal", ["x", "y", "z"]]]), [
"x",
"y",
"z"
]);
assert.deepStrictEqual(evaluate(["array", "string", ["literal", ["x", "y", "z"]]]), [
"x",
"y",
"z"
]);
assert.deepStrictEqual(evaluate(["array", "string", 3, ["literal", ["x", "y", "z"]]]), [
"x",
"y",
"z"
]);
});

it("array of booleans", function() {
assert.deepStrictEqual(evaluate(["array", ["literal", [true, false]]]), [true, false]);
assert.deepStrictEqual(evaluate(["array", "boolean", ["literal", [true, false]]]), [
true,
false
]);
assert.deepStrictEqual(evaluate(["array", "boolean", 2, ["literal", [true, false]]]), [
true,
false
]);
});

it("feature data", function() {
const speeds = [100, 120, 140];
assert.deepStrictEqual(evaluate(["array", ["get", "speeds"]], { speeds }), speeds);
});

it("array expected type", function() {
assert.throws(
() => evaluate(["array", "number", ["literal", [1, false]]]),
"expected array element at index 1 to have type 'number'"
);

assert.throws(
() => evaluate(["array", "string", ["literal", ["x", "y", 123]]]),
"expected array element at index 2 to have type 'string'"
);
});

it("array expected length", function() {
assert.throws(
() => evaluate(["array", "number", 2, ["literal", [1, 2, 3]]]),
"the array must have 2 element(s)"
);
});

it("syntax", function() {
assert.throws(() => evaluate(["array"]), "not enough arguments");

assert.throws(() => evaluate(["array", "object"]), "'object' is not an array");

assert.throws(
() => evaluate(["array", "object", ["literal", ["element"]]]),
`expected "boolean", "number" or "string" instead of '"object"'`
);

assert.throws(() => evaluate(["array", "number", 123]), "'123' is not an array");

assert.throws(
() => evaluate(["array", "number", 1, ["literal", [1]], "extra"]),
"too many arguments"
);
});
});

describe("Operator 'make-array'", function() {
it("create", function() {
assert.deepEqual(evaluate(["make-array", 1, 2, 3]), [1, 2, 3]);
assert.deepEqual(evaluate(["make-array", "x", 2, true]), ["x", 2, true]);

assert.deepEqual(evaluate(["make-array", "x", 2, ["get", "two"]]), ["x", 2, 2]);

assert.deepEqual(evaluate(["make-array", "x", 2, ["get", "numbers"]]), [
"x",
2,
[1, 2, 3]
]);
});

it("syntax", function() {
assert.throws(() => evaluate(["make-array"]), "not enough arguments");
});
});

describe("Operator 'typeof'", function() {
it("evaluate", function() {
assert.strictEqual(evaluate(["typeof", "x"]), "string");
Expand Down

0 comments on commit 541b5c1

Please sign in to comment.