From c027323ad9b17ce2bfee9dbb371362fc4d5d2c12 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 | 18 +++++++--- src/style-spec/function/evaluation_context.js | 1 + src/style-spec/function/parse_expression.js | 35 +++++++++++++++++++ .../evaluation-error/test.json | 28 +++++++++++++++ .../constant-folding/to-color/test.json | 16 +++++++++ 5 files changed, 94 insertions(+), 4 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..277da4edc61 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,19 @@ 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) { + return ctx.addVariable(value); + } 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..67f4253b07d 100644 --- a/src/style-spec/function/evaluation_context.js +++ b/src/style-spec/function/evaluation_context.js @@ -62,6 +62,7 @@ function ensure(condition: any, message: string) { module.exports = () => ({ types: types, + Color: Color, // used for compiling color literals ensure: ensure, error: (msg: string) => ensure(false, msg), diff --git a/src/style-spec/function/parse_expression.js b/src/style-spec/function/parse_expression.js index bac93ef2fd0..7022c9793c6 100644 --- a/src/style-spec/function/parse_expression.js +++ b/src/style-spec/function/parse_expression.js @@ -1,5 +1,7 @@ // @flow +const Literal = require('./definitions/literal'); +const {CompilationContext} = require('./expression'); import type {ParsingContext, Expression} from './expression'; /** @@ -55,6 +57,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 = require('./evaluation_context')(); + 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 +86,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] + ] + } +}