From 0567a8f38ced5c66ecc04c6c1efb4183edc5f476 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Fri, 15 Sep 2017 11:08:34 -0400 Subject: [PATCH] Fold compound expressions with literal arguments into literals --- .../function/definitions/literal.js | 19 +++++++--- src/style-spec/function/evaluation_context.js | 5 +-- src/style-spec/function/parse_expression.js | 36 +++++++++++++++++++ .../evaluation-error/test.json | 28 +++++++++++++++ .../constant-folding/to-color/test.json | 16 +++++++++ 5 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 test/integration/expression-tests/constant-folding/evaluation-error/test.json create mode 100644 test/integration/expression-tests/constant-folding/to-color/test.json diff --git a/src/style-spec/function/definitions/literal.js b/src/style-spec/function/definitions/literal.js index 41ca9cbd655..8da01ac617f 100644 --- a/src/style-spec/function/definitions/literal.js +++ b/src/style-spec/function/definitions/literal.js @@ -4,7 +4,7 @@ const { Color, isValue, typeOf } = require('../values'); import type { Type } from '../types'; import type { Value } from '../values'; -import type { Expression, ParsingContext } from '../expression'; +import type { Expression, ParsingContext, CompilationContext } from '../expression'; const u2028 = /\u2028/g; const u2029 = /\u2029/g; @@ -45,9 +45,20 @@ class Literal implements Expression { return new Literal(context.key, type, value); } - compile() { - const value = Literal.compile(this.value); - return typeof this.value === 'object' ? `(${value})` : value; + compile(ctx: CompilationContext) { + let value; + if (this.type.kind === 'Color') { + value = `(new $this.Color(${(this.value: any).join(', ')}))`; + } else { + value = Literal.compile(this.value); + } + + if (typeof this.value === 'object' && this.value !== null) { + const v = ctx.addVariable(value); + return v; + } else { + return value; + } } static compile(value: Value) { diff --git a/src/style-spec/function/evaluation_context.js b/src/style-spec/function/evaluation_context.js index d0b77731029..845cc85125b 100644 --- a/src/style-spec/function/evaluation_context.js +++ b/src/style-spec/function/evaluation_context.js @@ -13,7 +13,6 @@ const { toString, checkSubtype} = require('./types'); const {Color, typeOf} = require('./values'); -const Curve = require('./definitions/curve'); import type { ArrayType } from './types'; import type { Value } from './values'; @@ -59,8 +58,9 @@ function ensure(condition: any, message: string) { } -module.exports = () => ({ +const createContext = (Curve) => ({ types: types, + Color: Color, ensure: ensure, error: (msg: string) => ensure(false, msg), @@ -280,3 +280,4 @@ function findStopLessThanOrEqualTo(stops, input) { return Math.max(currentIndex - 1, 0); } +module.exports = () => createContext(require('./definitions/curve')); diff --git a/src/style-spec/function/parse_expression.js b/src/style-spec/function/parse_expression.js index bac93ef2fd0..94373639687 100644 --- a/src/style-spec/function/parse_expression.js +++ b/src/style-spec/function/parse_expression.js @@ -1,5 +1,8 @@ // @flow +const Literal = require('./definitions/literal'); +const {CompilationContext} = require('./expression'); +const evaluationContext = require('./evaluation_context'); import type {ParsingContext, Expression} from './expression'; /** @@ -55,6 +58,22 @@ function parseExpression(expr: mixed, context: ParsingContext): ?Expression { } } + // If an expression's arguments are all literals, we can evaluate + // it immediately and replace it with a literal value in the + // parsed/compiled result. + if (isConstant(parsed)) { + const cc = new CompilationContext(); + const ec = evaluationContext(); + const compiled = cc.compileToFunction(parsed, ec); + try { + const value = compiled({}, {}); + parsed = new Literal(parsed.key, parsed.type, value); + } catch (e) { + context.error(e.message); + return null; + } + } + return parsed; } @@ -68,4 +87,21 @@ function parseExpression(expr: mixed, context: ParsingContext): ?Expression { } } +const nonConstantExpressions = [ 'error', 'get', 'has', 'properties', 'id', 'geometry-type', 'zoom' ]; +function isConstant(expression: Expression) { + const {CompoundExpression} = require('./compound_expression'); + const Var = require('./definitions/var'); + if (expression instanceof CompoundExpression && nonConstantExpressions.indexOf(expression.name) >= 0) { + return false; + } else if (expression instanceof Var) { + return false; + } + + let constant = true; + expression.eachChild(arg => { + constant = constant && (arg instanceof Literal); + }); + return constant; +} + module.exports = parseExpression; diff --git a/test/integration/expression-tests/constant-folding/evaluation-error/test.json b/test/integration/expression-tests/constant-folding/evaluation-error/test.json new file mode 100644 index 00000000000..287d52c8395 --- /dev/null +++ b/test/integration/expression-tests/constant-folding/evaluation-error/test.json @@ -0,0 +1,28 @@ +{ + "expectExpressionType": {"kind": "Color"}, + "expression": [ + "curve", + ["step"], + ["get", "x"], + "black", + 0, + "invalid", + 10, + "blue" + ], + "inputs": [ + [{}, {"properties": {"x": -1}}], + [{}, {"properties": {"x": 0}}], + [{}, {"properties": {"x": 5}}], + [{}, {"properties": {"x": 10}}], + [{}, {"properties": {"x": 11}}] + ], + "expected": { + "compiled": { + "result": "error", + "errors": [ + {"key": "[5]", "error": "Could not parse color from value 'invalid'"} + ] + } + } +} diff --git a/test/integration/expression-tests/constant-folding/to-color/test.json b/test/integration/expression-tests/constant-folding/to-color/test.json new file mode 100644 index 00000000000..8858b58afb5 --- /dev/null +++ b/test/integration/expression-tests/constant-folding/to-color/test.json @@ -0,0 +1,16 @@ +{ + "expectExpressionType": null, + "expression": ["to-color", "red"], + "inputs": [[{}, {}]], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": true, + "isZoomConstant": true, + "type": "Color" + }, + "outputs": [ + [1, 0, 0, 1] + ] + } +}