Call expressions

This commit is contained in:
Tim Schaub
2013-06-10 16:17:21 -06:00
parent 2f7e74ab35
commit fbb028e15e
4 changed files with 138 additions and 15 deletions

View File

@@ -1,3 +1,4 @@
goog.provide('ol.expression.Call');
goog.provide('ol.expression.Comparison');
goog.provide('ol.expression.ComparisonOp');
goog.provide('ol.expression.Expression');
@@ -29,11 +30,62 @@ ol.expression.Expression = function() {};
*
* @param {Object} scope Evaluation scope. All properties of this object
* will be available as variables when evaluating the expression.
* @param {Object=} opt_fns Optional scope for looking up functions. If not
* provided, functions will be looked in the evaluation scope.
* @param {Object=} opt_this Object to use as this when evaluating call
* expressions. If not provided, `this` will resolve to a new object.
* @return {*} Result of the expression.
*/
ol.expression.Expression.prototype.evaluate = goog.abstractMethod;
/**
* A call expression (e.g. `foo(bar)`).
*
* @constructor
* @extends {ol.expression.Expression}
* @param {expr} expr An expression that resolves to a function.
* @param {Array.<ol.expression.Expression>} args Arguments.
*/
ol.expression.Call = function(expr, args) {
/**
* @type {expr}
* @private
*/
this.expr_ = expr;
/**
* @type {Array.<ol.expression.Expression>}
* @private
*/
this.args_ = args;
};
goog.inherits(ol.expression.Call, ol.expression.Expression);
/**
* @inheritDoc
*/
ol.expression.Call.prototype.evaluate = function(scope, opt_fns, opt_this) {
var fnScope = goog.isDefAndNotNull(opt_fns) ? opt_fns : scope;
var fn = this.expr_.evaluate(fnScope);
if (!fn || !goog.isFunction(fn)) {
throw new Error('No function in provided scope: ' + this.name_);
}
var thisArg = goog.isDef(opt_this) ? opt_this : {};
var len = this.args_.length;
var values = new Array(len);
for (var i = 0; i < len; ++i) {
values[i] = this.args_[i].evaluate(scope, opt_fns, opt_this);
}
return fn.apply(thisArg, values);
};
/**
* @enum {string}
*/
@@ -86,10 +138,11 @@ goog.inherits(ol.expression.Comparison, ol.expression.Expression);
/**
* @inheritDoc
*/
ol.expression.Comparison.prototype.evaluate = function(scope) {
ol.expression.Comparison.prototype.evaluate = function(scope, opt_this,
opt_fns) {
var result;
var rightVal = this.right_.evaluate(scope);
var leftVal = this.left_.evaluate(scope);
var rightVal = this.right_.evaluate(scope, opt_fns, opt_this);
var leftVal = this.left_.evaluate(scope, opt_fns, opt_this);
switch (this.operator_) {
case ol.expression.ComparisonOp.EQ:
@@ -174,7 +227,7 @@ goog.inherits(ol.expression.Literal, ol.expression.Expression);
/**
* @inheritDoc
*/
ol.expression.Literal.prototype.evaluate = function(scope) {
ol.expression.Literal.prototype.evaluate = function() {
return this.value_;
};
@@ -225,10 +278,10 @@ goog.inherits(ol.expression.Logical, ol.expression.Expression);
/**
* @inheritDoc
*/
ol.expression.Logical.prototype.evaluate = function(scope) {
ol.expression.Logical.prototype.evaluate = function(scope, opt_fns, opt_this) {
var result;
var rightVal = this.right_.evaluate(scope);
var leftVal = this.left_.evaluate(scope);
var rightVal = this.right_.evaluate(scope, opt_fns, opt_this);
var leftVal = this.left_.evaluate(scope, opt_fns, opt_this);
if (this.operator_ === ol.expression.LogicalOp.AND) {
result = leftVal && rightVal;
@@ -290,10 +343,10 @@ goog.inherits(ol.expression.Math, ol.expression.Expression);
/**
* @inheritDoc
*/
ol.expression.Math.prototype.evaluate = function(scope) {
ol.expression.Math.prototype.evaluate = function(scope, opt_fns, opt_this) {
var result;
var rightVal = this.right_.evaluate(scope);
var leftVal = this.left_.evaluate(scope);
var rightVal = this.right_.evaluate(scope, opt_fns, opt_this);
var leftVal = this.left_.evaluate(scope, opt_fns, opt_this);
/**
* 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
@@ -346,7 +399,6 @@ goog.inherits(ol.expression.Not, ol.expression.Expression);
/**
* @inheritDoc
*/
ol.expression.Not.prototype.evaluate = function(scope) {
return !this.expr_.evaluate(scope);
ol.expression.Not.prototype.evaluate = function(scope, opt_fns, opt_this) {
return !this.expr_.evaluate(scope, opt_fns, opt_this);
};

View File

@@ -761,7 +761,6 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) {
quote === ol.expression.Char.DOUBLE_QUOTE,
'Strings must start with a quote: ' + String.fromCharCode(quote));
var start = this.index_;
this.increment_(1);
var str = '';

View File

@@ -17,6 +17,7 @@ goog.provide('ol.expression.Parser');
goog.require('goog.asserts');
goog.require('ol.expression.Call');
goog.require('ol.expression.Comparison');
goog.require('ol.expression.ComparisonOp');
goog.require('ol.expression.Expression');
@@ -148,10 +149,11 @@ ol.expression.Parser.prototype.createBinaryExpression_ = function(operator,
*
* @param {ol.expression.Identifier} expr Identifier expression for function.
* @param {Array.<ol.expression.Expression>} args Arguments array.
* @return {ol.expression.Call} Call expression.
* @private
*/
ol.expression.Parser.prototype.createCallExpression_ = function(expr, args) {
throw new Error('Not implemented');
return new ol.expressions.Call(expr, args);
};

View File

@@ -1,6 +1,75 @@
goog.provide('ol.test.expression.Expression');
describe('ol.expression.Call', function() {
describe('constructor', function() {
it('creates a new expression', function() {
var expr = new ol.expression.Call(
new ol.expression.Identifier('sqrt'),
[new ol.expression.Literal(42)]);
expect(expr).to.be.a(ol.expression.Expression);
expect(expr).to.be.a(ol.expression.Call);
});
});
describe('#evaluate()', function() {
var fns = {
sqrt: function(value) {
return Math.sqrt(value);
},
strConcat: function() {
return Array.prototype.join.call(arguments, '');
},
discouraged: function() {
return this.message;
}
};
it('calls method on scope with literal args', function() {
var expr = new ol.expression.Call(
new ol.expression.Identifier('sqrt'),
[new ol.expression.Literal(42)]);
expect(expr.evaluate(fns)).to.be(Math.sqrt(42));
});
it('accepts a separate scope for functions', function() {
var expr = new ol.expression.Call(
new ol.expression.Identifier('sqrt'),
[new ol.expression.Identifier('foo')]);
expect(expr.evaluate({foo: 42}, fns)).to.be(Math.sqrt(42));
});
it('accepts multiple expression arguments', function() {
var expr = new ol.expression.Call(
new ol.expression.Identifier('strConcat'),
[
new ol.expression.Identifier('foo'),
new ol.expression.Literal(' comes after '),
new ol.expression.Math(
ol.expression.MathOp.SUBTRACT,
new ol.expression.Identifier('foo'),
new ol.expression.Literal(1))
]);
expect(expr.evaluate({foo: 42}, fns)).to.be('42 comes after 41');
});
it('accepts optional this arg', function() {
var expr = new ol.expression.Call(
new ol.expression.Identifier('discouraged'), []);
var thisArg = {
message: 'avoid this'
};
expect(expr.evaluate(fns, null, thisArg)).to.be('avoid this');
});
});
});
describe('ol.expression.Comparison', function() {
describe('constructor', function() {
@@ -367,6 +436,7 @@ describe('ol.expression.Not', function() {
});
goog.require('ol.expression.Call');
goog.require('ol.expression.Comparison');
goog.require('ol.expression.ComparisonOp');
goog.require('ol.expression.Expression');