diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index 5015345afc..12bb880a35 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -3,6 +3,8 @@ goog.provide('ol.expression.ComparisonOp'); goog.provide('ol.expression.Expression'); goog.provide('ol.expression.Identifier'); goog.provide('ol.expression.Literal'); +goog.provide('ol.expression.Math'); +goog.provide('ol.expression.MathOp'); goog.provide('ol.expression.Not'); @@ -175,6 +177,88 @@ ol.expression.Literal.prototype.evaluate = function(scope) { }; +/** + * @enum {string} + */ +ol.expression.MathOp = { + ADD: '+', + SUBTRACT: '-', + MULTIPLY: '*', + DIVIDE: '/', + MOD: '%' +}; + + + +/** + * A math expression (e.g. `foo + 42`, `bar % 10`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {ol.expression.MathOp} operator Math operator. + * @param {ol.expression.Expression} left Left expression. + * @param {ol.expression.Expression} right Right expression. + */ +ol.expression.Math = function(operator, left, right) { + + /** + * @type {ol.expression.MathOp} + * @private + */ + this.operator_ = operator; + + /** + * @type {ol.expression.Expression} + * @private + */ + this.left_ = left; + + /** + * @type {ol.expression.Expression} + * @private + */ + this.right_ = right; + +}; +goog.inherits(ol.expression.Math, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.Math.prototype.evaluate = function(scope) { + var result; + var rightVal = this.right_.evaluate(scope); + var leftVal = this.left_.evaluate(scope); + /** + * TODO: throw if rightVal, leftVal not numbers - this would require the use + * of a concat function for strings but it would let us serialize these as + * math functions where available elsewhere + */ + + switch (this.operator_) { + case ol.expression.MathOp.ADD: + result = leftVal + rightVal; + break; + case ol.expression.MathOp.SUBTRACT: + result = Number(leftVal) - Number(rightVal); + break; + case ol.expression.MathOp.MULTIPLY: + result = Number(leftVal) * Number(rightVal); + break; + case ol.expression.MathOp.DIVIDE: + result = Number(leftVal) / Number(rightVal); + break; + case ol.expression.MathOp.MOD: + result = Number(leftVal) % Number(rightVal); + break; + default: + throw new Error('Unsupported math operator: ' + this.operator_); + } + return result; +}; + + /** * A logical not expression (e.g. `!foo`). diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index f9c579cc13..f9297b72a4 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -184,6 +184,104 @@ describe('ol.expression.Literal', function() { }); +describe('ol.expression.Math', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.ADD, + new ol.expression.Literal(40), + new ol.expression.Literal(2)); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Math); + }); + }); + + describe('#evaluate()', function() { + it('does + with numeric literal', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.ADD, + new ol.expression.Literal(40), + new ol.expression.Literal(2)); + + expect(expr.evaluate({})).to.be(42); + }); + + it('does + with string literal (note: subject to change)', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.ADD, + new ol.expression.Literal('foo'), + new ol.expression.Literal('bar')); + + expect(expr.evaluate({})).to.be('foobar'); + }); + + it('does + with identifiers', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.ADD, + new ol.expression.Identifier('foo'), + new ol.expression.Identifier('bar')); + + expect(expr.evaluate({foo: 40, bar: 2})).to.be(42); + }); + + it('does - with identifiers', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.SUBTRACT, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(2)); + + expect(expr.evaluate({foo: 40})).to.be(38); + }); + + it('casts to number with - (note: this may throw later)', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.SUBTRACT, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(2)); + + expect(expr.evaluate({foo: '40'})).to.be(38); + }); + + it('does * with identifiers', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.MULTIPLY, + new ol.expression.Literal(2), + new ol.expression.Identifier('foo')); + + expect(expr.evaluate({foo: 21})).to.be(42); + }); + + it('casts to number with * (note: this may throw later)', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.MULTIPLY, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(2)); + + expect(expr.evaluate({foo: '21'})).to.be(42); + }); + + it('does % with identifiers', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.MOD, + new ol.expression.Literal(97), + new ol.expression.Identifier('foo')); + + expect(expr.evaluate({foo: 55})).to.be(42); + }); + + it('casts to number with % (note: this may throw later)', function() { + var expr = new ol.expression.Math( + ol.expression.MathOp.MOD, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(100)); + + expect(expr.evaluate({foo: '150'})).to.be(50); + }); + }); + +}); + describe('ol.expression.Not', function() { describe('constructor', function() { @@ -233,4 +331,6 @@ goog.require('ol.expression.ComparisonOp'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Literal'); +goog.require('ol.expression.Math'); +goog.require('ol.expression.MathOp'); goog.require('ol.expression.Not');