diff --git a/src/style-spec/expression/definitions/in.js b/src/style-spec/expression/definitions/in.js index 6a885d36892..0e6681a5080 100644 --- a/src/style-spec/expression/definitions/in.js +++ b/src/style-spec/expression/definitions/in.js @@ -1,6 +1,6 @@ // @flow -import {ValueType, BooleanType, toString} from '../types'; +import {BooleanType, StringType, ValueType, NullType, toString, NumberType, isValidType, isValidNativeType} from '../types'; import RuntimeError from '../runtime_error'; import {typeOf} from '../values'; @@ -8,26 +8,6 @@ import type {Expression} from '../expression'; import type ParsingContext from '../parsing_context'; import type EvaluationContext from '../evaluation_context'; import type {Type} from '../types'; -import type {Value} from '../values'; - -function isComparableType(type: Type) { - return type.kind === 'boolean' || - type.kind === 'string' || - type.kind === 'number' || - type.kind === 'null' || - type.kind === 'value'; -} - -function isComparableRuntimeValue(needle: boolean | string | number | null) { - return typeof needle === 'boolean' || - typeof needle === 'string' || - typeof needle === 'number'; -} - -function isSearchableRuntimeValue(haystack: Array | string) { - return Array.isArray(haystack) || - typeof haystack === 'string'; -} class In implements Expression { type: Type; @@ -51,7 +31,7 @@ class In implements Expression { if (!needle || !haystack) return null; - if (!isComparableType(needle.type)) { + if (!isValidType(needle.type, [BooleanType, StringType, NumberType, NullType, ValueType])) { return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${toString(needle.type)} instead`); } @@ -62,13 +42,13 @@ class In implements Expression { const needle = (this.needle.evaluate(ctx): any); const haystack = (this.haystack.evaluate(ctx): any); - if (needle == null || !haystack) return false; + if (!haystack) return false; - if (!isComparableRuntimeValue(needle)) { - throw new RuntimeError(`Expected first argument to be of type boolean, string or number, but found ${toString(typeOf(needle))} instead.`); + if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) { + throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${toString(typeOf(needle))} instead.`); } - if (!isSearchableRuntimeValue(haystack)) { + if (!isValidNativeType(haystack, ['string', 'array'])) { throw new RuntimeError(`Expected second argument to be of type array or string, but found ${toString(typeOf(haystack))} instead.`); } diff --git a/src/style-spec/expression/definitions/index.js b/src/style-spec/expression/definitions/index.js index 3114f7c059c..fcc1535c8bf 100644 --- a/src/style-spec/expression/definitions/index.js +++ b/src/style-spec/expression/definitions/index.js @@ -24,8 +24,10 @@ import Assertion from './assertion'; import Coercion from './coercion'; import At from './at'; import In from './in'; +import IndexOf from './index_of'; import Match from './match'; import Case from './case'; +import Slice from './slice'; import Step from './step'; import Interpolate from './interpolate'; import Coalesce from './coalesce'; @@ -64,6 +66,7 @@ const expressions: ExpressionRegistry = { 'format': FormatExpression, 'image': ImageExpression, 'in': In, + 'index-of': IndexOf, 'interpolate': Interpolate, 'interpolate-hcl': Interpolate, 'interpolate-lab': Interpolate, @@ -74,6 +77,7 @@ const expressions: ExpressionRegistry = { 'number': Assertion, 'number-format': NumberFormat, 'object': Assertion, + 'slice': Slice, 'step': Step, 'string': Assertion, 'to-boolean': Coercion, diff --git a/src/style-spec/expression/definitions/index_of.js b/src/style-spec/expression/definitions/index_of.js new file mode 100644 index 00000000000..48e629335cf --- /dev/null +++ b/src/style-spec/expression/definitions/index_of.js @@ -0,0 +1,89 @@ +// @flow + +import {BooleanType, StringType, ValueType, NullType, toString, NumberType, isValidType, isValidNativeType} from '../types'; +import RuntimeError from '../runtime_error'; +import {typeOf} from '../values'; + +import type {Expression} from '../expression'; +import type ParsingContext from '../parsing_context'; +import type EvaluationContext from '../evaluation_context'; +import type {Type} from '../types'; + +class IndexOf implements Expression { + type: Type; + needle: Expression; + haystack: Expression; + fromIndex: ?Expression; + + constructor(needle: Expression, haystack: Expression, fromIndex?: Expression) { + this.type = NumberType; + this.needle = needle; + this.haystack = haystack; + this.fromIndex = fromIndex; + } + + static parse(args: $ReadOnlyArray, context: ParsingContext) { + if (args.length <= 2 || args.length >= 5) { + return context.error(`Expected 3 or 4 arguments, but found ${args.length - 1} instead.`); + } + + const needle = context.parse(args[1], 1, ValueType); + + const haystack = context.parse(args[2], 2, ValueType); + + if (!needle || !haystack) return null; + if (!isValidType(needle.type, [BooleanType, StringType, NumberType, NullType, ValueType])) { + return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${toString(needle.type)} instead`); + } + + if (args.length === 4) { + const fromIndex = context.parse(args[3], 3, NumberType); + if (!fromIndex) return null; + return new IndexOf(needle, haystack, fromIndex); + } else { + return new IndexOf(needle, haystack); + } + } + + evaluate(ctx: EvaluationContext) { + const needle = (this.needle.evaluate(ctx): any); + const haystack = (this.haystack.evaluate(ctx): any); + + if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) { + throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${toString(typeOf(needle))} instead.`); + } + + if (!isValidNativeType(haystack, ['string', 'array'])) { + throw new RuntimeError(`Expected second argument to be of type array or string, but found ${toString(typeOf(haystack))} instead.`); + } + + if (this.fromIndex) { + const fromIndex = (this.fromIndex.evaluate(ctx): number); + return haystack.indexOf(needle, fromIndex); + } + + return haystack.indexOf(needle); + } + + eachChild(fn: (_: Expression) => void) { + fn(this.needle); + fn(this.haystack); + if (this.fromIndex) { + fn(this.fromIndex); + } + } + + outputDefined() { + return false; + } + + serialize() { + if (this.fromIndex != null && this.fromIndex !== undefined) { + const fromIndex = this.fromIndex.serialize(); + return ["index-of", this.needle.serialize(), this.haystack.serialize(), fromIndex]; + } + return ["index-of", this.needle.serialize(), this.haystack.serialize()]; + } +} + +export default IndexOf; diff --git a/src/style-spec/expression/definitions/slice.js b/src/style-spec/expression/definitions/slice.js new file mode 100644 index 00000000000..09a47c10e9a --- /dev/null +++ b/src/style-spec/expression/definitions/slice.js @@ -0,0 +1,86 @@ +// @flow + +import {ValueType, NumberType, StringType, array, toString, isValidType, isValidNativeType} from '../types'; +import RuntimeError from '../runtime_error'; +import {typeOf} from '../values'; + +import type {Expression} from '../expression'; +import type ParsingContext from '../parsing_context'; +import type EvaluationContext from '../evaluation_context'; +import type {Type} from '../types'; + +class Slice implements Expression { + type: Type; + input: Expression; + beginIndex: Expression; + endIndex: ?Expression; + + constructor(type: Type, input: Expression, beginIndex: Expression, endIndex?: Expression) { + this.type = type; + this.input = input; + this.beginIndex = beginIndex; + this.endIndex = endIndex; + + } + + static parse(args: $ReadOnlyArray, context: ParsingContext) { + if (args.length <= 2 || args.length >= 5) { + return context.error(`Expected 3 or 4 arguments, but found ${args.length - 1} instead.`); + } + + const input = context.parse(args[1], 1, ValueType); + const beginIndex = context.parse(args[2], 2, NumberType); + + if (!input || !beginIndex) return null; + + if (!isValidType(input.type, [array(ValueType), StringType, ValueType])) { + return context.error(`Expected first argument to be of type array or string, but found ${toString(input.type)} instead`); + } + + if (args.length === 4) { + const endIndex = context.parse(args[3], 3, NumberType); + if (!endIndex) return null; + return new Slice(input.type, input, beginIndex, endIndex); + } else { + return new Slice(input.type, input, beginIndex); + } + } + + evaluate(ctx: EvaluationContext) { + const input = (this.input.evaluate(ctx): any); + const beginIndex = (this.beginIndex.evaluate(ctx): number); + + if (!isValidNativeType(input, ['string', 'array'])) { + throw new RuntimeError(`Expected first argument to be of type array or string, but found ${toString(typeOf(input))} instead.`); + } + + if (this.endIndex) { + const endIndex = (this.endIndex.evaluate(ctx): number); + return input.slice(beginIndex, endIndex); + } + + return input.slice(beginIndex); + } + + eachChild(fn: (_: Expression) => void) { + fn(this.input); + fn(this.beginIndex); + if (this.endIndex) { + fn(this.endIndex); + } + } + + outputDefined() { + return false; + } + + serialize() { + if (this.endIndex != null && this.endIndex !== undefined) { + const endIndex = this.endIndex.serialize(); + return ["slice", this.input.serialize(), this.beginIndex.serialize(), endIndex]; + } + return ["slice", this.input.serialize(), this.beginIndex.serialize()]; + } +} + +export default Slice; diff --git a/src/style-spec/expression/types.js b/src/style-spec/expression/types.js index c088b87041c..320c713de94 100644 --- a/src/style-spec/expression/types.js +++ b/src/style-spec/expression/types.js @@ -34,6 +34,8 @@ export type ArrayType = { N: ?number } +export type NativeType = 'number' | 'string' | 'boolean' | 'null' | 'array' | 'object' + export const NullType = {kind: 'null'}; export const NumberType = {kind: 'number'}; export const StringType = {kind: 'string'}; @@ -104,3 +106,21 @@ export function checkSubtype(expected: Type, t: Type): ?string { return `Expected ${toString(expected)} but found ${toString(t)} instead.`; } + +export function isValidType(provided: Type, allowedTypes: Array): boolean { + return allowedTypes.some(t => t.kind === provided.kind); +} + +export function isValidNativeType(provided: any, allowedTypes: Array): boolean { + return allowedTypes.some(t => { + if (t === 'null') { + return provided === null; + } else if (t === 'array') { + return Array.isArray(provided); + } else if (t === 'object') { + return provided && !Array.isArray(provided) && typeof provided === 'object'; + } else { + return t === typeof provided; + } + }); +} diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 31a9dd4619a..0ced8c311e0 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -2597,6 +2597,15 @@ } } }, + "index-of": { + "doc": "Returns the first index at which a given element can be found in an array, or for a string, the first occurrence of the specified value. If a second argument is provided, then the search is started from that position. Returns -1 if the value is not found.", + "group": "Lookup", + "sdk-support": { + "basic functionality": { + "js": "1.10.0" + } + } + }, "case": { "doc": "Selects the first output whose corresponding test condition evaluates to true, or the fallback value otherwise.", "group": "Decision", @@ -3520,6 +3529,15 @@ "macos": "0.9.0" } } + }, + "slice": { + "doc": "Returns a portion of a string or an array starting from the provided beginning index. If a second argument is provided, then the return portion will run to, but not include, the end index.", + "group": "String", + "sdk-support": { + "basic functionality": { + "js": "1.10.0" + } + } } } }, diff --git a/test/integration/expression-tests/in/assert-array/test.json b/test/integration/expression-tests/in/assert-array/test.json index e3da9d024f0..2aaf19b5064 100644 --- a/test/integration/expression-tests/in/assert-array/test.json +++ b/test/integration/expression-tests/in/assert-array/test.json @@ -7,6 +7,7 @@ [{}, {"properties": {"i": null, "arr": [9, 8, 7]}}], [{}, {"properties": {"i": 1, "arr": [9, 8, 7]}}], [{}, {"properties": {"i": 9, "arr": [9, 8, 7]}}], + [{}, {"properties": {"i": null, "arr": [9, 8, 7, null]}}], [{}, {"properties": {"i": 1, "arr": null}}] ], "expected": { @@ -20,6 +21,7 @@ false, false, true, + true, {"error":"Expected value to be of type array, but found null instead."} ], "serialized": [ diff --git a/test/integration/expression-tests/in/basic-array/test.json b/test/integration/expression-tests/in/basic-array/test.json index 91b2270d2c5..2bdd62bd061 100644 --- a/test/integration/expression-tests/in/basic-array/test.json +++ b/test/integration/expression-tests/in/basic-array/test.json @@ -9,6 +9,7 @@ [{}, {"properties": {"i": 9, "arr": [9, 8, 7]}}], [{}, {"properties": {"i": "foo", "arr": ["baz", "bar", "hello", "foo", "world"]}}], [{}, {"properties": {"i": true, "arr": ["foo", 123, null, 456, false, {}, true]}}], + [{}, {"properties": {"i": null, "arr": ["foo", 123, null, 456, false, {}, true]}}], [{}, {"properties": {"i": 1, "arr": null}}] ], "expected": { @@ -24,6 +25,7 @@ true, true, true, + true, false ], "serialized": [ diff --git a/test/integration/expression-tests/in/invalid-needle/test.json b/test/integration/expression-tests/in/invalid-needle/test.json index 0ff5cae1b2e..e70ee8b12f1 100644 --- a/test/integration/expression-tests/in/invalid-needle/test.json +++ b/test/integration/expression-tests/in/invalid-needle/test.json @@ -15,8 +15,8 @@ "type": "boolean" }, "outputs": [ - {"error":"Expected first argument to be of type boolean, string or number, but found object instead."}, - {"error":"Expected first argument to be of type boolean, string or number, but found object instead."} + {"error":"Expected first argument to be of type boolean, string, number or null, but found object instead."}, + {"error":"Expected first argument to be of type boolean, string, number or null, but found object instead."} ], "serialized": [ "boolean", diff --git a/test/integration/expression-tests/index-of/assert-array/test.json b/test/integration/expression-tests/index-of/assert-array/test.json new file mode 100644 index 00000000000..eb8db5fc0ca --- /dev/null +++ b/test/integration/expression-tests/index-of/assert-array/test.json @@ -0,0 +1,26 @@ +{ + "expression": ["index-of", ["get", "i"], ["array", ["get", "arr"]]], + "inputs": [ + [{}, { "properties": { "i": null, "arr": [9, 8, 7] } }], + [{}, { "properties": { "i": null, "arr": [9, 8, 7, null] } }], + [{}, { "properties": { "i": 1, "arr": [9, 8, 7] } }], + [{}, { "properties": { "i": 9, "arr": [9, 8, 7, 9] } }], + [{}, { "properties": { "i": 1, "arr": null } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "number" + }, + "outputs": [ + -1, + 3, + -1, + 0, + { "error": "Expected value to be of type array, but found null instead." } + ], + "serialized": ["index-of", ["get", "i"], ["array", ["get", "arr"]]] + } +} diff --git a/test/integration/expression-tests/index-of/assert-string/test.json b/test/integration/expression-tests/index-of/assert-string/test.json new file mode 100644 index 00000000000..169c9808aef --- /dev/null +++ b/test/integration/expression-tests/index-of/assert-string/test.json @@ -0,0 +1,26 @@ +{ + "expression": ["index-of", ["get", "substr"], ["string", ["get", "str"]]], + "inputs": [ + [{}, { "properties": { "substr": null, "str": "helloworld" } }], + [{}, { "properties": { "substr": "foo", "str": "helloworld" } }], + [{}, { "properties": { "substr": "low", "str": "helloworld" } }], + [{}, { "properties": { "substr": "low", "str": null } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "number" + }, + "outputs": [ + -1, + -1, + 3, + { + "error": "Expected value to be of type string, but found null instead." + } + ], + "serialized": ["index-of", ["get", "substr"], ["string", ["get", "str"]]] + } +} diff --git a/test/integration/expression-tests/index-of/basic-array/test.json b/test/integration/expression-tests/index-of/basic-array/test.json new file mode 100644 index 00000000000..ec4c61c5a9b --- /dev/null +++ b/test/integration/expression-tests/index-of/basic-array/test.json @@ -0,0 +1,46 @@ +{ + "expression": ["index-of", ["get", "i"], ["get", "arr"]], + "inputs": [ + [{}, { "properties": { "i": null, "arr": [9, 8, 7] } }], + [{}, { "properties": { "i": 1, "arr": [9, 8, 7] } }], + [{}, { "properties": { "i": 9, "arr": [9, 8, 7] } }], + [ + {}, + { + "properties": { + "i": "foo", + "arr": ["baz", "bar", "hello", "foo", "world"] + } + } + ], + [ + {}, + { + "properties": { + "i": true, + "arr": ["foo", 123, null, 456, false, {}, true] + } + } + ], + [{}, { "properties": { "i": 1, "arr": null } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "number" + }, + "outputs": [ + -1, + -1, + 0, + 3, + 6, + { + "error": "Expected second argument to be of type array or string, but found null instead." + } + ], + "serialized": ["index-of", ["get", "i"], ["get", "arr"]] + } +} diff --git a/test/integration/expression-tests/index-of/basic-string/test.json b/test/integration/expression-tests/index-of/basic-string/test.json new file mode 100644 index 00000000000..3e27a80d168 --- /dev/null +++ b/test/integration/expression-tests/index-of/basic-string/test.json @@ -0,0 +1,32 @@ +{ + "expression": ["index-of", ["get", "substr"], ["get", "str"]], + "inputs": [ + [{}, { "properties": { "substr": null, "str": "helloworld" } }], + [{}, { "properties": { "substr": "foo", "str": "helloworld" } }], + [{}, { "properties": { "substr": "low", "str": "helloworld" } }], + [{}, { "properties": { "substr": true, "str": "falsetrue" } }], + [{}, { "properties": { "substr": false, "str": "falsetrue" } }], + [{}, { "properties": { "substr": 123, "str": "hello123world" } }], + [{}, { "properties": { "substr": "low", "str": null } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "number" + }, + "outputs": [ + -1, + -1, + 3, + 5, + 0, + 5, + { + "error": "Expected second argument to be of type array or string, but found null instead." + } + ], + "serialized": ["index-of", ["get", "substr"], ["get", "str"]] + } +} diff --git a/test/integration/expression-tests/index-of/invalid-haystack/test.json b/test/integration/expression-tests/index-of/invalid-haystack/test.json new file mode 100644 index 00000000000..519d706cbc1 --- /dev/null +++ b/test/integration/expression-tests/index-of/invalid-haystack/test.json @@ -0,0 +1,28 @@ +{ + "expression": ["index-of", ["get", "needle"], ["get", "haystack"]], + "inputs": [ + [{}, { "properties": { "needle": 1, "haystack": 123 } }], + [{}, { "properties": { "needle": "foo", "haystack": {} } }], + [{}, { "properties": { "needle": "foo", "haystack": null } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "number" + }, + "outputs": [ + { + "error": "Expected second argument to be of type array or string, but found number instead." + }, + { + "error": "Expected second argument to be of type array or string, but found object instead." + }, + { + "error": "Expected second argument to be of type array or string, but found null instead." + } + ], + "serialized": ["index-of", ["get", "needle"], ["get", "haystack"]] + } +} diff --git a/test/integration/expression-tests/index-of/invalid-needle/test.json b/test/integration/expression-tests/index-of/invalid-needle/test.json new file mode 100644 index 00000000000..c74a3489d3a --- /dev/null +++ b/test/integration/expression-tests/index-of/invalid-needle/test.json @@ -0,0 +1,24 @@ +{ + "expression": ["index-of", ["get", "needle"], ["get", "haystack"]], + "inputs": [ + [{}, { "properties": { "needle": {}, "haystack": [9, 8, 7] } }], + [{}, { "properties": { "needle": {}, "haystack": "helloworld" } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "number" + }, + "outputs": [ + { + "error": "Expected first argument to be of type boolean, string, number or null, but found object instead." + }, + { + "error": "Expected first argument to be of type boolean, string, number or null, but found object instead." + } + ], + "serialized": ["index-of", ["get", "needle"], ["get", "haystack"]] + } +} diff --git a/test/integration/expression-tests/index-of/with-from-index/test.json b/test/integration/expression-tests/index-of/with-from-index/test.json new file mode 100644 index 00000000000..e168817f569 --- /dev/null +++ b/test/integration/expression-tests/index-of/with-from-index/test.json @@ -0,0 +1,43 @@ +{ + "expression": ["index-of", ["get", "needle"], ["get", "hay"], ["get", "i"]], + "inputs": [ + [{}, { "properties": { "needle": null, "hay": "helloworld", "i": 0 } }], + [{}, { "properties": { "needle": "foo", "hay": "helloworld", "i": 0 } }], + [{}, { "properties": { "needle": "low", "hay": "helloworldlow", "i": 4 } }], + [{}, { "properties": { "needle": true, "hay": "falsetruetrue", "i": 6 } }], + [{}, { "properties": { "needle": false, "hay": "falsetrue", "i": 0 } }], + [{}, { "properties": { "needle": 123, "hay": "hello123world", "i": 6 } }], + [{}, { "properties": { "needle": "low", "hay": null, "i": 0 } }], + [{}, { "properties": { "needle": 7, "hay": [9, 8, 7, 8, 7, 7], "i": 3 } }], + [{}, { "properties": { "needle": 9, "hay": [9, 8, 7, 8, 7, 7], "i": 1 } }], + [{}, { "properties": { "needle": 8, "hay": [9, 8, 7, 8, 7, 7], "i": 1 } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "number" + }, + "outputs": [ + -1, + -1, + 10, + 9, + 0, + -1, + { + "error": "Expected second argument to be of type array or string, but found null instead." + }, + 4, + -1, + 1 + ], + "serialized": [ + "index-of", + ["get", "needle"], + ["get", "hay"], + ["number", ["get", "i"]] + ] + } +} diff --git a/test/integration/expression-tests/slice/array-one-index/test.json b/test/integration/expression-tests/slice/array-one-index/test.json new file mode 100644 index 00000000000..485cc3cd599 --- /dev/null +++ b/test/integration/expression-tests/slice/array-one-index/test.json @@ -0,0 +1,23 @@ +{ + "expression": ["slice", ["array", ["get", "val"]], ["get", "index"]], + "inputs": [ + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "index": 2 } }], + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "index": 0 } }], + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "index": 99 } }], + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "index": -2 } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "array" + }, + "outputs": [[3, 4, 5], [1, 2, 3, 4, 5], [], [4, 5]], + "serialized": [ + "slice", + ["array", ["get", "val"]], + ["number", ["get", "index"]] + ] + } +} diff --git a/test/integration/expression-tests/slice/array-two-indexes/test.json b/test/integration/expression-tests/slice/array-two-indexes/test.json new file mode 100644 index 00000000000..3cf98723e15 --- /dev/null +++ b/test/integration/expression-tests/slice/array-two-indexes/test.json @@ -0,0 +1,31 @@ +{ + "expression": [ + "slice", + ["array", ["get", "val"]], + ["get", "i1"], + ["get", "i2"] + ], + "inputs": [ + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "i1": 2, "i2": 4 } }], + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "i1": 1, "i2": 5 } }], + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "i1": 1, "i2": 99 } }], + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "i1": -4, "i2": -2 } }], + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "i1": 0, "i2": -1 } }], + [{}, { "properties": { "val": [1, 2, 3, 4, 5], "i1": 0, "i2": 0 } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "array" + }, + "outputs": [[3, 4], [2, 3, 4, 5], [2, 3, 4, 5], [2, 3], [1, 2, 3, 4], []], + "serialized": [ + "slice", + ["array", ["get", "val"]], + ["number", ["get", "i1"]], + ["number", ["get", "i2"]] + ] + } +} diff --git a/test/integration/expression-tests/slice/invalid-inputs/test.json b/test/integration/expression-tests/slice/invalid-inputs/test.json new file mode 100644 index 00000000000..408ba716db3 --- /dev/null +++ b/test/integration/expression-tests/slice/invalid-inputs/test.json @@ -0,0 +1,40 @@ +{ + "expression": ["slice", ["get", "input"], ["get", "i1"]], + "inputs": [ + [{}, { "properties": { "input": false, "i1": 1 } }], + [{}, { "properties": { "input": null, "i1": 1 } }], + [{}, { "properties": { "input": 12, "i1": 1 } }], + [{}, { "properties": { "input": {}, "i1": 1 } }], + [{}, { "properties": { "other": 1, "i1": 1 } }], + [{}, { "properties": { "input": "correct", "i1": "one" } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "value" + }, + "outputs": [ + { + "error": "Expected first argument to be of type array or string, but found boolean instead." + }, + { + "error": "Expected first argument to be of type array or string, but found null instead." + }, + { + "error": "Expected first argument to be of type array or string, but found number instead." + }, + { + "error": "Expected first argument to be of type array or string, but found object instead." + }, + { + "error": "Expected first argument to be of type array or string, but found null instead." + }, + { + "error": "Expected value to be of type number, but found string instead." + } + ], + "serialized": ["slice", ["get", "input"], ["number", ["get", "i1"]]] + } +} diff --git a/test/integration/expression-tests/slice/string-one-index/test.json b/test/integration/expression-tests/slice/string-one-index/test.json new file mode 100644 index 00000000000..e27c27b39fd --- /dev/null +++ b/test/integration/expression-tests/slice/string-one-index/test.json @@ -0,0 +1,19 @@ +{ + "expression": ["slice", ["get", "val"], ["get", "index"]], + "inputs": [ + [{}, { "properties": { "val": "0123456789", "index": 0 } }], + [{}, { "properties": { "val": "0123456789", "index": 4 } }], + [{}, { "properties": { "val": "0123456789", "index": 99 } }], + [{}, { "properties": { "val": "0123456789", "index": -2 } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "value" + }, + "outputs": ["0123456789", "456789", "", "89"], + "serialized": ["slice", ["get", "val"], ["number", ["get", "index"]]] + } +} diff --git a/test/integration/expression-tests/slice/string-two-indexes/test.json b/test/integration/expression-tests/slice/string-two-indexes/test.json new file mode 100644 index 00000000000..39089a83c4f --- /dev/null +++ b/test/integration/expression-tests/slice/string-two-indexes/test.json @@ -0,0 +1,25 @@ +{ + "expression": ["slice", ["get", "val"], ["get", "i1"], ["get", "i2"]], + "inputs": [ + [{}, { "properties": { "val": "0123456789", "i1": 1, "i2": 8 } }], + [{}, { "properties": { "val": "0123456789", "i1": 4, "i2": -2 } }], + [{}, { "properties": { "val": "0123456789", "i1": -3, "i2": -1 } }], + [{}, { "properties": { "val": "0123456789", "i1": 0, "i2": -1 } }], + [{}, { "properties": { "val": "0123456789", "i1": 0, "i2": 99 } }] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "value" + }, + "outputs": ["1234567", "4567", "78", "012345678", "0123456789"], + "serialized": [ + "slice", + ["get", "val"], + ["number", ["get", "i1"]], + ["number", ["get", "i2"]] + ] + } +}