add addzone and update zone in map-adapter interface
This commit is contained in:
Generated
Vendored
+761
@@ -0,0 +1,761 @@
|
||||
import {typeToString,
|
||||
NumberType,
|
||||
StringType,
|
||||
BooleanType,
|
||||
ColorType,
|
||||
ObjectType,
|
||||
ValueType,
|
||||
ErrorType,
|
||||
CollatorType,
|
||||
array,
|
||||
} from './types';
|
||||
|
||||
import {ParsingContext} from './parsing_context';
|
||||
import {EvaluationContext} from './evaluation_context';
|
||||
|
||||
import {expressions} from './definitions/index';
|
||||
import {CollatorExpression} from './definitions/collator';
|
||||
import {Within} from './definitions/within';
|
||||
import {Literal} from './definitions/literal';
|
||||
import {Assertion} from './definitions/assertion';
|
||||
import {Coercion} from './definitions/coercion';
|
||||
import {Var} from './definitions/var';
|
||||
import {Distance} from './definitions/distance';
|
||||
import {GlobalState} from './definitions/global_state';
|
||||
|
||||
import type {Expression, ExpressionRegistry} from './expression';
|
||||
import type {Value} from './values';
|
||||
import type {Type} from './types';
|
||||
|
||||
import {typeOf, validateRGBA, valueToString} from './values';
|
||||
import {RuntimeError} from './runtime_error';
|
||||
import {Color} from './types/color';
|
||||
|
||||
export type Varargs = {
|
||||
type: Type;
|
||||
};
|
||||
type Signature = Array<Type> | Varargs;
|
||||
type Evaluate = (b: EvaluationContext, a: Array<Expression>) => Value;
|
||||
|
||||
type Definition = [Type, Signature, Evaluate] | {
|
||||
type: Type;
|
||||
overloads: Array<[Signature, Evaluate]>;
|
||||
};
|
||||
|
||||
export class CompoundExpression implements Expression {
|
||||
name: string;
|
||||
type: Type;
|
||||
_evaluate: Evaluate;
|
||||
args: Array<Expression>;
|
||||
|
||||
static definitions: {[_: string]: Definition};
|
||||
|
||||
constructor(name: string, type: Type, evaluate: Evaluate, args: Array<Expression>) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this._evaluate = evaluate;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext) {
|
||||
return this._evaluate(ctx, this.args);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
this.args.forEach(fn);
|
||||
}
|
||||
|
||||
outputDefined() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Expression {
|
||||
const op: string = (args[0] as any);
|
||||
const definition = CompoundExpression.definitions[op];
|
||||
if (!definition) {
|
||||
return context.error(`Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, 0) as null;
|
||||
}
|
||||
|
||||
// Now check argument types against each signature
|
||||
const type = Array.isArray(definition) ?
|
||||
definition[0] : definition.type;
|
||||
|
||||
const availableOverloads = Array.isArray(definition) ?
|
||||
[[definition[1], definition[2]]] :
|
||||
definition.overloads;
|
||||
|
||||
const overloads = availableOverloads.filter(([signature]) => (
|
||||
!Array.isArray(signature) || // varags
|
||||
signature.length === args.length - 1 // correct param count
|
||||
));
|
||||
|
||||
let signatureContext: ParsingContext = null;
|
||||
|
||||
for (const [params, evaluate] of overloads) {
|
||||
// Use a fresh context for each attempted signature so that, if
|
||||
// we eventually succeed, we haven't polluted `context.errors`.
|
||||
signatureContext = new ParsingContext(context.registry, isExpressionConstant, context.path, null, context.scope);
|
||||
|
||||
// First parse all the args, potentially coercing to the
|
||||
// types expected by this overload.
|
||||
const parsedArgs: Array<Expression> = [];
|
||||
let argParseFailed = false;
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
const expectedType = Array.isArray(params) ?
|
||||
params[i - 1] :
|
||||
(params as Varargs).type;
|
||||
|
||||
const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType);
|
||||
if (!parsed) {
|
||||
argParseFailed = true;
|
||||
break;
|
||||
}
|
||||
parsedArgs.push(parsed);
|
||||
}
|
||||
if (argParseFailed) {
|
||||
// Couldn't coerce args of this overload to expected type, move
|
||||
// on to next one.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(params)) {
|
||||
if (params.length !== parsedArgs.length) {
|
||||
signatureContext.error(`Expected ${params.length} arguments, but found ${parsedArgs.length} instead.`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < parsedArgs.length; i++) {
|
||||
const expected = Array.isArray(params) ? params[i] : (params as Varargs).type;
|
||||
const arg = parsedArgs[i];
|
||||
signatureContext.concat(i + 1).checkSubtype(expected, arg.type);
|
||||
}
|
||||
|
||||
if (signatureContext.errors.length === 0) {
|
||||
return new CompoundExpression(op, type, evaluate as Evaluate, parsedArgs);
|
||||
}
|
||||
}
|
||||
|
||||
if (overloads.length === 1) {
|
||||
context.errors.push(...signatureContext.errors);
|
||||
} else {
|
||||
const expected = overloads.length ? overloads : availableOverloads;
|
||||
const signatures = expected
|
||||
.map(([params]) => stringifySignature(params as Signature))
|
||||
.join(' | ');
|
||||
|
||||
const actualTypes = [];
|
||||
// For error message, re-parse arguments without trying to
|
||||
// apply any coercions
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
const parsed = context.parse(args[i], 1 + actualTypes.length);
|
||||
if (!parsed) return null;
|
||||
actualTypes.push(typeToString(parsed.type));
|
||||
}
|
||||
context.error(`Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static register(
|
||||
registry: ExpressionRegistry,
|
||||
definitions: {[_: string]: Definition}
|
||||
) {
|
||||
CompoundExpression.definitions = definitions;
|
||||
for (const name in definitions) {
|
||||
registry[name] = CompoundExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rgba(ctx, [r, g, b, a]) {
|
||||
r = r.evaluate(ctx);
|
||||
g = g.evaluate(ctx);
|
||||
b = b.evaluate(ctx);
|
||||
const alpha = a ? a.evaluate(ctx) : 1;
|
||||
const error = validateRGBA(r, g, b, alpha);
|
||||
if (error) throw new RuntimeError(error);
|
||||
return new Color(r / 255, g / 255, b / 255, alpha, false);
|
||||
}
|
||||
|
||||
function has(key, obj) {
|
||||
return key in obj;
|
||||
}
|
||||
|
||||
function get(key, obj) {
|
||||
const v = obj[key];
|
||||
return typeof v === 'undefined' ? null : v;
|
||||
}
|
||||
|
||||
function binarySearch(v, a, i, j) {
|
||||
while (i <= j) {
|
||||
const m = (i + j) >> 1;
|
||||
if (a[m] === v)
|
||||
return true;
|
||||
if (a[m] > v)
|
||||
j = m - 1;
|
||||
else
|
||||
i = m + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function varargs(type: Type): Varargs {
|
||||
return {type};
|
||||
}
|
||||
|
||||
CompoundExpression.register(expressions, {
|
||||
'error': [
|
||||
ErrorType,
|
||||
[StringType],
|
||||
(ctx, [v]) => { throw new RuntimeError(v.evaluate(ctx)); }
|
||||
],
|
||||
'typeof': [
|
||||
StringType,
|
||||
[ValueType],
|
||||
(ctx, [v]) => typeToString(typeOf(v.evaluate(ctx)))
|
||||
],
|
||||
'to-rgba': [
|
||||
array(NumberType, 4),
|
||||
[ColorType],
|
||||
(ctx, [v]) => {
|
||||
const [r, g, b, a] = v.evaluate(ctx).rgb;
|
||||
return [r * 255, g * 255, b * 255, a];
|
||||
},
|
||||
],
|
||||
'rgb': [
|
||||
ColorType,
|
||||
[NumberType, NumberType, NumberType],
|
||||
rgba
|
||||
],
|
||||
'rgba': [
|
||||
ColorType,
|
||||
[NumberType, NumberType, NumberType, NumberType],
|
||||
rgba
|
||||
],
|
||||
'has': {
|
||||
type: BooleanType,
|
||||
overloads: [
|
||||
[
|
||||
[StringType],
|
||||
(ctx, [key]) => has(key.evaluate(ctx), ctx.properties())
|
||||
], [
|
||||
[StringType, ObjectType],
|
||||
(ctx, [key, obj]) => has(key.evaluate(ctx), obj.evaluate(ctx))
|
||||
]
|
||||
]
|
||||
},
|
||||
'get': {
|
||||
type: ValueType,
|
||||
overloads: [
|
||||
[
|
||||
[StringType],
|
||||
(ctx, [key]) => get(key.evaluate(ctx), ctx.properties())
|
||||
], [
|
||||
[StringType, ObjectType],
|
||||
(ctx, [key, obj]) => get(key.evaluate(ctx), obj.evaluate(ctx))
|
||||
]
|
||||
]
|
||||
},
|
||||
'feature-state': [
|
||||
ValueType,
|
||||
[StringType],
|
||||
(ctx, [key]) => get(key.evaluate(ctx), ctx.featureState || {})
|
||||
],
|
||||
'properties': [
|
||||
ObjectType,
|
||||
[],
|
||||
(ctx) => ctx.properties()
|
||||
],
|
||||
'geometry-type': [
|
||||
StringType,
|
||||
[],
|
||||
(ctx) => ctx.geometryType()
|
||||
],
|
||||
'id': [
|
||||
ValueType,
|
||||
[],
|
||||
(ctx) => ctx.id()
|
||||
],
|
||||
'zoom': [
|
||||
NumberType,
|
||||
[],
|
||||
(ctx) => ctx.globals.zoom
|
||||
],
|
||||
'heatmap-density': [
|
||||
NumberType,
|
||||
[],
|
||||
(ctx) => ctx.globals.heatmapDensity || 0
|
||||
],
|
||||
'elevation': [
|
||||
NumberType,
|
||||
[],
|
||||
(ctx) => ctx.globals.elevation || 0
|
||||
],
|
||||
'line-progress': [
|
||||
NumberType,
|
||||
[],
|
||||
(ctx) => ctx.globals.lineProgress || 0
|
||||
],
|
||||
'accumulated': [
|
||||
ValueType,
|
||||
[],
|
||||
(ctx) => ctx.globals.accumulated === undefined ? null : ctx.globals.accumulated
|
||||
],
|
||||
'+': [
|
||||
NumberType,
|
||||
varargs(NumberType),
|
||||
(ctx, args) => {
|
||||
let result = 0;
|
||||
for (const arg of args) {
|
||||
result += arg.evaluate(ctx);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
],
|
||||
'*': [
|
||||
NumberType,
|
||||
varargs(NumberType),
|
||||
(ctx, args) => {
|
||||
let result = 1;
|
||||
for (const arg of args) {
|
||||
result *= arg.evaluate(ctx);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
],
|
||||
'-': {
|
||||
type: NumberType,
|
||||
overloads: [
|
||||
[
|
||||
[NumberType, NumberType],
|
||||
(ctx, [a, b]) => a.evaluate(ctx) - b.evaluate(ctx)
|
||||
], [
|
||||
[NumberType],
|
||||
(ctx, [a]) => -a.evaluate(ctx)
|
||||
]
|
||||
]
|
||||
},
|
||||
'/': [
|
||||
NumberType,
|
||||
[NumberType, NumberType],
|
||||
(ctx, [a, b]) => a.evaluate(ctx) / b.evaluate(ctx)
|
||||
],
|
||||
'%': [
|
||||
NumberType,
|
||||
[NumberType, NumberType],
|
||||
(ctx, [a, b]) => a.evaluate(ctx) % b.evaluate(ctx)
|
||||
],
|
||||
'ln2': [
|
||||
NumberType,
|
||||
[],
|
||||
() => Math.LN2
|
||||
],
|
||||
'pi': [
|
||||
NumberType,
|
||||
[],
|
||||
() => Math.PI
|
||||
],
|
||||
'e': [
|
||||
NumberType,
|
||||
[],
|
||||
() => Math.E
|
||||
],
|
||||
'^': [
|
||||
NumberType,
|
||||
[NumberType, NumberType],
|
||||
(ctx, [b, e]) => Math.pow(b.evaluate(ctx), e.evaluate(ctx))
|
||||
],
|
||||
'sqrt': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [x]) => Math.sqrt(x.evaluate(ctx))
|
||||
],
|
||||
'log10': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN10
|
||||
],
|
||||
'ln': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.log(n.evaluate(ctx))
|
||||
],
|
||||
'log2': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN2
|
||||
],
|
||||
'sin': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.sin(n.evaluate(ctx))
|
||||
],
|
||||
'cos': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.cos(n.evaluate(ctx))
|
||||
],
|
||||
'tan': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.tan(n.evaluate(ctx))
|
||||
],
|
||||
'asin': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.asin(n.evaluate(ctx))
|
||||
],
|
||||
'acos': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.acos(n.evaluate(ctx))
|
||||
],
|
||||
'atan': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.atan(n.evaluate(ctx))
|
||||
],
|
||||
'min': [
|
||||
NumberType,
|
||||
varargs(NumberType),
|
||||
(ctx, args) => Math.min(...args.map(arg => arg.evaluate(ctx)))
|
||||
],
|
||||
'max': [
|
||||
NumberType,
|
||||
varargs(NumberType),
|
||||
(ctx, args) => Math.max(...args.map(arg => arg.evaluate(ctx)))
|
||||
],
|
||||
'abs': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.abs(n.evaluate(ctx))
|
||||
],
|
||||
'round': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => {
|
||||
const v = n.evaluate(ctx);
|
||||
// Javascript's Math.round() rounds towards +Infinity for halfway
|
||||
// values, even when they're negative. It's more common to round
|
||||
// away from 0 (e.g., this is what python and C++ do)
|
||||
return v < 0 ? -Math.round(-v) : Math.round(v);
|
||||
}
|
||||
],
|
||||
'floor': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.floor(n.evaluate(ctx))
|
||||
],
|
||||
'ceil': [
|
||||
NumberType,
|
||||
[NumberType],
|
||||
(ctx, [n]) => Math.ceil(n.evaluate(ctx))
|
||||
],
|
||||
'filter-==': [
|
||||
BooleanType,
|
||||
[StringType, ValueType],
|
||||
(ctx, [k, v]) => ctx.properties()[(k as any).value] === (v as any).value
|
||||
],
|
||||
'filter-id-==': [
|
||||
BooleanType,
|
||||
[ValueType],
|
||||
(ctx, [v]) => ctx.id() === (v as any).value
|
||||
],
|
||||
'filter-type-==': [
|
||||
BooleanType,
|
||||
[StringType],
|
||||
(ctx, [v]) => ctx.geometryType() === (v as any).value
|
||||
],
|
||||
'filter-<': [
|
||||
BooleanType,
|
||||
[StringType, ValueType],
|
||||
(ctx, [k, v]) => {
|
||||
const a = ctx.properties()[(k as any).value];
|
||||
const b = (v as any).value;
|
||||
return typeof a === typeof b && a < b;
|
||||
}
|
||||
],
|
||||
'filter-id-<': [
|
||||
BooleanType,
|
||||
[ValueType],
|
||||
(ctx, [v]) => {
|
||||
const a = ctx.id();
|
||||
const b = (v as any).value;
|
||||
return typeof a === typeof b && a < b;
|
||||
}
|
||||
],
|
||||
'filter->': [
|
||||
BooleanType,
|
||||
[StringType, ValueType],
|
||||
(ctx, [k, v]) => {
|
||||
const a = ctx.properties()[(k as any).value];
|
||||
const b = (v as any).value;
|
||||
return typeof a === typeof b && a > b;
|
||||
}
|
||||
],
|
||||
'filter-id->': [
|
||||
BooleanType,
|
||||
[ValueType],
|
||||
(ctx, [v]) => {
|
||||
const a = ctx.id();
|
||||
const b = (v as any).value;
|
||||
return typeof a === typeof b && a > b;
|
||||
}
|
||||
],
|
||||
'filter-<=': [
|
||||
BooleanType,
|
||||
[StringType, ValueType],
|
||||
(ctx, [k, v]) => {
|
||||
const a = ctx.properties()[(k as any).value];
|
||||
const b = (v as any).value;
|
||||
return typeof a === typeof b && a <= b;
|
||||
}
|
||||
],
|
||||
'filter-id-<=': [
|
||||
BooleanType,
|
||||
[ValueType],
|
||||
(ctx, [v]) => {
|
||||
const a = ctx.id();
|
||||
const b = (v as any).value;
|
||||
return typeof a === typeof b && a <= b;
|
||||
}
|
||||
],
|
||||
'filter->=': [
|
||||
BooleanType,
|
||||
[StringType, ValueType],
|
||||
(ctx, [k, v]) => {
|
||||
const a = ctx.properties()[(k as any).value];
|
||||
const b = (v as any).value;
|
||||
return typeof a === typeof b && a >= b;
|
||||
}
|
||||
],
|
||||
'filter-id->=': [
|
||||
BooleanType,
|
||||
[ValueType],
|
||||
(ctx, [v]) => {
|
||||
const a = ctx.id();
|
||||
const b = (v as any).value;
|
||||
return typeof a === typeof b && a >= b;
|
||||
}
|
||||
],
|
||||
'filter-has': [
|
||||
BooleanType,
|
||||
[ValueType],
|
||||
(ctx, [k]) => (k as any).value in ctx.properties()
|
||||
],
|
||||
'filter-has-id': [
|
||||
BooleanType,
|
||||
[],
|
||||
(ctx) => (ctx.id() !== null && ctx.id() !== undefined)
|
||||
],
|
||||
'filter-type-in': [
|
||||
BooleanType,
|
||||
[array(StringType)],
|
||||
(ctx, [v]) => (v as any).value.indexOf(ctx.geometryType()) >= 0
|
||||
],
|
||||
'filter-id-in': [
|
||||
BooleanType,
|
||||
[array(ValueType)],
|
||||
(ctx, [v]) => (v as any).value.indexOf(ctx.id()) >= 0
|
||||
],
|
||||
'filter-in-small': [
|
||||
BooleanType,
|
||||
[StringType, array(ValueType)],
|
||||
// assumes v is an array literal
|
||||
(ctx, [k, v]) => (v as any).value.indexOf(ctx.properties()[(k as any).value]) >= 0
|
||||
],
|
||||
'filter-in-large': [
|
||||
BooleanType,
|
||||
[StringType, array(ValueType)],
|
||||
// assumes v is a array literal with values sorted in ascending order and of a single type
|
||||
(ctx, [k, v]) => binarySearch(ctx.properties()[(k as any).value], (v as any).value, 0, (v as any).value.length - 1)
|
||||
],
|
||||
'all': {
|
||||
type: BooleanType,
|
||||
overloads: [
|
||||
[
|
||||
[BooleanType, BooleanType],
|
||||
(ctx, [a, b]) => a.evaluate(ctx) && b.evaluate(ctx)
|
||||
],
|
||||
[
|
||||
varargs(BooleanType),
|
||||
(ctx, args) => {
|
||||
for (const arg of args) {
|
||||
if (!arg.evaluate(ctx))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
'any': {
|
||||
type: BooleanType,
|
||||
overloads: [
|
||||
[
|
||||
[BooleanType, BooleanType],
|
||||
(ctx, [a, b]) => a.evaluate(ctx) || b.evaluate(ctx)
|
||||
],
|
||||
[
|
||||
varargs(BooleanType),
|
||||
(ctx, args) => {
|
||||
for (const arg of args) {
|
||||
if (arg.evaluate(ctx))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
'!': [
|
||||
BooleanType,
|
||||
[BooleanType],
|
||||
(ctx, [b]) => !b.evaluate(ctx)
|
||||
],
|
||||
'is-supported-script': [
|
||||
BooleanType,
|
||||
[StringType],
|
||||
// At parse time this will always return true, so we need to exclude this expression with isGlobalPropertyConstant
|
||||
(ctx, [s]) => {
|
||||
const isSupportedScript = ctx.globals && ctx.globals.isSupportedScript;
|
||||
if (isSupportedScript) {
|
||||
return isSupportedScript(s.evaluate(ctx));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
],
|
||||
'upcase': [
|
||||
StringType,
|
||||
[StringType],
|
||||
(ctx, [s]) => s.evaluate(ctx).toUpperCase()
|
||||
],
|
||||
'downcase': [
|
||||
StringType,
|
||||
[StringType],
|
||||
(ctx, [s]) => s.evaluate(ctx).toLowerCase()
|
||||
],
|
||||
'concat': [
|
||||
StringType,
|
||||
varargs(ValueType),
|
||||
(ctx, args) => args.map(arg => valueToString(arg.evaluate(ctx))).join('')
|
||||
],
|
||||
'resolved-locale': [
|
||||
StringType,
|
||||
[CollatorType],
|
||||
(ctx, [collator]) => collator.evaluate(ctx).resolvedLocale()
|
||||
]
|
||||
});
|
||||
|
||||
function stringifySignature(signature: Signature): string {
|
||||
if (Array.isArray(signature)) {
|
||||
return `(${signature.map(typeToString).join(', ')})`;
|
||||
} else {
|
||||
return `(${typeToString(signature.type)}...)`;
|
||||
}
|
||||
}
|
||||
|
||||
function isExpressionConstant(expression: Expression) {
|
||||
if (expression instanceof Var) {
|
||||
return isExpressionConstant(expression.boundExpression);
|
||||
} else if (expression instanceof CompoundExpression && expression.name === 'error') {
|
||||
return false;
|
||||
} else if (expression instanceof CollatorExpression) {
|
||||
// Although the results of a Collator expression with fixed arguments
|
||||
// generally shouldn't change between executions, we can't serialize them
|
||||
// as constant expressions because results change based on environment.
|
||||
return false;
|
||||
} else if (expression instanceof Within) {
|
||||
return false;
|
||||
} else if (expression instanceof Distance) {
|
||||
return false;
|
||||
} else if (expression instanceof GlobalState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isTypeAnnotation = expression instanceof Coercion ||
|
||||
expression instanceof Assertion;
|
||||
|
||||
let childrenConstant = true;
|
||||
expression.eachChild(child => {
|
||||
// We can _almost_ assume that if `expressions` children are constant,
|
||||
// they would already have been evaluated to Literal values when they
|
||||
// were parsed. Type annotations are the exception, because they might
|
||||
// have been inferred and added after a child was parsed.
|
||||
|
||||
// So we recurse into isConstant() for the children of type annotations,
|
||||
// but otherwise simply check whether they are Literals.
|
||||
if (isTypeAnnotation) {
|
||||
childrenConstant = childrenConstant && isExpressionConstant(child);
|
||||
} else {
|
||||
childrenConstant = childrenConstant && child instanceof Literal;
|
||||
}
|
||||
});
|
||||
if (!childrenConstant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isFeatureConstant(expression) &&
|
||||
isGlobalPropertyConstant(expression,
|
||||
['zoom', 'heatmap-density', 'elevation', 'line-progress', 'accumulated', 'is-supported-script']);
|
||||
}
|
||||
|
||||
function isFeatureConstant(e: Expression) {
|
||||
if (e instanceof CompoundExpression) {
|
||||
if (e.name === 'get' && e.args.length === 1) {
|
||||
return false;
|
||||
} else if (e.name === 'feature-state') {
|
||||
return false;
|
||||
} else if (e.name === 'has' && e.args.length === 1) {
|
||||
return false;
|
||||
} else if (
|
||||
e.name === 'properties' ||
|
||||
e.name === 'geometry-type' ||
|
||||
e.name === 'id'
|
||||
) {
|
||||
return false;
|
||||
} else if (/^filter-/.test(e.name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (e instanceof Within) {
|
||||
return false;
|
||||
}
|
||||
if (e instanceof Distance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let result = true;
|
||||
e.eachChild(arg => {
|
||||
if (result && !isFeatureConstant(arg)) { result = false; }
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function isStateConstant(e: Expression) {
|
||||
if (e instanceof CompoundExpression) {
|
||||
if (e.name === 'feature-state') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let result = true;
|
||||
e.eachChild(arg => {
|
||||
if (result && !isStateConstant(arg)) { result = false; }
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function isGlobalPropertyConstant(e: Expression, properties: Array<string>) {
|
||||
if (e instanceof CompoundExpression && properties.indexOf(e.name) >= 0) { return false; }
|
||||
let result = true;
|
||||
e.eachChild((arg) => {
|
||||
if (result && !isGlobalPropertyConstant(arg, properties)) { result = false; }
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export {isFeatureConstant, isGlobalPropertyConstant, isStateConstant, isExpressionConstant};
|
||||
Reference in New Issue
Block a user