From 88fd0fda64a2bd6c6a1ac892db231ccb944fb4c2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Jun 2013 16:52:03 -0600 Subject: [PATCH 01/84] Initial bits for parser --- src/ol/expression/expression.js | 112 ++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/ol/expression/expression.js diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js new file mode 100644 index 0000000000..a83e086934 --- /dev/null +++ b/src/ol/expression/expression.js @@ -0,0 +1,112 @@ +/** + * Support a very limited subset of ECMAScript 5.1 + * http://www.ecma-international.org/ecma-262/5.1/ + * + * Inspired by Esprima (https://github.com/ariya/esprima) + * BSD Licensed + */ + + +/** + * @enum {string} + */ +ol.expression.Syntax = { + BinaryExpression: 'BinaryExpression', + CallExpression: 'CallExpression', + Identifier: 'Identifier', + Literal: 'Literal', + LogicalExpression: 'LogicalExpression', + MemberExpression: 'MemberExpression', + Property: 'Property', // dot notation only + UnaryExpression: 'UnaryExpression' // only with logical not +}; + + +/** + * @enum {string} + */ +ol.expression.Token = { + BooleanLiteral: 'Boolean', + EOF: '', + Identifier: 'Identifier', + Keyword: 'Keyword', + NullLiteral: 'Null', + NumericLiteral: 'Numeric', + Punctuator: 'Punctuator', + StringLiteral: 'String' +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.2 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is whitespace. + */ +ol.expression.isWhitespace = function(ch) { + return (ch === 32) || // + (ch === 9) || // + (ch === 0xB) || // + (ch === 0xC) || // + (ch === 0xA0) || // + (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' + + '\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF' + .indexOf(String.fromCharCode(ch)) > 0); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a line terminator. + */ +ol.expression.isLineTerminator = function(ch) { + return (ch === 10) || // + (ch === 13) || // + (ch === 0x2028) || // + (ch === 0x2029); // +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 + * Doesn't deal with non-ascii identifiers. + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a valid identifier start. + */ +ol.expression.isIdentifierStart = function(ch) { + return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) + (ch >= 65 && ch <= 90) || // A..Z + (ch >= 97 && ch <= 122); // a..z +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 + * Doesn't deal with non-ascii identifiers. + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a valid identifier part. + */ +ol.expression.isIdentifierPart = function(ch) { + return ol.expression.isIdentifierStart(ch) || + (ch >= 48 && ch <= 57); // 0..9 +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.2 + * @param {string} id A string identifier. + * @return {boolean} The identifier is a future reserved word. + */ +ol.expression.isFutureReservedWord = function(id) { + switch (id) { + case 'class': + case 'enum': + case 'export': + case 'extends': + case 'import': + case 'super': + return true; + default: + return false; + } +}; From 84a9fb40ef2a20c2946e7dec98727eb1783f2c5d Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Jun 2013 23:47:47 -0600 Subject: [PATCH 02/84] More char code tests --- src/ol/expression/expression.js | 195 +++++++++++++++++++++----------- 1 file changed, 129 insertions(+), 66 deletions(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index a83e086934..1d8a7e4118 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -1,5 +1,8 @@ +goog.provide('ol.expression'); + + /** - * Support a very limited subset of ECMAScript 5.1 + * Support an extremely limited subset of ECMAScript 5.1 * http://www.ecma-international.org/ecma-262/5.1/ * * Inspired by Esprima (https://github.com/ariya/esprima) @@ -7,88 +10,70 @@ */ +/** + * @enum {number} + */ +ol.expression.Char = { + CARRIAGE_RETURN: 13, + DIGIT_0: 48, + DIGIT_7: 55, + DIGIT_9: 57, + DOLLAR: 36, + FORM_FEED: 0xC, + LINE_FEED: 10, + LINE_SEPARATOR: 0x2028, + LOWER_A: 97, + LOWER_F: 102, + LOWER_Z: 122, + NONBREAKING_SPACE: 0xA0, + PARAGRAPH_SEPARATOR: 0x2029, + SPACE: 32, + TAB: 9, + UNDERSCORE: 95, + UPPER_A: 65, + UPPER_F: 70, + UPPER_Z: 90, + VERTICAL_TAB: 0xB +}; + + /** * @enum {string} */ ol.expression.Syntax = { - BinaryExpression: 'BinaryExpression', - CallExpression: 'CallExpression', - Identifier: 'Identifier', - Literal: 'Literal', - LogicalExpression: 'LogicalExpression', - MemberExpression: 'MemberExpression', - Property: 'Property', // dot notation only - UnaryExpression: 'UnaryExpression' // only with logical not + BINARY_EXPRESSION: 'BinaryExpression', + CALL_EXPRESSION: 'CallExpression', + IDENTIFIER: 'Identifier', + LITERAL: 'Literal', + LOGICAL_EXPRESSION: 'LogicalExpression', + MEMBER_EXPRESSION: 'MemberExpression', + PROPERTY: 'Property', // dot notation only + UNARY_EXPRESSION: 'UnaryExpression' // only with logical not }; /** * @enum {string} */ -ol.expression.Token = { - BooleanLiteral: 'Boolean', +ol.expression.TokenType = { + BOOLEAN_LITERAL: 'Boolean', EOF: '', - Identifier: 'Identifier', - Keyword: 'Keyword', - NullLiteral: 'Null', - NumericLiteral: 'Numeric', - Punctuator: 'Punctuator', - StringLiteral: 'String' + IDENTIFIER: 'Identifier', + KEYWORD: 'Keyword', + NULL_LITERAL: 'Null', + NUMERIC_LITERAL: 'Numeric', + PUNCTUATOR: 'Punctuator', + STRING_LITERAL: 'String' }; /** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.2 + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 * @param {number} ch The unicode of a character. - * @return {boolean} The character is whitespace. + * @return {boolean} The character is a decimal digit. */ -ol.expression.isWhitespace = function(ch) { - return (ch === 32) || // - (ch === 9) || // - (ch === 0xB) || // - (ch === 0xC) || // - (ch === 0xA0) || // - (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' + - '\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF' - .indexOf(String.fromCharCode(ch)) > 0); -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 - * @param {number} ch The unicode of a character. - * @return {boolean} The character is a line terminator. - */ -ol.expression.isLineTerminator = function(ch) { - return (ch === 10) || // - (ch === 13) || // - (ch === 0x2028) || // - (ch === 0x2029); // -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 - * Doesn't deal with non-ascii identifiers. - * @param {number} ch The unicode of a character. - * @return {boolean} The character is a valid identifier start. - */ -ol.expression.isIdentifierStart = function(ch) { - return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) - (ch >= 65 && ch <= 90) || // A..Z - (ch >= 97 && ch <= 122); // a..z -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 - * Doesn't deal with non-ascii identifiers. - * @param {number} ch The unicode of a character. - * @return {boolean} The character is a valid identifier part. - */ -ol.expression.isIdentifierPart = function(ch) { - return ol.expression.isIdentifierStart(ch) || - (ch >= 48 && ch <= 57); // 0..9 +ol.expression.isDecimalDigit = function(ch) { + return (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_9); }; @@ -110,3 +95,81 @@ ol.expression.isFutureReservedWord = function(id) { return false; } }; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a hex digit. + */ +ol.expression.isHexDigit = function(ch) { + return ol.expression.isDecimalDigit(ch) || + (ch >= ol.expression.Char.LOWER_A && ch <= ol.expression.Char.LOWER_F) || + (ch >= ol.expression.Char.UPPER_A && ch <= ol.expression.Char.UPPER_F); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 + * Doesn't deal with non-ascii identifiers. + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a valid identifier part. + */ +ol.expression.isIdentifierPart = function(ch) { + return ol.expression.isIdentifierStart(ch) || + (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_9); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 + * Doesn't yet deal with non-ascii identifiers. + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a valid identifier start. + */ +ol.expression.isIdentifierStart = function(ch) { + return (ch === ol.expression.Char.DOLLAR) || + (ch === ol.expression.Char.UNDERSCORE) || + (ch >= ol.expression.Char.UPPER_A && ch <= ol.expression.Char.UPPER_Z) || + (ch >= ol.expression.Char.LOWER_A && ch <= ol.expression.Char.LOWER_Z); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a line terminator. + */ +ol.expression.isLineTerminator = function(ch) { + return (ch === ol.expression.Char.LINE_FEED) || + (ch === ol.expression.Char.CARRIAGE_RETURN) || + (ch === ol.expression.Char.LINE_SEPARATOR) || + (ch === ol.expression.Char.PARAGRAPH_SEPARATOR); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is an octal digit. + */ +ol.expression.isOctalDigit = function(ch) { + return (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_7); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.2 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is whitespace. + */ +ol.expression.isWhitespace = function(ch) { + return (ch === ol.expression.Char.SPACE) || + (ch === ol.expression.Char.TAB) || + (ch === ol.expression.Char.VERTICAL_TAB) || + (ch === ol.expression.Char.FORM_FEED) || + (ch === ol.expression.Char.NONBREAKING_SPACE) || + (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' + + '\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF' + .indexOf(String.fromCharCode(ch)) > 0); +}; From a5343161bfecbb2dd50b1b446287fca4f33cde4d Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 5 Jun 2013 14:57:20 -0600 Subject: [PATCH 03/84] Separate lexer --- src/ol/expression/expression.js | 150 --------- src/ol/expression/lexer.js | 426 ++++++++++++++++++++++++++ test/spec/ol/expression/lexer.test.js | 14 + 3 files changed, 440 insertions(+), 150 deletions(-) create mode 100644 src/ol/expression/lexer.js create mode 100644 test/spec/ol/expression/lexer.test.js diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index 1d8a7e4118..b7606fbe47 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -10,33 +10,6 @@ goog.provide('ol.expression'); */ -/** - * @enum {number} - */ -ol.expression.Char = { - CARRIAGE_RETURN: 13, - DIGIT_0: 48, - DIGIT_7: 55, - DIGIT_9: 57, - DOLLAR: 36, - FORM_FEED: 0xC, - LINE_FEED: 10, - LINE_SEPARATOR: 0x2028, - LOWER_A: 97, - LOWER_F: 102, - LOWER_Z: 122, - NONBREAKING_SPACE: 0xA0, - PARAGRAPH_SEPARATOR: 0x2029, - SPACE: 32, - TAB: 9, - UNDERSCORE: 95, - UPPER_A: 65, - UPPER_F: 70, - UPPER_Z: 90, - VERTICAL_TAB: 0xB -}; - - /** * @enum {string} */ @@ -50,126 +23,3 @@ ol.expression.Syntax = { PROPERTY: 'Property', // dot notation only UNARY_EXPRESSION: 'UnaryExpression' // only with logical not }; - - -/** - * @enum {string} - */ -ol.expression.TokenType = { - BOOLEAN_LITERAL: 'Boolean', - EOF: '', - IDENTIFIER: 'Identifier', - KEYWORD: 'Keyword', - NULL_LITERAL: 'Null', - NUMERIC_LITERAL: 'Numeric', - PUNCTUATOR: 'Punctuator', - STRING_LITERAL: 'String' -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 - * @param {number} ch The unicode of a character. - * @return {boolean} The character is a decimal digit. - */ -ol.expression.isDecimalDigit = function(ch) { - return (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_9); -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.2 - * @param {string} id A string identifier. - * @return {boolean} The identifier is a future reserved word. - */ -ol.expression.isFutureReservedWord = function(id) { - switch (id) { - case 'class': - case 'enum': - case 'export': - case 'extends': - case 'import': - case 'super': - return true; - default: - return false; - } -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 - * @param {number} ch The unicode of a character. - * @return {boolean} The character is a hex digit. - */ -ol.expression.isHexDigit = function(ch) { - return ol.expression.isDecimalDigit(ch) || - (ch >= ol.expression.Char.LOWER_A && ch <= ol.expression.Char.LOWER_F) || - (ch >= ol.expression.Char.UPPER_A && ch <= ol.expression.Char.UPPER_F); -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 - * Doesn't deal with non-ascii identifiers. - * @param {number} ch The unicode of a character. - * @return {boolean} The character is a valid identifier part. - */ -ol.expression.isIdentifierPart = function(ch) { - return ol.expression.isIdentifierStart(ch) || - (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_9); -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 - * Doesn't yet deal with non-ascii identifiers. - * @param {number} ch The unicode of a character. - * @return {boolean} The character is a valid identifier start. - */ -ol.expression.isIdentifierStart = function(ch) { - return (ch === ol.expression.Char.DOLLAR) || - (ch === ol.expression.Char.UNDERSCORE) || - (ch >= ol.expression.Char.UPPER_A && ch <= ol.expression.Char.UPPER_Z) || - (ch >= ol.expression.Char.LOWER_A && ch <= ol.expression.Char.LOWER_Z); -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 - * @param {number} ch The unicode of a character. - * @return {boolean} The character is a line terminator. - */ -ol.expression.isLineTerminator = function(ch) { - return (ch === ol.expression.Char.LINE_FEED) || - (ch === ol.expression.Char.CARRIAGE_RETURN) || - (ch === ol.expression.Char.LINE_SEPARATOR) || - (ch === ol.expression.Char.PARAGRAPH_SEPARATOR); -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 - * @param {number} ch The unicode of a character. - * @return {boolean} The character is an octal digit. - */ -ol.expression.isOctalDigit = function(ch) { - return (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_7); -}; - - -/** - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.2 - * @param {number} ch The unicode of a character. - * @return {boolean} The character is whitespace. - */ -ol.expression.isWhitespace = function(ch) { - return (ch === ol.expression.Char.SPACE) || - (ch === ol.expression.Char.TAB) || - (ch === ol.expression.Char.VERTICAL_TAB) || - (ch === ol.expression.Char.FORM_FEED) || - (ch === ol.expression.Char.NONBREAKING_SPACE) || - (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' + - '\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF' - .indexOf(String.fromCharCode(ch)) > 0); -}; diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js new file mode 100644 index 0000000000..564d580268 --- /dev/null +++ b/src/ol/expression/lexer.js @@ -0,0 +1,426 @@ +goog.provide('ol.expression.Lexer'); + + +/** + * @enum {number} + */ +ol.expression.Char = { + AMPERSAND: 38, + BANG: 33, // ! + CARRIAGE_RETURN: 13, + COMMA: 44, + DIGIT_0: 48, + DIGIT_7: 55, + DIGIT_9: 57, + DOLLAR: 36, + DOUBLE_QUOTE: 34, + DOT: 46, + EQUAL: 61, + FORM_FEED: 0xC, + GREATER: 62, + LEFT_PAREN: 40, + LESS: 60, + LINE_FEED: 10, + LINE_SEPARATOR: 0x2028, + LOWER_A: 97, + LOWER_F: 102, + LOWER_Z: 122, + MINUS: 45, + NONBREAKING_SPACE: 0xA0, + PARAGRAPH_SEPARATOR: 0x2029, + PERCENT: 37, + PIPE: 124, + PLUS: 43, + RIGHT_PAREN: 41, + SINGLE_QUOTE: 39, + SPACE: 32, + STAR: 42, + TAB: 9, + TILDE: 126, + UNDERSCORE: 95, + UPPER_A: 65, + UPPER_F: 70, + UPPER_Z: 90, + VERTICAL_TAB: 0xB +}; + + +/** + * @enum {string} + */ +ol.expression.TokenType = { + BOOLEAN_LITERAL: 'Boolean', + EOF: '', + IDENTIFIER: 'Identifier', + KEYWORD: 'Keyword', + NULL_LITERAL: 'Null', + NUMERIC_LITERAL: 'Numeric', + PUNCTUATOR: 'Punctuator', + STRING_LITERAL: 'String' +}; + + +/** + * @typedef {{type: (ol.expression.TokenType), + * value: (string|number|boolean|null)}} + */ +ol.expression.Token; + + + +/** + * Lexer constructor. + * @constructor + * @param {string} source Source code. + */ +ol.expression.Lexer = function(source) { + + /** + * Source code. + * @type {string} + * @private + */ + this.source_ = source; + + /** + * Source length. + * @type {number} + * @private + */ + this.length_ = source.length; + + /** + * Current character index. + * @type {number} + * @private + */ + this.index_ = 0; + +}; + + +/** + * Scan next token. + * @return {ol.expression.Token} Next token. + * @private + */ +ol.expression.Lexer.prototype.advance_ = function() { + if (this.index_ >= this.length_) { + return { + type: ol.expression.TokenType.EOF, + value: null + }; + } + var ch = this.getCurrentCharCode_(); + + // check for common punctuation + if (ch === ol.expression.Char.LEFT_PAREN || + ch === ol.expression.Char.RIGHT_PAREN) { + return this.scanPunctuator_(); + } + + // check for string literal + if (ch === ol.expression.Char.SINGLE_QUOTE || + ch === ol.expression.Char.DOUBLE_QUOTE) { + return this.scanStringLiteral_(); + } + + // check for identifier + if (this.isIdentifierStart_(ch)) { + this.scanIdentifier_(); + } + + // check dot punctuation or decimal + if (ch === ol.expression.Char.DOT) { + if (this.isDecimalDigit_(this.getCharCode_(1))) { + return this.scanNumericLiteral_(); + } + return this.scanPunctuator_(); + } + + // check decimal number + if (this.isDecimalDigit_(ch)) { + return this.scanNumericLiteral_(); + } + + // all the rest is punctuation + return this.scanPunctuator_(); +}; + + +/** + * Increment the current character index. + * @param {number} delta Delta by which the index is advanced. + * @private + */ +ol.expression.Lexer.prototype.increment_ = function(delta) { + this.index_ += delta; +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a decimal digit. + * @private + */ +ol.expression.Lexer.prototype.isDecimalDigit_ = function(ch) { + return (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_9); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.2 + * @param {string} id A string identifier. + * @return {boolean} The identifier is a future reserved word. + * @private + */ +ol.expression.Lexer.prototype.isFutureReservedWord_ = function(id) { + return ( + id === 'class' || + id === 'enum' || + id === 'export' || + id === 'extends' || + id === 'import' || + id === 'super'); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a hex digit. + * @private + */ +ol.expression.Lexer.prototype.isHexDigit_ = function(ch) { + return this.isDecimalDigit_(ch) || + (ch >= ol.expression.Char.LOWER_A && ch <= ol.expression.Char.LOWER_F) || + (ch >= ol.expression.Char.UPPER_A && ch <= ol.expression.Char.UPPER_F); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 + * Doesn't deal with non-ascii identifiers. + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a valid identifier part. + * @private + */ +ol.expression.Lexer.prototype.isIdentifierPart_ = function(ch) { + return this.isIdentifierStart_(ch) || + (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_9); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 + * Doesn't yet deal with non-ascii identifiers. + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a valid identifier start. + * @private + */ +ol.expression.Lexer.prototype.isIdentifierStart_ = function(ch) { + return (ch === ol.expression.Char.DOLLAR) || + (ch === ol.expression.Char.UNDERSCORE) || + (ch >= ol.expression.Char.UPPER_A && ch <= ol.expression.Char.UPPER_Z) || + (ch >= ol.expression.Char.LOWER_A && ch <= ol.expression.Char.LOWER_Z); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is a line terminator. + * @private + */ +ol.expression.Lexer.prototype.isLineTerminator_ = function(ch) { + return (ch === ol.expression.Char.LINE_FEED) || + (ch === ol.expression.Char.CARRIAGE_RETURN) || + (ch === ol.expression.Char.LINE_SEPARATOR) || + (ch === ol.expression.Char.PARAGRAPH_SEPARATOR); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is an octal digit. + * @private + */ +ol.expression.Lexer.prototype.isOctalDigit_ = function(ch) { + return (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_7); +}; + + +/** + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.2 + * @param {number} ch The unicode of a character. + * @return {boolean} The character is whitespace. + * @private + */ +ol.expression.Lexer.prototype.isWhitespace_ = function(ch) { + return (ch === ol.expression.Char.SPACE) || + (ch === ol.expression.Char.TAB) || + (ch === ol.expression.Char.VERTICAL_TAB) || + (ch === ol.expression.Char.FORM_FEED) || + (ch === ol.expression.Char.NONBREAKING_SPACE) || + (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' + + '\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF' + .indexOf(String.fromCharCode(ch)) > 0); +}; + + +/** + * Get the unicode of the character at the given offset from the current index. + * @param {number} delta Offset from current index. + * @return {number} The character code. + * @private + */ +ol.expression.Lexer.prototype.getCharCode_ = function(delta) { + return this.source_.charCodeAt(this.index_ + delta); +}; + + +/** + * Get the unicode of the character at the current index. + * @return {number} The current character code. + * @private + */ +ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { + return this.getCharCode_(0); +}; + + +/** + * Scan punctuator token (a subset of allowed tokens in 7.7). + * @return {ol.expression.Token} Punctuator token. + * @private + */ +ol.expression.Lexer.prototype.scanPunctuator_ = function() { + var ch = this.getCurrentCharCode_(); + + // single char punctuation + if (ch === ol.expression.Char.DOT || + ch === ol.expression.Char.LEFT_PAREN || + ch === ol.expression.Char.RIGHT_PAREN || + ch === ol.expression.Char.COMMA || + ch === ol.expression.Char.GREATER || + ch === ol.expression.Char.LESS || + ch === ol.expression.Char.PLUS || + ch === ol.expression.Char.MINUS || + ch === ol.expression.Char.STAR || + ch === ol.expression.Char.PERCENT || + ch === ol.expression.Char.PIPE || + ch === ol.expression.Char.AMPERSAND || + ch === ol.expression.Char.TILDE) { + + this.increment_(1); + return { + type: ol.expression.TokenType.PUNCTUATOR, + value: String.fromCharCode(ch) + }; + } + + // check for 2-character punctuation + var ch1 = this.getCharCode_(1); + + // assignment or comparison (and we don't allow assignment) + if (ch1 === ol.expression.Char.EQUAL) { + if (ch === ol.expression.Char.BANG || ch === ol.expression.Char.EQUAL) { + // we're looking at !=, ==, !==, or === + this.increment_(2); + + // check for triple + if (this.getCharCode_(1) === ol.expression.Char.EQUAL) { + this.increment_(1); + return { + type: ol.expression.TokenType.PUNCTUATOR, + value: String.fromCharCode(ch) + '==' + }; + } else { + // != or == + return { + type: ol.expression.TokenType.PUNCTUATOR, + value: String.fromCharCode(ch) + '=' + }; + } + } + + if (ch === ol.expression.Char.GREATER || ch === ol.expression.Char.LESS) { + return { + type: ol.expression.TokenType.PUNCTUATOR, + value: String.fromCharCode(ch) + '=' + }; + } + } + + // remaining 2-charcter punctuators are || and && + if (ch === ch1 && + (ch === ol.expression.Char.PIPE || ch === ol.expression.Char.AMPERSAND)) { + + this.increment_(2); + var str = String.fromCharCode(ch); + return { + type: ol.expression.TokenType.PUNCTUATOR, + value: str + str + }; + } + + // we don't allow 4-character punctuator (>>>=) + // and the allowed 3-character punctuators (!==, ===) are already consumed + + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + String.fromCharCode(ch)); +}; + + +/** + * Scan identifier token. + * @return {ol.expression.Token} Identifier token. + * @private + */ +ol.expression.Lexer.prototype.scanIdentifier_ = function() { + throw new Error('Not yet implemented'); +}; + + +/** + * Scan numeric literal token. + * @return {ol.expression.Token} Numeric literal token. + * @private + */ +ol.expression.Lexer.prototype.scanNumericLiteral_ = function() { + throw new Error('Not yet implemented'); +}; + + +/** + * Scan string literal token. + * @return {ol.expression.Token} String literal token. + * @private + */ +ol.expression.Lexer.prototype.scanStringLiteral_ = function() { + throw new Error('Not yet implemented'); +}; + + +/** + * Peek at the next token, but don't advance the index. + * @return {ol.expression.Token} The upcoming token. + * @private + */ +ol.expression.Lexer.prototype.peek_ = function() { + var currentIndex = this.index_; + var token = this.advance_(); + this.index_ = currentIndex; + return token; +}; + + +/** + * Tokenize the provided code. + * @return {Array.} Tokens. + */ +ol.expression.Lexer.prototype.tokenize = function() { + return []; +}; diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js new file mode 100644 index 0000000000..91040c928a --- /dev/null +++ b/test/spec/ol/expression/lexer.test.js @@ -0,0 +1,14 @@ +goog.provide('ol.test.expression.Lexer'); + +describe('ol.expression.Lexer', function() { + + describe('constructor', function() { + it('creates a new lexer', function() { + var lexer = new ol.expression.Lexer('foo'); + expect(lexer).to.be.a(ol.expression.Lexer); + }); + }); + +}); + +goog.require('ol.expression.Lexer'); From 81b344715a8e49bccd77af0e1dfb0439a27fbf32 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 5 Jun 2013 19:05:40 -0600 Subject: [PATCH 04/84] Renaming for clarity --- src/ol/expression/lexer.js | 125 ++++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 58 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 564d580268..0a09aae85f 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -188,85 +188,92 @@ ol.expression.Lexer.prototype.isFutureReservedWord_ = function(id) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 - * @param {number} ch The unicode of a character. + * @param {number} code The unicode of a character. * @return {boolean} The character is a hex digit. * @private */ -ol.expression.Lexer.prototype.isHexDigit_ = function(ch) { - return this.isDecimalDigit_(ch) || - (ch >= ol.expression.Char.LOWER_A && ch <= ol.expression.Char.LOWER_F) || - (ch >= ol.expression.Char.UPPER_A && ch <= ol.expression.Char.UPPER_F); +ol.expression.Lexer.prototype.isHexDigit_ = function(code) { + return this.isDecimalDigit_(code) || + (code >= ol.expression.Char.LOWER_A && + code <= ol.expression.Char.LOWER_F) || + (code >= ol.expression.Char.UPPER_A && + code <= ol.expression.Char.UPPER_F); }; /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 * Doesn't deal with non-ascii identifiers. - * @param {number} ch The unicode of a character. + * @param {number} code The unicode of a character. * @return {boolean} The character is a valid identifier part. * @private */ -ol.expression.Lexer.prototype.isIdentifierPart_ = function(ch) { - return this.isIdentifierStart_(ch) || - (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_9); +ol.expression.Lexer.prototype.isIdentifierPart_ = function(code) { + return this.isIdentifierStart_(code) || + (code >= ol.expression.Char.DIGIT_0 && + code <= ol.expression.Char.DIGIT_9); }; /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 * Doesn't yet deal with non-ascii identifiers. - * @param {number} ch The unicode of a character. + * @param {number} code The unicode of a character. * @return {boolean} The character is a valid identifier start. * @private */ -ol.expression.Lexer.prototype.isIdentifierStart_ = function(ch) { - return (ch === ol.expression.Char.DOLLAR) || - (ch === ol.expression.Char.UNDERSCORE) || - (ch >= ol.expression.Char.UPPER_A && ch <= ol.expression.Char.UPPER_Z) || - (ch >= ol.expression.Char.LOWER_A && ch <= ol.expression.Char.LOWER_Z); +ol.expression.Lexer.prototype.isIdentifierStart_ = function(code) { + return (code === ol.expression.Char.DOLLAR) || + (code === ol.expression.Char.UNDERSCORE) || + (code >= ol.expression.Char.UPPER_A && + code <= ol.expression.Char.UPPER_Z) || + (code >= ol.expression.Char.LOWER_A && + code <= ol.expression.Char.LOWER_Z); }; /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 - * @param {number} ch The unicode of a character. + * @param {number} code The unicode of a character. * @return {boolean} The character is a line terminator. * @private */ -ol.expression.Lexer.prototype.isLineTerminator_ = function(ch) { - return (ch === ol.expression.Char.LINE_FEED) || - (ch === ol.expression.Char.CARRIAGE_RETURN) || - (ch === ol.expression.Char.LINE_SEPARATOR) || - (ch === ol.expression.Char.PARAGRAPH_SEPARATOR); +ol.expression.Lexer.prototype.isLineTerminator_ = function(code) { + return (code === ol.expression.Char.LINE_FEED) || + (code === ol.expression.Char.CARRIAGE_RETURN) || + (code === ol.expression.Char.LINE_SEPARATOR) || + (code === ol.expression.Char.PARAGRAPH_SEPARATOR); }; /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 - * @param {number} ch The unicode of a character. + * @param {number} code The unicode of a character. * @return {boolean} The character is an octal digit. * @private */ -ol.expression.Lexer.prototype.isOctalDigit_ = function(ch) { - return (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_7); +ol.expression.Lexer.prototype.isOctalDigit_ = function(code) { + return ( + code >= ol.expression.Char.DIGIT_0 && + code <= ol.expression.Char.DIGIT_7); }; /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.2 - * @param {number} ch The unicode of a character. + * @param {number} code The unicode of a character. * @return {boolean} The character is whitespace. * @private */ -ol.expression.Lexer.prototype.isWhitespace_ = function(ch) { - return (ch === ol.expression.Char.SPACE) || - (ch === ol.expression.Char.TAB) || - (ch === ol.expression.Char.VERTICAL_TAB) || - (ch === ol.expression.Char.FORM_FEED) || - (ch === ol.expression.Char.NONBREAKING_SPACE) || - (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' + +ol.expression.Lexer.prototype.isWhitespace_ = function(code) { + return (code === ol.expression.Char.SPACE) || + (code === ol.expression.Char.TAB) || + (code === ol.expression.Char.VERTICAL_TAB) || + (code === ol.expression.Char.FORM_FEED) || + (code === ol.expression.Char.NONBREAKING_SPACE) || + (code >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' + '\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF' - .indexOf(String.fromCharCode(ch)) > 0); + .indexOf(String.fromCharCode(code)) > 0); }; @@ -297,36 +304,36 @@ ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { * @private */ ol.expression.Lexer.prototype.scanPunctuator_ = function() { - var ch = this.getCurrentCharCode_(); + var code = this.getCurrentCharCode_(); // single char punctuation - if (ch === ol.expression.Char.DOT || - ch === ol.expression.Char.LEFT_PAREN || - ch === ol.expression.Char.RIGHT_PAREN || - ch === ol.expression.Char.COMMA || - ch === ol.expression.Char.GREATER || - ch === ol.expression.Char.LESS || - ch === ol.expression.Char.PLUS || - ch === ol.expression.Char.MINUS || - ch === ol.expression.Char.STAR || - ch === ol.expression.Char.PERCENT || - ch === ol.expression.Char.PIPE || - ch === ol.expression.Char.AMPERSAND || - ch === ol.expression.Char.TILDE) { + if (code === ol.expression.Char.DOT || + code === ol.expression.Char.LEFT_PAREN || + code === ol.expression.Char.RIGHT_PAREN || + code === ol.expression.Char.COMMA || + code === ol.expression.Char.GREATER || + code === ol.expression.Char.LESS || + code === ol.expression.Char.PLUS || + code === ol.expression.Char.MINUS || + code === ol.expression.Char.STAR || + code === ol.expression.Char.PERCENT || + code === ol.expression.Char.PIPE || + code === ol.expression.Char.AMPERSAND || + code === ol.expression.Char.TILDE) { this.increment_(1); return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(ch) + value: String.fromCharCode(code) }; } // check for 2-character punctuation - var ch1 = this.getCharCode_(1); + var nextCode = this.getCharCode_(1); // assignment or comparison (and we don't allow assignment) - if (ch1 === ol.expression.Char.EQUAL) { - if (ch === ol.expression.Char.BANG || ch === ol.expression.Char.EQUAL) { + if (nextCode === ol.expression.Char.EQUAL) { + if (code === ol.expression.Char.BANG || code === ol.expression.Char.EQUAL) { // we're looking at !=, ==, !==, or === this.increment_(2); @@ -335,31 +342,33 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function() { this.increment_(1); return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(ch) + '==' + value: String.fromCharCode(code) + '==' }; } else { // != or == return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(ch) + '=' + value: String.fromCharCode(code) + '=' }; } } - if (ch === ol.expression.Char.GREATER || ch === ol.expression.Char.LESS) { + if (code === ol.expression.Char.GREATER || + code === ol.expression.Char.LESS) { return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(ch) + '=' + value: String.fromCharCode(code) + '=' }; } } // remaining 2-charcter punctuators are || and && - if (ch === ch1 && - (ch === ol.expression.Char.PIPE || ch === ol.expression.Char.AMPERSAND)) { + if (code === nextCode && + (code === ol.expression.Char.PIPE || + code === ol.expression.Char.AMPERSAND)) { this.increment_(2); - var str = String.fromCharCode(ch); + var str = String.fromCharCode(code); return { type: ol.expression.TokenType.PUNCTUATOR, value: str + str From 193cf4de5099e0a9bd95a1beddaa1e7fb3ef4996 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 5 Jun 2013 19:05:56 -0600 Subject: [PATCH 05/84] Scan numeric literals --- src/ol/expression/lexer.js | 253 ++++++++++++++++++++++---- test/spec/ol/expression/lexer.test.js | 44 +++++ 2 files changed, 263 insertions(+), 34 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 0a09aae85f..cf2ddd42b2 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -1,5 +1,7 @@ goog.provide('ol.expression.Lexer'); +goog.require('goog.asserts'); + /** * @enum {number} @@ -23,7 +25,9 @@ ol.expression.Char = { LINE_FEED: 10, LINE_SEPARATOR: 0x2028, LOWER_A: 97, + LOWER_E: 101, LOWER_F: 102, + LOWER_X: 120, LOWER_Z: 122, MINUS: 45, NONBREAKING_SPACE: 0xA0, @@ -39,7 +43,9 @@ ol.expression.Char = { TILDE: 126, UNDERSCORE: 95, UPPER_A: 65, + UPPER_E: 69, UPPER_F: 70, + UPPER_X: 88, UPPER_Z: 90, VERTICAL_TAB: 0xB }; @@ -111,35 +117,35 @@ ol.expression.Lexer.prototype.advance_ = function() { value: null }; } - var ch = this.getCurrentCharCode_(); + var code = this.getCurrentCharCode_(); // check for common punctuation - if (ch === ol.expression.Char.LEFT_PAREN || - ch === ol.expression.Char.RIGHT_PAREN) { + if (code === ol.expression.Char.LEFT_PAREN || + code === ol.expression.Char.RIGHT_PAREN) { return this.scanPunctuator_(); } // check for string literal - if (ch === ol.expression.Char.SINGLE_QUOTE || - ch === ol.expression.Char.DOUBLE_QUOTE) { + if (code === ol.expression.Char.SINGLE_QUOTE || + code === ol.expression.Char.DOUBLE_QUOTE) { return this.scanStringLiteral_(); } // check for identifier - if (this.isIdentifierStart_(ch)) { + if (this.isIdentifierStart_(code)) { this.scanIdentifier_(); } // check dot punctuation or decimal - if (ch === ol.expression.Char.DOT) { + if (code === ol.expression.Char.DOT) { if (this.isDecimalDigit_(this.getCharCode_(1))) { return this.scanNumericLiteral_(); } return this.scanPunctuator_(); } - // check decimal number - if (this.isDecimalDigit_(ch)) { + // check for numeric literal + if (this.isDecimalDigit_(code)) { return this.scanNumericLiteral_(); } @@ -160,12 +166,14 @@ ol.expression.Lexer.prototype.increment_ = function(delta) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 - * @param {number} ch The unicode of a character. + * @param {number} code The unicode of a character. * @return {boolean} The character is a decimal digit. * @private */ -ol.expression.Lexer.prototype.isDecimalDigit_ = function(ch) { - return (ch >= ol.expression.Char.DIGIT_0 && ch <= ol.expression.Char.DIGIT_9); +ol.expression.Lexer.prototype.isDecimalDigit_ = function(code) { + return ( + code >= ol.expression.Char.DIGIT_0 && + code <= ol.expression.Char.DIGIT_9); }; @@ -288,6 +296,16 @@ ol.expression.Lexer.prototype.getCharCode_ = function(delta) { }; +/** + * Get the character at the current index. + * @return {string} The current character. + * @private + */ +ol.expression.Lexer.prototype.getCurrentChar_ = function() { + return this.source_[this.index_]; +}; + + /** * Get the unicode of the character at the current index. * @return {number} The current character code. @@ -298,6 +316,193 @@ ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { }; +/** + * Scan hex literal as numeric token. + * @return {ol.expression.Token} Numeric literal token. + * @private + */ +ol.expression.Lexer.prototype.scanHexLiteral_ = function() { + var code = this.getCurrentCharCode_(); + var str = ''; + + while (this.index_ < this.length_) { + if (!this.isHexDigit_(code)) { + break; + } + str += String.fromCharCode(code); + this.increment_(1); + code = this.getCurrentCharCode_(); + } + + if (str.length === 0) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + String.fromCharCode(code)); + } + + if (this.isIdentifierStart_(code)) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + String.fromCharCode(code)); + } + + goog.asserts.assert(!isNaN(parseInt('0x' + str, 16)), 'Valid hex: ' + str); + + return { + type: ol.expression.TokenType.NUMERIC_LITERAL, + value: parseInt('0x' + str, 16) + }; +}; + + +/** + * Scan identifier token. + * @return {ol.expression.Token} Identifier token. + * @private + */ +ol.expression.Lexer.prototype.scanIdentifier_ = function() { + throw new Error('Not yet implemented'); +}; + + +/** + * Scan numeric literal token. + * @return {ol.expression.Token} Numeric literal token. + * @private + */ +ol.expression.Lexer.prototype.scanNumericLiteral_ = function() { + var code = this.getCurrentCharCode_(); + goog.asserts.assert( + code === ol.expression.Char.DOT || this.isDecimalDigit_(code), + 'Valid start for numeric literal: ' + String.fromCharCode(code)); + + // start assembling numeric string + var str = ''; + + if (code !== ol.expression.Char.DOT) { + + if (code === ol.expression.Char.DIGIT_0) { + var nextCode = this.getCharCode_(1); + + // hex literals start with 0X or 0x + if (nextCode === ol.expression.Char.UPPER_X || + nextCode === ol.expression.Char.LOWER_X) { + this.increment_(2); + return this.scanHexLiteral_(); + } + + // octals start with 0 + if (this.isOctalDigit_(nextCode)) { + this.increment_(1); + return this.scanOctalLiteral_(); + } + + // numbers like 04 not allowed + if (this.isDecimalDigit_(nextCode)) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + String.fromCharCode(nextCode)); + } + } + + // scan all decimal chars + while (this.isDecimalDigit_(code)) { + str += String.fromCharCode(code); + this.increment_(1); + code = this.getCurrentCharCode_(); + } + } + + // scan fractional part + if (code === ol.expression.Char.DOT) { + str += String.fromCharCode(code); + this.increment_(1); + code = this.getCurrentCharCode_(); + + // scan all decimal chars + while (this.isDecimalDigit_(code)) { + str += String.fromCharCode(code); + this.increment_(1); + code = this.getCurrentCharCode_(); + } + } + + // scan exponent + if (code === ol.expression.Char.UPPER_E || + code === ol.expression.Char.LOWER_E) { + str += 'E'; + this.increment_(1); + + code = this.getCurrentCharCode_(); + if (code === ol.expression.Char.PLUS || + code === ol.expression.Char.MINUS) { + str += String.fromCharCode(code); + this.increment_(1); + code = this.getCurrentCharCode_(); + } + + if (!this.isDecimalDigit_(code)) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + String.fromCharCode(code)); + } + + // scan all decimal chars (TODO: unduplicate this) + while (this.isDecimalDigit_(code)) { + str += String.fromCharCode(code); + this.increment_(1); + code = this.getCurrentCharCode_(); + } + } + + if (this.isIdentifierStart_(code)) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + String.fromCharCode(code)); + } + + goog.asserts.assert(!isNaN(parseFloat(str)), 'Valid number: ' + str); + + return { + type: ol.expression.TokenType.NUMERIC_LITERAL, + value: parseFloat(str) + }; + +}; + + +/** + * Scan octal literal as numeric token. + * @return {ol.expression.Token} Numeric literal token. + * @private + */ +ol.expression.Lexer.prototype.scanOctalLiteral_ = function() { + var code = this.getCurrentCharCode_(); + goog.asserts.assert(this.isOctalDigit_(code)); + + var str = '0' + String.fromCharCode(code); + this.increment_(1); + + while (this.index_ < this.length_) { + code = this.getCurrentCharCode_(); + if (!this.isOctalDigit_(code)) { + break; + } + str += String.fromCharCode(code); + this.increment_(1); + } + + code = this.getCurrentCharCode_(); + if (this.isIdentifierStart_(code) || + this.isDecimalDigit_(code)) { + throw new Error('Unexpected token at index ' + (this.index_ - 1) + + ': ' + String.fromCharCode(code)); + } + + goog.asserts.assert(!isNaN(parseInt(str, 8)), 'Valid octal: ' + str); + + return { + type: ol.expression.TokenType.NUMERIC_LITERAL, + value: parseInt(str, 8) + }; +}; + + /** * Scan punctuator token (a subset of allowed tokens in 7.7). * @return {ol.expression.Token} Punctuator token. @@ -378,28 +583,8 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function() { // we don't allow 4-character punctuator (>>>=) // and the allowed 3-character punctuators (!==, ===) are already consumed - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + String.fromCharCode(ch)); -}; - - -/** - * Scan identifier token. - * @return {ol.expression.Token} Identifier token. - * @private - */ -ol.expression.Lexer.prototype.scanIdentifier_ = function() { - throw new Error('Not yet implemented'); -}; - - -/** - * Scan numeric literal token. - * @return {ol.expression.Token} Numeric literal token. - * @private - */ -ol.expression.Lexer.prototype.scanNumericLiteral_ = function() { - throw new Error('Not yet implemented'); + throw new Error('Unexpected token at index ' + (this.index_ - 1) + + ': ' + String.fromCharCode(code)); }; diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 91040c928a..42d4bc3867 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -9,6 +9,50 @@ describe('ol.expression.Lexer', function() { }); }); + describe('#scanNumericLiteral_()', function() { + + function scan(code) { + var lexer = new ol.expression.Lexer(code); + return lexer.scanNumericLiteral_(); + } + + it('works for integers', function() { + var token = scan('123'); + expect(token.value).to.be(123); + }); + + it('works for float', function() { + var token = scan('123.456'); + expect(token.value).to.be(123.456); + }); + + it('works with exponent', function() { + var token = scan('1.234e5'); + expect(token.value).to.be(1.234e5); + }); + + it('works with explicit positive exponent', function() { + var token = scan('1.234e+5'); + expect(token.value).to.be(1.234e5); + }); + + it('works with negative exponent', function() { + var token = scan('1.234e-5'); + expect(token.value).to.be(1.234e-5); + }); + + it('works with octals', function() { + var token = scan('02322'); + expect(token.value).to.be(1234); + }); + + it('works with hex', function() { + var token = scan('0x4d2'); + expect(token.value).to.be(1234); + }); + + }); + }); goog.require('ol.expression.Lexer'); From 040c80a5ad1d8d7d1ce490c773a050774ca89dfa Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 7 Jun 2013 10:56:51 -0600 Subject: [PATCH 06/84] Expectations about token type --- src/ol/expression/lexer.js | 1 + test/spec/ol/expression/lexer.test.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index cf2ddd42b2..9dbbb2afc3 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -1,4 +1,5 @@ goog.provide('ol.expression.Lexer'); +goog.provide('ol.expression.TokenType'); goog.require('goog.asserts'); diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 42d4bc3867..89f37614d5 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -19,36 +19,43 @@ describe('ol.expression.Lexer', function() { it('works for integers', function() { var token = scan('123'); expect(token.value).to.be(123); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); it('works for float', function() { var token = scan('123.456'); expect(token.value).to.be(123.456); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); it('works with exponent', function() { var token = scan('1.234e5'); expect(token.value).to.be(1.234e5); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); it('works with explicit positive exponent', function() { var token = scan('1.234e+5'); expect(token.value).to.be(1.234e5); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); it('works with negative exponent', function() { var token = scan('1.234e-5'); expect(token.value).to.be(1.234e-5); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); it('works with octals', function() { var token = scan('02322'); expect(token.value).to.be(1234); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); it('works with hex', function() { var token = scan('0x4d2'); expect(token.value).to.be(1234); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); }); @@ -56,3 +63,4 @@ describe('ol.expression.Lexer', function() { }); goog.require('ol.expression.Lexer'); +goog.require('ol.expression.TokenType'); From a748665646ede8bb365a1ba70c0f95c80ae6e555 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 7 Jun 2013 11:50:33 -0600 Subject: [PATCH 07/84] Space and comment --- src/ol/expression/lexer.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 9dbbb2afc3..db47971464 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -77,6 +77,7 @@ ol.expression.Token; /** * Lexer constructor. + * * @constructor * @param {string} source Source code. */ @@ -108,6 +109,7 @@ ol.expression.Lexer = function(source) { /** * Scan next token. + * * @return {ol.expression.Token} Next token. * @private */ @@ -157,6 +159,7 @@ ol.expression.Lexer.prototype.advance_ = function() { /** * Increment the current character index. + * * @param {number} delta Delta by which the index is advanced. * @private */ @@ -167,6 +170,7 @@ ol.expression.Lexer.prototype.increment_ = function(delta) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * * @param {number} code The unicode of a character. * @return {boolean} The character is a decimal digit. * @private @@ -180,6 +184,7 @@ ol.expression.Lexer.prototype.isDecimalDigit_ = function(code) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.2 + * * @param {string} id A string identifier. * @return {boolean} The identifier is a future reserved word. * @private @@ -197,6 +202,7 @@ ol.expression.Lexer.prototype.isFutureReservedWord_ = function(id) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * * @param {number} code The unicode of a character. * @return {boolean} The character is a hex digit. * @private @@ -213,6 +219,7 @@ ol.expression.Lexer.prototype.isHexDigit_ = function(code) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 * Doesn't deal with non-ascii identifiers. + * * @param {number} code The unicode of a character. * @return {boolean} The character is a valid identifier part. * @private @@ -227,6 +234,7 @@ ol.expression.Lexer.prototype.isIdentifierPart_ = function(code) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6 * Doesn't yet deal with non-ascii identifiers. + * * @param {number} code The unicode of a character. * @return {boolean} The character is a valid identifier start. * @private @@ -243,6 +251,7 @@ ol.expression.Lexer.prototype.isIdentifierStart_ = function(code) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 + * * @param {number} code The unicode of a character. * @return {boolean} The character is a line terminator. * @private @@ -257,6 +266,7 @@ ol.expression.Lexer.prototype.isLineTerminator_ = function(code) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * * @param {number} code The unicode of a character. * @return {boolean} The character is an octal digit. * @private @@ -270,6 +280,7 @@ ol.expression.Lexer.prototype.isOctalDigit_ = function(code) { /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.2 + * * @param {number} code The unicode of a character. * @return {boolean} The character is whitespace. * @private @@ -288,6 +299,7 @@ ol.expression.Lexer.prototype.isWhitespace_ = function(code) { /** * Get the unicode of the character at the given offset from the current index. + * * @param {number} delta Offset from current index. * @return {number} The character code. * @private @@ -299,6 +311,7 @@ ol.expression.Lexer.prototype.getCharCode_ = function(delta) { /** * Get the character at the current index. + * * @return {string} The current character. * @private */ @@ -309,6 +322,7 @@ ol.expression.Lexer.prototype.getCurrentChar_ = function() { /** * Get the unicode of the character at the current index. + * * @return {number} The current character code. * @private */ @@ -319,6 +333,7 @@ ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { /** * Scan hex literal as numeric token. + * * @return {ol.expression.Token} Numeric literal token. * @private */ @@ -356,6 +371,7 @@ ol.expression.Lexer.prototype.scanHexLiteral_ = function() { /** * Scan identifier token. + * * @return {ol.expression.Token} Identifier token. * @private */ @@ -366,6 +382,8 @@ ol.expression.Lexer.prototype.scanIdentifier_ = function() { /** * Scan numeric literal token. + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 + * * @return {ol.expression.Token} Numeric literal token. * @private */ @@ -396,7 +414,7 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function() { return this.scanOctalLiteral_(); } - // numbers like 04 not allowed + // numbers like 09 not allowed if (this.isDecimalDigit_(nextCode)) { throw new Error('Unexpected token at index ' + this.index_ + ': ' + String.fromCharCode(nextCode)); @@ -469,6 +487,7 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function() { /** * Scan octal literal as numeric token. + * * @return {ol.expression.Token} Numeric literal token. * @private */ @@ -506,6 +525,7 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function() { /** * Scan punctuator token (a subset of allowed tokens in 7.7). + * * @return {ol.expression.Token} Punctuator token. * @private */ @@ -591,6 +611,7 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function() { /** * Scan string literal token. + * * @return {ol.expression.Token} String literal token. * @private */ @@ -601,6 +622,7 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function() { /** * Peek at the next token, but don't advance the index. + * * @return {ol.expression.Token} The upcoming token. * @private */ @@ -614,6 +636,7 @@ ol.expression.Lexer.prototype.peek_ = function() { /** * Tokenize the provided code. + * * @return {Array.} Tokens. */ ol.expression.Lexer.prototype.tokenize = function() { From 0844df8cc2db9bd66c750871bcf0f651e4a96b69 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 7 Jun 2013 11:52:34 -0600 Subject: [PATCH 08/84] Scanning identifiers This includes code that is likely not necessary. The escape sequence scanning will likely not be used in our case, but I'm committing it here so it can be brought back if needed later. --- src/ol/expression/lexer.js | 191 +++++++++++++++++++++++++- test/spec/ol/expression/lexer.test.js | 91 +++++++++++- 2 files changed, 279 insertions(+), 3 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index db47971464..e19111dfcc 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -9,6 +9,7 @@ goog.require('goog.asserts'); */ ol.expression.Char = { AMPERSAND: 38, + BACKSLASH: 92, BANG: 33, // ! CARRIAGE_RETURN: 13, COMMA: 44, @@ -28,6 +29,7 @@ ol.expression.Char = { LOWER_A: 97, LOWER_E: 101, LOWER_F: 102, + LOWER_U: 117, LOWER_X: 120, LOWER_Z: 122, MINUS: 45, @@ -249,6 +251,47 @@ ol.expression.Lexer.prototype.isIdentifierStart_ = function(code) { }; +/** + * Determine if the given identifier is an ECMAScript keyword. These cannot + * be used as identifiers in programs. There is no real reason these could not + * be used in ol expressions - so it might be worth allowing them. + * + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.1 + * @param {string} id Identifier. + * @return {boolean} The identifier is a keyword. + * @private + */ +ol.expression.Lexer.prototype.isKeyword_ = function(id) { + return ( + id === 'break' || + id === 'case' || + id === 'catch' || + id === 'continue' || + id === 'debugger' || + id === 'default' || + id === 'delete' || + id === 'do' || + id === 'else' || + id === 'finally' || + id === 'for' || + id === 'function' || + id === 'if' || + id === 'in' || + id === 'instanceof' || + id === 'new' || + id === 'return' || + id === 'switch' || + id === 'this' || + id === 'throw' || + id === 'try' || + id === 'typeof' || + id === 'var' || + id === 'void' || + id === 'while' || + id === 'with'); +}; + + /** * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3 * @@ -331,6 +374,129 @@ ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { }; +/** + * Get an identifier that includes escape sequences. + * + * @return {string} The identifier. + * @private + */ +ol.expression.Lexer.prototype.getEscapedIdentifier_ = function() { + var code = this.getCurrentCharCode_(); + var id = String.fromCharCode(code); + + this.increment_(1); + + // the \u sequence denotes an escaped character + if (code === ol.expression.Char.BACKSLASH) { + if (this.getCurrentCharCode_() !== ol.expression.Char.LOWER_U) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + this.getCurrentChar_()); + } + this.increment_(1); + code = this.scanEscapeSequence_(ol.expression.Char.LOWER_U); + + if (!code || code === ol.expression.Char.BACKSLASH || + !this.isIdentifierStart_(code)) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + this.getCurrentChar_()); + } + id = String.fromCharCode(code); + } + + while (this.index_ < this.length_) { + code = this.getCurrentCharCode_(); + if (!this.isIdentifierPart_(code)) { + break; + } + this.increment_(1); + id += String.fromCharCode(code); + + // the \u sequence denotes an escaped character + if (code === ol.expression.Char.BACKSLASH) { + if (this.getCurrentCharCode_() !== ol.expression.Char.LOWER_U) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + this.getCurrentChar_()); + } + id = id.substr(0, id.length - 1); + this.increment_(1); + code = this.scanEscapeSequence_(ol.expression.Char.LOWER_U); + + if (!code || code === ol.expression.Char.BACKSLASH || + !this.isIdentifierStart_(code)) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + this.getCurrentChar_()); + } + id += String.fromCharCode(code); + } + } + + return id; +}; + + +/** + * Get an identifier. This assumes we've encountered an identifier that doesn't + * start with an escape sequence. If an escape sequence is encountered during + * the scan, we switch to the `getEscapedIdentifier_` method. + * + * @return {string} The identifier. + * @private + */ +ol.expression.Lexer.prototype.getIdentifier_ = function() { + goog.asserts.assert( + this.getCurrentCharCode_() !== ol.expression.Char.BACKSLASH, + 'Must not be called with first char a backslash'); + + var start = this.index_; + this.increment_(1); + + var code; + while (this.index_ < this.length_) { + code = this.getCurrentCharCode_(); + if (code === ol.expression.Char.BACKSLASH) { + // reset cursor and start over scanning escaped identifier + this.index_ = start; + return this.getEscapedIdentifier_(); + } + if (this.isIdentifierPart_(code)) { + this.increment_(1); + } else { + break; + } + } + return this.source_.slice(start, this.index_); +}; + + +/** + * Scan an escape sequence of characters prefixed by the given character + * code. This works for both unicode escape sequences (e.g. \u0123) and + * hex escape sequences (e.g. \x12). + * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4 + * + * @param {number} prefix The character code of the escape prefix. + * @return {number} The unicode of the string resulting from the escape + * sequence. For invalid escape sequences, 0 is returned. + * @private + */ +ol.expression.Lexer.prototype.scanEscapeSequence_ = function(prefix) { + var code = 0; + var len = (prefix === ol.expression.Char.LOWER_U) ? 4 : 2; + var ch; + for (var i = 0; i < len; ++i) { + if (this.index_ < this.length_ && + this.isHexDigit_(this.getCurrentCharCode_())) { + ch = this.getCurrentChar_(); + code = (code * 16) + parseInt(ch, 16); + this.increment_(1); + } else { + return 0; + } + } + return code; +}; + + /** * Scan hex literal as numeric token. * @@ -376,7 +542,30 @@ ol.expression.Lexer.prototype.scanHexLiteral_ = function() { * @private */ ol.expression.Lexer.prototype.scanIdentifier_ = function() { - throw new Error('Not yet implemented'); + var code = this.getCurrentCharCode_(); + goog.asserts.assert(this.isIdentifierStart_(code), + 'Must be called with a valid identifier'); + + var id = (code === ol.expression.Char.BACKSLASH) ? + this.getEscapedIdentifier_() : this.getIdentifier_(); + + var type; + if (id.length === 1) { + type = ol.expression.TokenType.IDENTIFIER; + } else if (this.isKeyword_(id)) { + type = ol.expression.TokenType.KEYWORD; + } else if (id === 'null') { + type = ol.expression.TokenType.NULL_LITERAL; + } else if (id === 'true' || id === 'false') { + type = ol.expression.TokenType.BOOLEAN_LITERAL; + } else { + type = ol.expression.TokenType.IDENTIFIER; + } + + return { + type: type, + value: id + }; }; diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 89f37614d5..0bb094a281 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -9,10 +9,97 @@ describe('ol.expression.Lexer', function() { }); }); + describe('#scanIdentifier_()', function() { + + function scan(source) { + var lexer = new ol.expression.Lexer(source); + return lexer.scanIdentifier_(); + } + + it('works for short identifiers', function() { + var token = scan('a'); + expect(token.value).to.be('a'); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + }); + + it('works for longer identifiers', function() { + var token = scan('foo'); + expect(token.value).to.be('foo'); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + }); + + it('works for $ anywhere', function() { + var token = scan('$foo$bar$'); + expect(token.value).to.be('$foo$bar$'); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + }); + + it('works for _ anywhere', function() { + var token = scan('_foo_bar_'); + expect(token.value).to.be('_foo_bar_'); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + }); + + it('works for keywords', function() { + var token = scan('delete'); + expect(token.value).to.be('delete'); + expect(token.type).to.be(ol.expression.TokenType.KEYWORD); + }); + + it('works for null', function() { + var token = scan('null'); + expect(token.value).to.be('null'); + expect(token.type).to.be(ol.expression.TokenType.NULL_LITERAL); + }); + + it('works for boolean true', function() { + var token = scan('true'); + expect(token.value).to.be('true'); + expect(token.type).to.be(ol.expression.TokenType.BOOLEAN_LITERAL); + }); + + it('works for boolean false', function() { + var token = scan('false'); + expect(token.value).to.be('false'); + expect(token.type).to.be(ol.expression.TokenType.BOOLEAN_LITERAL); + }); + + it('works with unicode escape sequences', function() { + var token = scan('\u006f\u006c\u0033'); + expect(token.value).to.be('ol3'); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + }); + + it('works with hex escape sequences', function() { + var token = scan('\x6f\x6c\x33'); + expect(token.value).to.be('ol3'); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + }); + + it('throws for identifiers starting with a number', function() { + expect(function() { + scan('4foo'); + }).throwException(); + }); + + it('throws for identifiers starting with a punctuation char', function() { + expect(function() { + scan('!foo'); + }).throwException(); + }); + + it('only scans valid identifier part', function() { + var token = scan('foo>bar'); + expect(token.value).to.be('foo'); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + }); + + }); + describe('#scanNumericLiteral_()', function() { - function scan(code) { - var lexer = new ol.expression.Lexer(code); + function scan(source) { + var lexer = new ol.expression.Lexer(source); return lexer.scanNumericLiteral_(); } From 4d62cea7000fd13d2207acffb74d4478ed9401f9 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 7 Jun 2013 12:05:45 -0600 Subject: [PATCH 09/84] Escape sequences in our identifiers have already been handled --- src/ol/expression/lexer.js | 138 ++++--------------------------------- 1 file changed, 12 insertions(+), 126 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index e19111dfcc..ea564bc6f7 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -29,7 +29,6 @@ ol.expression.Char = { LOWER_A: 97, LOWER_E: 101, LOWER_F: 102, - LOWER_U: 117, LOWER_X: 120, LOWER_Z: 122, MINUS: 45, @@ -374,129 +373,6 @@ ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { }; -/** - * Get an identifier that includes escape sequences. - * - * @return {string} The identifier. - * @private - */ -ol.expression.Lexer.prototype.getEscapedIdentifier_ = function() { - var code = this.getCurrentCharCode_(); - var id = String.fromCharCode(code); - - this.increment_(1); - - // the \u sequence denotes an escaped character - if (code === ol.expression.Char.BACKSLASH) { - if (this.getCurrentCharCode_() !== ol.expression.Char.LOWER_U) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + this.getCurrentChar_()); - } - this.increment_(1); - code = this.scanEscapeSequence_(ol.expression.Char.LOWER_U); - - if (!code || code === ol.expression.Char.BACKSLASH || - !this.isIdentifierStart_(code)) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + this.getCurrentChar_()); - } - id = String.fromCharCode(code); - } - - while (this.index_ < this.length_) { - code = this.getCurrentCharCode_(); - if (!this.isIdentifierPart_(code)) { - break; - } - this.increment_(1); - id += String.fromCharCode(code); - - // the \u sequence denotes an escaped character - if (code === ol.expression.Char.BACKSLASH) { - if (this.getCurrentCharCode_() !== ol.expression.Char.LOWER_U) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + this.getCurrentChar_()); - } - id = id.substr(0, id.length - 1); - this.increment_(1); - code = this.scanEscapeSequence_(ol.expression.Char.LOWER_U); - - if (!code || code === ol.expression.Char.BACKSLASH || - !this.isIdentifierStart_(code)) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + this.getCurrentChar_()); - } - id += String.fromCharCode(code); - } - } - - return id; -}; - - -/** - * Get an identifier. This assumes we've encountered an identifier that doesn't - * start with an escape sequence. If an escape sequence is encountered during - * the scan, we switch to the `getEscapedIdentifier_` method. - * - * @return {string} The identifier. - * @private - */ -ol.expression.Lexer.prototype.getIdentifier_ = function() { - goog.asserts.assert( - this.getCurrentCharCode_() !== ol.expression.Char.BACKSLASH, - 'Must not be called with first char a backslash'); - - var start = this.index_; - this.increment_(1); - - var code; - while (this.index_ < this.length_) { - code = this.getCurrentCharCode_(); - if (code === ol.expression.Char.BACKSLASH) { - // reset cursor and start over scanning escaped identifier - this.index_ = start; - return this.getEscapedIdentifier_(); - } - if (this.isIdentifierPart_(code)) { - this.increment_(1); - } else { - break; - } - } - return this.source_.slice(start, this.index_); -}; - - -/** - * Scan an escape sequence of characters prefixed by the given character - * code. This works for both unicode escape sequences (e.g. \u0123) and - * hex escape sequences (e.g. \x12). - * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4 - * - * @param {number} prefix The character code of the escape prefix. - * @return {number} The unicode of the string resulting from the escape - * sequence. For invalid escape sequences, 0 is returned. - * @private - */ -ol.expression.Lexer.prototype.scanEscapeSequence_ = function(prefix) { - var code = 0; - var len = (prefix === ol.expression.Char.LOWER_U) ? 4 : 2; - var ch; - for (var i = 0; i < len; ++i) { - if (this.index_ < this.length_ && - this.isHexDigit_(this.getCurrentCharCode_())) { - ch = this.getCurrentChar_(); - code = (code * 16) + parseInt(ch, 16); - this.increment_(1); - } else { - return 0; - } - } - return code; -}; - - /** * Scan hex literal as numeric token. * @@ -546,8 +422,18 @@ ol.expression.Lexer.prototype.scanIdentifier_ = function() { goog.asserts.assert(this.isIdentifierStart_(code), 'Must be called with a valid identifier'); - var id = (code === ol.expression.Char.BACKSLASH) ? - this.getEscapedIdentifier_() : this.getIdentifier_(); + var start = this.index_; + this.increment_(1); + + while (this.index_ < this.length_) { + code = this.getCurrentCharCode_(); + if (this.isIdentifierPart_(code)) { + this.increment_(1); + } else { + break; + } + } + var id = this.source_.slice(start, this.index_); var type; if (id.length === 1) { From 50f94911b1e73549ca21859d70faee687332589c Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 7 Jun 2013 16:14:22 -0600 Subject: [PATCH 10/84] Scan string literals --- src/ol/expression/lexer.js | 35 ++++++++- test/spec/ol/expression/lexer.test.js | 105 ++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index ea564bc6f7..881fb26361 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -691,7 +691,40 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function() { * @private */ ol.expression.Lexer.prototype.scanStringLiteral_ = function() { - throw new Error('Not yet implemented'); + var quote = this.getCurrentCharCode_(); + goog.asserts.assert(quote === ol.expression.Char.SINGLE_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 = ''; + var code; + while (this.index_ < this.length_) { + code = this.getCurrentCharCode_(); + this.increment_(1); + if (code === quote) { + quote = 0; + break; + } + // look for escaped quote or backslash + if (code === ol.expression.Char.BACKSLASH) { + str += this.getCurrentChar_(); + this.increment_(1); + } else { + str += String.fromCharCode(code); + } + } + + if (quote !== 0) { + throw new Error('Unterminated string literal'); + } + + return { + type: ol.expression.TokenType.STRING_LITERAL, + value: str + }; }; diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 0bb094a281..69aff7a6a3 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -147,6 +147,111 @@ describe('ol.expression.Lexer', function() { }); + describe('#scanStringLiteral_()', function() { + + function scan(source) { + var lexer = new ol.expression.Lexer(source); + return lexer.scanStringLiteral_(); + } + + it('parses double quoted string', function() { + var token = scan('"my string"'); + expect(token.value).to.be('my string'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses double quoted string with internal single quotes', function() { + var token = scan('"my \'quoted\' string"'); + expect(token.value).to.be('my \'quoted\' string'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses double quoted string with escaped double quotes', function() { + var token = scan('"my \\"quoted\\" string"'); + expect(token.value).to.be('my "quoted" string'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses double quoted string with escaped backslash', function() { + var token = scan('"my \\\ string"'); + expect(token.value).to.be('my \ string'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses double quoted string with unicode escape sequences', function() { + var token = scan('"\u006f\u006c\u0033"'); + expect(token.value).to.be('ol3'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses double quoted string with hex escape sequences', function() { + var token = scan('"\x6f\x6c\x33"'); + expect(token.value).to.be('ol3'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses double quoted string with tab', function() { + var token = scan('"a\ttab"'); + expect(token.value).to.be('a\ttab'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('throws on unterminated double quote', function() { + expect(function() { + scan('"never \'ending\' string'); + }).to.throwException(); + }); + + it('parses single quoted string', function() { + var token = scan('\'my string\''); + expect(token.value).to.be('my string'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses single quoted string with internal double quotes', function() { + var token = scan('\'my "quoted" string\''); + expect(token.value).to.be('my "quoted" string'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses single quoted string with escaped single quotes', function() { + var token = scan('\'my \\\'quoted\\\' string\''); + expect(token.value).to.be('my \'quoted\' string'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses single quoted string with escaped backslash', function() { + var token = scan('\'my \\\ string\''); + expect(token.value).to.be('my \ string'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses single quoted string with unicode escape sequences', function() { + var token = scan('\'\u006f\u006c\u0033\''); + expect(token.value).to.be('ol3'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses single quoted string with hex escape sequences', function() { + var token = scan('\'\x6f\x6c\x33\''); + expect(token.value).to.be('ol3'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('parses single quoted string with tab', function() { + var token = scan('\'a\ttab\''); + expect(token.value).to.be('a\ttab'); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + }); + + it('throws on unterminated single quote', function() { + expect(function() { + scan('\'never "ending" string'); + }).to.throwException(); + }); + + }); + }); goog.require('ol.expression.Lexer'); From 957b6db3d7d103fd0b6b9f3bfd63d2e733688f20 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 7 Jun 2013 16:29:21 -0600 Subject: [PATCH 11/84] Skip whitespace --- src/ol/expression/lexer.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 881fb26361..a107995764 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -728,6 +728,21 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function() { }; +/** + * Skip all whitespace. + * @private + */ +ol.expression.Lexer.prototype.skipWhitespace_ = function() { + var code; + while (this.index_ < this.length_) { + code = this.getCurrentCharCode_(); + if (this.isWhitespace_(code)) { + this.increment_(1); + } + } +}; + + /** * Peek at the next token, but don't advance the index. * From 5baa38b82c30bb31db6e873ec17ae431825e9a47 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sun, 9 Jun 2013 00:02:21 -0600 Subject: [PATCH 12/84] Test punctuator scanning --- src/ol/expression/lexer.js | 25 ++++++-- test/spec/ol/expression/lexer.test.js | 87 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 6 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index a107995764..1b37a740c1 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -39,6 +39,7 @@ ol.expression.Char = { PLUS: 43, RIGHT_PAREN: 41, SINGLE_QUOTE: 39, + SLASH: 47, SPACE: 32, STAR: 42, TAB: 9, @@ -607,19 +608,17 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function() { ol.expression.Lexer.prototype.scanPunctuator_ = function() { var code = this.getCurrentCharCode_(); - // single char punctuation + // single char punctuation that also doesn't start longer punctuation + // (we disallow assignment, so no += etc.) if (code === ol.expression.Char.DOT || code === ol.expression.Char.LEFT_PAREN || code === ol.expression.Char.RIGHT_PAREN || code === ol.expression.Char.COMMA || - code === ol.expression.Char.GREATER || - code === ol.expression.Char.LESS || code === ol.expression.Char.PLUS || code === ol.expression.Char.MINUS || code === ol.expression.Char.STAR || + code === ol.expression.Char.SLASH || code === ol.expression.Char.PERCENT || - code === ol.expression.Char.PIPE || - code === ol.expression.Char.AMPERSAND || code === ol.expression.Char.TILDE) { this.increment_(1); @@ -639,7 +638,7 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function() { this.increment_(2); // check for triple - if (this.getCharCode_(1) === ol.expression.Char.EQUAL) { + if (this.getCurrentCharCode_() === ol.expression.Char.EQUAL) { this.increment_(1); return { type: ol.expression.TokenType.PUNCTUATOR, @@ -679,6 +678,20 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function() { // we don't allow 4-character punctuator (>>>=) // and the allowed 3-character punctuators (!==, ===) are already consumed + // other single character punctuators + if (code === ol.expression.Char.GREATER || + code === ol.expression.Char.LESS || + code === ol.expression.Char.BANG || + code === ol.expression.Char.AMPERSAND || + code === ol.expression.Char.PIPE) { + + this.increment_(1); + return { + type: ol.expression.TokenType.PUNCTUATOR, + value: String.fromCharCode(code) + }; + } + throw new Error('Unexpected token at index ' + (this.index_ - 1) + ': ' + String.fromCharCode(code)); }; diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 69aff7a6a3..52eec7ae99 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -147,6 +147,93 @@ describe('ol.expression.Lexer', function() { }); + describe('#scanPunctuator_()', function() { + + function scan(source) { + var lexer = new ol.expression.Lexer(source); + return lexer.scanPunctuator_(); + } + + it('works for dot', function() { + var token = scan('.'); + expect(token.value).to.be('.'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for bang', function() { + var token = scan('!'); + expect(token.value).to.be('!'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for double equal', function() { + var token = scan('=='); + expect(token.value).to.be('=='); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for triple equal', function() { + var token = scan('==='); + expect(token.value).to.be('==='); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for not double equal', function() { + var token = scan('!='); + expect(token.value).to.be('!='); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for not triple equal', function() { + var token = scan('!=='); + expect(token.value).to.be('!=='); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for logical or', function() { + var token = scan('||'); + expect(token.value).to.be('||'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for logical and', function() { + var token = scan('&&'); + expect(token.value).to.be('&&'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for plus', function() { + var token = scan('+'); + expect(token.value).to.be('+'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for minus', function() { + var token = scan('-'); + expect(token.value).to.be('-'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for star', function() { + var token = scan('*'); + expect(token.value).to.be('*'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for slash', function() { + var token = scan('/'); + expect(token.value).to.be('/'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + it('works for percent', function() { + var token = scan('%'); + expect(token.value).to.be('%'); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + }); + + }); + describe('#scanStringLiteral_()', function() { function scan(source) { From f272350e00d2ee01e3ff01328a883a75b82a1611 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sun, 9 Jun 2013 00:11:41 -0600 Subject: [PATCH 13/84] Expose next and peek methods --- src/ol/expression/lexer.js | 109 +++++++++++++------------- test/spec/ol/expression/lexer.test.js | 82 +++++++++++++++++++ 2 files changed, 138 insertions(+), 53 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 1b37a740c1..b1cefa4475 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -109,56 +109,6 @@ ol.expression.Lexer = function(source) { }; -/** - * Scan next token. - * - * @return {ol.expression.Token} Next token. - * @private - */ -ol.expression.Lexer.prototype.advance_ = function() { - if (this.index_ >= this.length_) { - return { - type: ol.expression.TokenType.EOF, - value: null - }; - } - var code = this.getCurrentCharCode_(); - - // check for common punctuation - if (code === ol.expression.Char.LEFT_PAREN || - code === ol.expression.Char.RIGHT_PAREN) { - return this.scanPunctuator_(); - } - - // check for string literal - if (code === ol.expression.Char.SINGLE_QUOTE || - code === ol.expression.Char.DOUBLE_QUOTE) { - return this.scanStringLiteral_(); - } - - // check for identifier - if (this.isIdentifierStart_(code)) { - this.scanIdentifier_(); - } - - // check dot punctuation or decimal - if (code === ol.expression.Char.DOT) { - if (this.isDecimalDigit_(this.getCharCode_(1))) { - return this.scanNumericLiteral_(); - } - return this.scanPunctuator_(); - } - - // check for numeric literal - if (this.isDecimalDigit_(code)) { - return this.scanNumericLiteral_(); - } - - // all the rest is punctuation - return this.scanPunctuator_(); -}; - - /** * Increment the current character index. * @@ -374,6 +324,56 @@ ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { }; +/** + * Scan the next token. + * + * @return {ol.expression.Token} Next token. + */ +ol.expression.Lexer.prototype.next = function() { + var code = this.skipWhitespace_(); + + if (this.index_ >= this.length_) { + return { + type: ol.expression.TokenType.EOF, + value: null + }; + } + + // check for common punctuation + if (code === ol.expression.Char.LEFT_PAREN || + code === ol.expression.Char.RIGHT_PAREN) { + return this.scanPunctuator_(); + } + + // check for string literal + if (code === ol.expression.Char.SINGLE_QUOTE || + code === ol.expression.Char.DOUBLE_QUOTE) { + return this.scanStringLiteral_(); + } + + // check for identifier + if (this.isIdentifierStart_(code)) { + return this.scanIdentifier_(); + } + + // check dot punctuation or decimal + if (code === ol.expression.Char.DOT) { + if (this.isDecimalDigit_(this.getCharCode_(1))) { + return this.scanNumericLiteral_(); + } + return this.scanPunctuator_(); + } + + // check for numeric literal + if (this.isDecimalDigit_(code)) { + return this.scanNumericLiteral_(); + } + + // all the rest is punctuation + return this.scanPunctuator_(); +}; + + /** * Scan hex literal as numeric token. * @@ -743,6 +743,7 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function() { /** * Skip all whitespace. + * @return {number} The character code of the first non-whitespace character. * @private */ ol.expression.Lexer.prototype.skipWhitespace_ = function() { @@ -751,8 +752,11 @@ ol.expression.Lexer.prototype.skipWhitespace_ = function() { code = this.getCurrentCharCode_(); if (this.isWhitespace_(code)) { this.increment_(1); + } else { + break; } } + return code; }; @@ -760,11 +764,10 @@ ol.expression.Lexer.prototype.skipWhitespace_ = function() { * Peek at the next token, but don't advance the index. * * @return {ol.expression.Token} The upcoming token. - * @private */ -ol.expression.Lexer.prototype.peek_ = function() { +ol.expression.Lexer.prototype.peek = function() { var currentIndex = this.index_; - var token = this.advance_(); + var token = this.next(); this.index_ = currentIndex; return token; }; diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 52eec7ae99..8673b52081 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -9,6 +9,88 @@ describe('ol.expression.Lexer', function() { }); }); + describe.only('#next()', function() { + + it('returns one token at a time', function() { + var source = 'foo === "bar"'; + var lexer = new ol.expression.Lexer(source); + + // scan first token + var token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.value).to.be('foo'); + + // scan second token + token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.value).to.be('==='); + + // scan third token + token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.value).to.be('bar'); + + // scan again + token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.EOF); + + // and again + token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.EOF); + }); + + }); + + describe.only('#peek()', function() { + + var lexer; + beforeEach(function() { + lexer = new ol.expression.Lexer('foo > 42 && bar == "chicken"'); + }); + + it('looks ahead without consuming token', function() { + var token = lexer.peek(); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.value).to.be('foo'); + + token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.value).to.be('foo'); + }); + + it('works after a couple scans', function() { + var token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.value).to.be('foo'); + + token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.value).to.be('>'); + + token = lexer.peek(); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.value).to.be(42); + + token = lexer.next(); + expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.value).to.be(42); + }); + + it('returns the same thing when called multiple times', function() { + var token = lexer.peek(); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.value).to.be('foo'); + + for (var i = 0; i < 10; ++i) { + token = lexer.peek(); + expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.value).to.be('foo'); + } + }); + + }); + + describe('#scanIdentifier_()', function() { function scan(source) { From 9edc9ebcc5d44fb3e3d2999204eeee6f930b0897 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sun, 9 Jun 2013 00:12:33 -0600 Subject: [PATCH 14/84] Use next instead --- src/ol/expression/lexer.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index b1cefa4475..e051bc3d41 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -771,13 +771,3 @@ ol.expression.Lexer.prototype.peek = function() { this.index_ = currentIndex; return token; }; - - -/** - * Tokenize the provided code. - * - * @return {Array.} Tokens. - */ -ol.expression.Lexer.prototype.tokenize = function() { - return []; -}; From 063b461ffdb05094312e97144d400a8863f321f2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sun, 9 Jun 2013 00:21:55 -0600 Subject: [PATCH 15/84] Fewer calls to charCodeAt --- src/ol/expression/lexer.js | 42 +++++++++++++-------------- test/spec/ol/expression/lexer.test.js | 8 ++--- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index e051bc3d41..05ad29d376 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -342,46 +342,46 @@ ol.expression.Lexer.prototype.next = function() { // check for common punctuation if (code === ol.expression.Char.LEFT_PAREN || code === ol.expression.Char.RIGHT_PAREN) { - return this.scanPunctuator_(); + return this.scanPunctuator_(code); } // check for string literal if (code === ol.expression.Char.SINGLE_QUOTE || code === ol.expression.Char.DOUBLE_QUOTE) { - return this.scanStringLiteral_(); + return this.scanStringLiteral_(code); } // check for identifier if (this.isIdentifierStart_(code)) { - return this.scanIdentifier_(); + return this.scanIdentifier_(code); } // check dot punctuation or decimal if (code === ol.expression.Char.DOT) { if (this.isDecimalDigit_(this.getCharCode_(1))) { - return this.scanNumericLiteral_(); + return this.scanNumericLiteral_(code); } - return this.scanPunctuator_(); + return this.scanPunctuator_(code); } // check for numeric literal if (this.isDecimalDigit_(code)) { - return this.scanNumericLiteral_(); + return this.scanNumericLiteral_(code); } // all the rest is punctuation - return this.scanPunctuator_(); + return this.scanPunctuator_(code); }; /** * Scan hex literal as numeric token. * + * @param {number} code The current character code. * @return {ol.expression.Token} Numeric literal token. * @private */ -ol.expression.Lexer.prototype.scanHexLiteral_ = function() { - var code = this.getCurrentCharCode_(); +ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { var str = ''; while (this.index_ < this.length_) { @@ -415,11 +415,11 @@ ol.expression.Lexer.prototype.scanHexLiteral_ = function() { /** * Scan identifier token. * + * @param {number} code The current character code. * @return {ol.expression.Token} Identifier token. * @private */ -ol.expression.Lexer.prototype.scanIdentifier_ = function() { - var code = this.getCurrentCharCode_(); +ol.expression.Lexer.prototype.scanIdentifier_ = function(code) { goog.asserts.assert(this.isIdentifierStart_(code), 'Must be called with a valid identifier'); @@ -460,11 +460,11 @@ ol.expression.Lexer.prototype.scanIdentifier_ = function() { * Scan numeric literal token. * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 * + * @param {number} code The current character code. * @return {ol.expression.Token} Numeric literal token. * @private */ -ol.expression.Lexer.prototype.scanNumericLiteral_ = function() { - var code = this.getCurrentCharCode_(); +ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { goog.asserts.assert( code === ol.expression.Char.DOT || this.isDecimalDigit_(code), 'Valid start for numeric literal: ' + String.fromCharCode(code)); @@ -481,13 +481,13 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function() { if (nextCode === ol.expression.Char.UPPER_X || nextCode === ol.expression.Char.LOWER_X) { this.increment_(2); - return this.scanHexLiteral_(); + return this.scanHexLiteral_(this.getCurrentCharCode_()); } // octals start with 0 if (this.isOctalDigit_(nextCode)) { this.increment_(1); - return this.scanOctalLiteral_(); + return this.scanOctalLiteral_(nextCode); } // numbers like 09 not allowed @@ -564,11 +564,11 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function() { /** * Scan octal literal as numeric token. * + * @param {number} code The current character code. * @return {ol.expression.Token} Numeric literal token. * @private */ -ol.expression.Lexer.prototype.scanOctalLiteral_ = function() { - var code = this.getCurrentCharCode_(); +ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { goog.asserts.assert(this.isOctalDigit_(code)); var str = '0' + String.fromCharCode(code); @@ -602,11 +602,11 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function() { /** * Scan punctuator token (a subset of allowed tokens in 7.7). * + * @param {number} code The current character code. * @return {ol.expression.Token} Punctuator token. * @private */ -ol.expression.Lexer.prototype.scanPunctuator_ = function() { - var code = this.getCurrentCharCode_(); +ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { // single char punctuation that also doesn't start longer punctuation // (we disallow assignment, so no += etc.) @@ -700,11 +700,11 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function() { /** * Scan string literal token. * + * @param {number} quote The current character code. * @return {ol.expression.Token} String literal token. * @private */ -ol.expression.Lexer.prototype.scanStringLiteral_ = function() { - var quote = this.getCurrentCharCode_(); +ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { goog.asserts.assert(quote === ol.expression.Char.SINGLE_QUOTE || quote === ol.expression.Char.DOUBLE_QUOTE, 'Strings must start with a quote: ' + String.fromCharCode(quote)); diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 8673b52081..435d421d37 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -95,7 +95,7 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); - return lexer.scanIdentifier_(); + return lexer.scanIdentifier_(lexer.getCurrentCharCode_()); } it('works for short identifiers', function() { @@ -182,7 +182,7 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); - return lexer.scanNumericLiteral_(); + return lexer.scanNumericLiteral_(lexer.getCurrentCharCode_()); } it('works for integers', function() { @@ -233,7 +233,7 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); - return lexer.scanPunctuator_(); + return lexer.scanPunctuator_(lexer.getCurrentCharCode_()); } it('works for dot', function() { @@ -320,7 +320,7 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); - return lexer.scanStringLiteral_(); + return lexer.scanStringLiteral_(lexer.getCurrentCharCode_()); } it('parses double quoted string', function() { From d7e0d043f398cefee8c98a6d4552137082df8efb Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sun, 9 Jun 2013 00:30:38 -0600 Subject: [PATCH 16/84] Consistently number --- src/ol/expression/lexer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 05ad29d376..84181ba211 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -747,7 +747,7 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { * @private */ ol.expression.Lexer.prototype.skipWhitespace_ = function() { - var code; + var code = NaN; while (this.index_ < this.length_) { code = this.getCurrentCharCode_(); if (this.isWhitespace_(code)) { From a87ebfe97b8843e5ec0060013546be208883e212 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 01:11:11 -0600 Subject: [PATCH 17/84] Specific expression types --- src/ol/expression/expression.js | 190 +++++++++++++++++++-- test/spec/ol/expression/expression.test.js | 73 ++++++++ 2 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 test/spec/ol/expression/expression.test.js diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index b7606fbe47..b309730135 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -1,4 +1,10 @@ -goog.provide('ol.expression'); +goog.provide('ol.expression.BooleanLiteral'); +goog.provide('ol.expression.Expression'); +goog.provide('ol.expression.Identifier'); +goog.provide('ol.expression.Not'); +goog.provide('ol.expression.NullLiteral'); +goog.provide('ol.expression.NumericLiteral'); +goog.provide('ol.expression.StringLiteral'); /** @@ -10,16 +16,178 @@ goog.provide('ol.expression'); */ + /** - * @enum {string} + * @constructor */ -ol.expression.Syntax = { - BINARY_EXPRESSION: 'BinaryExpression', - CALL_EXPRESSION: 'CallExpression', - IDENTIFIER: 'Identifier', - LITERAL: 'Literal', - LOGICAL_EXPRESSION: 'LogicalExpression', - MEMBER_EXPRESSION: 'MemberExpression', - PROPERTY: 'Property', // dot notation only - UNARY_EXPRESSION: 'UnaryExpression' // only with logical not +ol.expression.Expression = function() {}; + + +/** + * Evaluate the expression and return the result. + * + * @param {Object} scope Evaluation scope. All properties of this object + * will be available as variables when evaluating the expression. + * @return {string|number|boolean|null} Result of the expression. + */ +ol.expression.Expression.prototype.evaluate = goog.abstractMethod; + + + +/** + * A boolean literal expression (e.g. `true`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {boolean} value A boolean value. + */ +ol.expression.BooleanLiteral = function(value) { + + /** + * @type {boolean} + * @private + */ + this.value_ = value; + +}; +goog.inherits(ol.expression.BooleanLiteral, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.BooleanLiteral.prototype.evaluate = function(scope) { + return this.value_; +}; + + + +/** + * An identifier expression (e.g. `foo`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {string} name An identifier name. + */ +ol.expression.Identifier = function(name) { + + /** + * @type {string} + * @private + */ + this.name_ = name; + +}; +goog.inherits(ol.expression.Identifier, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.Identifier.prototype.evaluate = function(scope) { + return scope[this.name_]; +}; + + + +/** + * A logical not expression (e.g. `!foo`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {ol.expression.Expression} expr Expression to negate. + */ +ol.expression.Not = function(expr) { + + /** + * @type {ol.expression.Expression} + * @private + */ + this.expr_ = expr; + +}; +goog.inherits(ol.expression.Not, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.Not.prototype.evaluate = function(scope) { + return !this.expr_.evaluate(scope); +}; + + + +/** + * A numeric literal expression (e.g. `42`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {number} value A numeric value. + */ +ol.expression.NumericLiteral = function(value) { + + /** + * @type {number} + * @private + */ + this.value_ = value; + +}; +goog.inherits(ol.expression.NumericLiteral, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.NumericLiteral.prototype.evaluate = function(scope) { + return this.value_; +}; + + + +/** + * A null literal expression (i.e. `null`). + * + * @constructor + * @extends {ol.expression.Expression} + */ +ol.expression.NullLiteral = function() { +}; +goog.inherits(ol.expression.NullLiteral, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.NullLiteral.prototype.evaluate = function(scope) { + return null; +}; + + + +/** + * A string literal expression (e.g. `"chicken"`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {string} value A string. + */ +ol.expression.StringLiteral = function(value) { + + /** + * @type {string} + * @private + */ + this.value_ = value; + +}; +goog.inherits(ol.expression.StringLiteral, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.StringLiteral.prototype.evaluate = function(scope) { + return this.value_; }; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js new file mode 100644 index 0000000000..3deb416d0a --- /dev/null +++ b/test/spec/ol/expression/expression.test.js @@ -0,0 +1,73 @@ +goog.provide('ol.test.expression.Expression'); + + +describe('ol.expression.BooleanLiteral', function() { + + describe('constructor', function() { + var expr = new ol.expression.BooleanLiteral(true); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).not.to.be.a(ol.expression.BooleanLiteral); + }); + +}); + + +describe('ol.expression.Identifier', function() { + + describe('constructor', function() { + var expr = new ol.expression.Identifier('foo'); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Identifier); + }); + +}); + +describe('ol.expression.Not', function() { + + describe('constructor', function() { + var expr = new ol.expression.Not( + new ol.expression.BooleanLiteral(true)); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Not); + }); + +}); + +describe('ol.expression.NullLiteral', function() { + + describe('constructor', function() { + var expr = new ol.expression.NullLiteral(); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.NullLiteral); + }); + +}); + +describe('ol.expression.NumericLiteral', function() { + + describe('constructor', function() { + var expr = new ol.expression.NumericLiteral(42); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.NumericLiteral); + }); + +}); + +describe('ol.expression.StringLiteral', function() { + + describe('constructor', function() { + var expr = new ol.expression.StringLiteral('bar'); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.StringLiteral); + }); + +}); + + +goog.require('ol.expression.BooleanLiteral'); +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Identifier'); +goog.require('ol.expression.Not'); +goog.require('ol.expression.NullLiteral'); +goog.require('ol.expression.NumericLiteral'); +goog.require('ol.expression.StringLiteral'); From 1f68522837bc2d28d8fba69346dfaae9946d9e8c Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 01:11:44 -0600 Subject: [PATCH 18/84] Utility methods on the lexer --- src/ol/expression/lexer.js | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 84181ba211..0eef619bc5 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -1,4 +1,5 @@ goog.provide('ol.expression.Lexer'); +goog.provide('ol.expression.Token'); goog.provide('ol.expression.TokenType'); goog.require('goog.asserts'); @@ -106,6 +107,27 @@ ol.expression.Lexer = function(source) { */ this.index_ = 0; + /** + * Next character index (only set after `peek`ing). + * @type {number} + * @private + */ + this.nextIndex_ = 0; + +}; + + +/** + * Scan the next token and throw if it doesn't match. + * @param {string} value Token value. + */ +ol.expression.Lexer.prototype.expect = function(value) { + var match = this.match(value); + this.skip(); + if (!match) { + throw new Error('Unexpected token at index ' + this.index_ + + ': ' + this.getCurrentChar_()); + } }; @@ -324,6 +346,19 @@ ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { }; +/** + * Determine whether the upcoming token matches the given punctuator. + * @param {string} value Punctuator value. + * @return {boolean} The token matches. + */ +ol.expression.Lexer.prototype.match = function(value) { + var token = this.peek(); + return ( + token.type === ol.expression.TokenType.PUNCTUATOR && + token.value === value); +}; + + /** * Scan the next token. * @@ -768,6 +803,15 @@ ol.expression.Lexer.prototype.skipWhitespace_ = function() { ol.expression.Lexer.prototype.peek = function() { var currentIndex = this.index_; var token = this.next(); + this.nextIndex_ = this.index_; this.index_ = currentIndex; return token; }; + + +/** + * After peeking, skip may be called to advance the cursor without re-scanning. + */ +ol.expression.Lexer.prototype.skip = function() { + this.index_ = this.nextIndex_; +}; From ed2e21dffa710e8ce686395aa19f8590f032a8a7 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 01:12:10 -0600 Subject: [PATCH 19/84] Parsing --- src/ol/expression/parser.js | 435 ++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 src/ol/expression/parser.js diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js new file mode 100644 index 0000000000..632cf24674 --- /dev/null +++ b/src/ol/expression/parser.js @@ -0,0 +1,435 @@ +goog.provide('ol.expression.Parser'); + +goog.require('goog.asserts'); + +goog.require('ol.expression.BooleanLiteral'); +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Identifier'); +goog.require('ol.expression.Lexer'); +goog.require('ol.expression.Not'); +goog.require('ol.expression.NullLiteral'); +goog.require('ol.expression.NumericLiteral'); +goog.require('ol.expression.StringLiteral'); +goog.require('ol.expression.Token'); +goog.require('ol.expression.TokenType'); + + + +/** + * Instances of ol.expression.Parser parse a very limited set of ECMAScript + * expressions (http://www.ecma-international.org/ecma-262/5.1/#sec-11). + * + * - Primary Expression (11.1): + * - Identifier (e.g. `foo`) + * - Literal (e.g. `"some string"` or `42`) + * - Grouped (e.g. `(foo)`) + * - Left-Hand-Side Expression (11.2): + * - Property Accessors + * - Dot notation only + * - Function Calls + * - Identifier with arguments only (e.g. `foo(bar, 42)`) + * - Unary Operators (11.4) + * - Logical Not (e.g. `!foo`) + * - Multiplicitave Operators (11.5) + * - Additive Operators (11.6) + * - Relational Operators (11.8) + * - <, >, <=, and >= only + * - Equality Operators (11.9) + * - Binary Logical Operators (11.11) + * + * @constructor + */ +ol.expression.Parser = function() { +}; + + +/** + * Determine the precedence for the given token. + * + * @param {ol.expression.Token} token A token. + * @return {number} The precedence for the given token. Higher gets more + * precedence. + * @private + */ +ol.expression.Parser.prototype.binaryPrecedence_ = function(token) { + var precedence = 0; + + if (token.type !== ol.expression.TokenType.PUNCTUATOR) { + return precedence; + } + + switch (token.value) { + case '||': + precedence = 1; + break; + + case '&&': + precedence = 2; + break; + + case '|': + precedence = 3; + break; + + case '^': + precedence = 4; + break; + + case '&': + precedence = 5; + break; + + case '==': + case '!=': + case '===': + case '!==': + precedence = 6; + break; + + case '<': + case '>': + case '<=': + case '>=': + precedence = 7; + break; + + case '<<': + case '>>': + precedence = 8; + break; + + case '+': + case '-': + precedence = 9; + break; + + case '*': + case '/': + case '%': + precedence = 10; + break; + + default: + break; + } + + return precedence; +}; + + +/** + * Create a binary expression. + * + * @param {string} operator Operator. + * @param {ol.expression.Expression} left Left expression. + * @param {ol.expression.Expression} right Right expression. + * @private + */ +ol.expression.Parser.prototype.createBinaryExpression_ = function(operator, + left, right) { + throw new Error('Not implemented'); +}; + + +/** + * Create a call expression. + * + * @param {ol.expression.Identifier} expr Identifier expression for function. + * @param {Array.} args Arguments array. + * @private + */ +ol.expression.Parser.prototype.createCallExpression_ = function(expr, args) { + throw new Error('Not implemented'); +}; + + +/** + * Create an identifier expression. + * + * @param {string} name Identifier name. + * @return {ol.expression.Identifier} Identifier expression. + * @private + */ +ol.expression.Parser.prototype.createIdentifier_ = function(name) { + return new ol.expression.Identifier(name); +}; + + +/** + * Create a literal expression. + * + * @param {string|number|boolean|null} value Literal value. + * @return {ol.expression.StringLiteral|ol.expression.NumericLiteral| + * ol.expression.BooleanLiteral|ol.expression.NullLiteral} The + * expression. + * @private + */ +ol.expression.Parser.prototype.createLiteral_ = function(value) { + var expr; + if (goog.isString(value)) { + expr = new ol.expression.StringLiteral(value); + } else if (goog.isNumber(value)) { + expr = new ol.expression.NumericLiteral(value); + } else if (goog.isBoolean(value)) { + expr = new ol.expression.BooleanLiteral(value); + } else { + goog.asserts.assert(goog.isNull(value)); + expr = new ol.expression.NullLiteral(); + } + return expr; +}; + + +/** + * Create a member expression. + * + * // TODO: make exp {ol.expression.Member|ol.expression.Identifier} + * @param {ol.expression.Expression} expr Expression. + * @param {ol.expression.Identifier} property Member name. + * @private + */ +ol.expression.Parser.prototype.createMemberExpression_ = function(expr, + property) { + throw new Error('Not implemented'); +}; + + +/** + * Create a unary expression. + * + * @param {string} op Operator. + * @param {ol.expression.Expression} expr Expression. + * @return {ol.expression.Not} The logical not of the input expression. + * @private + */ +ol.expression.Parser.prototype.createUnaryExpression_ = function(op, expr) { + goog.asserts.assert(op === '!'); + return new ol.expression.Not(expr); +}; + + +/** + * Parse an expression. + * + * @param {string} source Expression source. + * @return {ol.expression.Expression} Expression. + */ +ol.expression.Parser.prototype.parse = function(source) { + var lexer = new ol.expression.Lexer(source); + return this.parseExpression_(lexer); +}; + + +/** + * Parse call arguments + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.4 + * + * @param {ol.expression.Lexer} lexer Lexer. + * @return {Array.} Arguments. + * @private + */ +ol.expression.Parser.prototype.parseArguments_ = function(lexer) { + var args = []; + + lexer.expect('('); + + while (!lexer.match(')')) { + args.push(this.parseBinaryExpression_(lexer)); + if (lexer.match(')')) { + break; + } + lexer.expect(','); + } + lexer.skip(); + + return args; +}; + + +/** + * Parse a binary expression. + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.11 + * + * @param {ol.expression.Lexer} lexer Lexer. + * @return {ol.expression.Expression} Expression. + * @private + */ +ol.expression.Parser.prototype.parseBinaryExpression_ = function(lexer) { + var left = this.parseUnaryExpression_(lexer); + + var operator = lexer.peek(); + var precedence = this.binaryPrecedence_(operator); + if (precedence === 0) { + // not a binary operator + return left; + } + lexer.skip(); + + var right = this.parseUnaryExpression_(lexer); + var stack = [left, operator, right]; + + operator = lexer.peek(); + precedence = this.binaryPrecedence_(operator); + while (precedence > 0) { + // TODO: cache operator precedence in stack + while (stack.length > 2 && + (precedence <= this.binaryPrecedence_(stack[stack.length - 2]))) { + right = stack.pop(); + operator = stack.pop(); + left = stack.pop(); + stack.push(this.createBinaryExpression_(operator.value, left, right)); + } + lexer.skip(); + operator = lexer.peek(); + precedence = this.binaryPrecedence_(operator); + stack.push(operator); + stack.push(this.parseUnaryExpression_(lexer)); + } + + var i = stack.length - 1; + var expr = stack[i]; + while (i > 1) { + expr = this.createBinaryExpression_(stack[i - 1].value, stack[i - 2], expr); + i -= 2; + } + + return expr; +}; + + +/** + * Parse a group expression. + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.1.6 + * + * @param {ol.expression.Lexer} lexer Lexer. + * @return {ol.expression.Expression} Expression. + * @private + */ +ol.expression.Parser.prototype.parseGroupExpression_ = function(lexer) { + lexer.expect('('); + var expr = this.parseExpression_(lexer); + lexer.expect(')'); + return expr; +}; + + +/** + * Parse left-hand-side expression. + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 + * + * @param {ol.expression.Lexer} lexer Lexer. + * @return {ol.expression.Expression} Expression. + * @private + */ +ol.expression.Parser.prototype.parseLeftHandSideExpression_ = function(lexer) { + var expr = this.parsePrimaryExpression_(lexer); + var token = lexer.peek(); + if (token.value === '(') { + // only allow calls on identifiers (e.g. `foo()` not `foo.bar()`) + if (!(expr instanceof ol.expression.Identifier)) { + // TODO: token.index + // TODO: more helpful error messages for restricted syntax + throw new Error('Unexpected token: ('); + } + var args = this.parseArguments_(lexer); + expr = this.createCallExpression_(expr, args); + } else { + // TODO: throw if not Identifier + while (token.value === '.') { + var property = this.parseNonComputedMember_(lexer); + expr = this.createMemberExpression_(expr, property); + } + } + return expr; +}; + + +/** + * Parse non-computed member. + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 + * + * @param {ol.expression.Lexer} lexer Lexer. + * @return {ol.expression.Identifier} Expression. + * @private + */ +ol.expression.Parser.prototype.parseNonComputedMember_ = function(lexer) { + lexer.expect('.'); + + var token = lexer.next(); + if (token.type !== ol.expression.TokenType.IDENTIFIER && + token.type !== ol.expression.TokenType.KEYWORD && + token.type !== ol.expression.TokenType.BOOLEAN_LITERAL && + token.type !== ol.expression.TokenType.NULL_LITERAL) { + // TODO: token.index + throw new Error('Unexpected token: ' + token.value); + } + + return this.createIdentifier_(String(token.value)); +}; + + +/** + * Parse primary expression. + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.1 + * + * @param {ol.expression.Lexer} lexer Lexer. + * @return {ol.expression.Expression} Expression. + * @private + */ +ol.expression.Parser.prototype.parsePrimaryExpression_ = function(lexer) { + var token = lexer.peek(); + if (token.value === '(') { + return this.parseGroupExpression_(lexer); + } + lexer.skip(); + var expr; + var type = token.type; + if (type === ol.expression.TokenType.IDENTIFIER) { + expr = this.createIdentifier_(/** @type {string} */ (token.value)); + } else if (type === ol.expression.TokenType.STRING_LITERAL || + type === ol.expression.TokenType.NUMERIC_LITERAL || + type === ol.expression.TokenType.BOOLEAN_LITERAL || + type === ol.expression.TokenType.NULL_LITERAL) { + expr = this.createLiteral_(token.value); + } else { + throw new Error('Unexpected token: ' + token.value); + } + return expr; +}; + + +/** + * Parse expression with a unary operator. + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.4 + * + * @param {ol.expression.Lexer} lexer Lexer. + * @return {ol.expression.Expression} Expression. + * @private + */ +ol.expression.Parser.prototype.parseUnaryExpression_ = function(lexer) { + var expr; + var operator = lexer.peek(); + if (operator.type !== ol.expression.TokenType.PUNCTUATOR) { + expr = this.parseLeftHandSideExpression_(lexer); + } else if (operator.value === '!') { + lexer.skip(); + expr = this.parseUnaryExpression_(lexer); + expr = this.createUnaryExpression_('!', expr); + } else { + // TODO: add token.index + throw new Error('Unexpected token: ' + operator.value); + } + return expr; +}; + + +/** + * Parse an expression. + * + * @param {ol.expression.Lexer} lexer Lexer. + * @return {ol.expression.Expression} Expression. + * @private + */ +ol.expression.Parser.prototype.parseExpression_ = function(lexer) { + return this.parseBinaryExpression_(lexer); +}; From 0abcbb9854c1d79826dd1d9c6453d5b5bad55a34 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 01:38:53 -0600 Subject: [PATCH 20/84] Stray not --- test/spec/ol/expression/expression.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 3deb416d0a..c709ed80a8 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -6,7 +6,7 @@ describe('ol.expression.BooleanLiteral', function() { describe('constructor', function() { var expr = new ol.expression.BooleanLiteral(true); expect(expr).to.be.a(ol.expression.Expression); - expect(expr).not.to.be.a(ol.expression.BooleanLiteral); + expect(expr).to.be.a(ol.expression.BooleanLiteral); }); }); From 20b66fc4470695e745cb645ec84c24d0815d610a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 01:46:18 -0600 Subject: [PATCH 21/84] Proper specs --- test/spec/ol/expression/expression.test.js | 50 ++++++++++++++-------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index c709ed80a8..60a2838abc 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -4,9 +4,11 @@ goog.provide('ol.test.expression.Expression'); describe('ol.expression.BooleanLiteral', function() { describe('constructor', function() { - var expr = new ol.expression.BooleanLiteral(true); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.BooleanLiteral); + it('creates a new expression', function() { + var expr = new ol.expression.BooleanLiteral(true); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.BooleanLiteral); + }); }); }); @@ -15,9 +17,11 @@ describe('ol.expression.BooleanLiteral', function() { describe('ol.expression.Identifier', function() { describe('constructor', function() { - var expr = new ol.expression.Identifier('foo'); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.Identifier); + it('creates a new expression', function() { + var expr = new ol.expression.Identifier('foo'); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Identifier); + }); }); }); @@ -25,10 +29,12 @@ describe('ol.expression.Identifier', function() { describe('ol.expression.Not', function() { describe('constructor', function() { - var expr = new ol.expression.Not( - new ol.expression.BooleanLiteral(true)); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.Not); + it('creates a new expression', function() { + var expr = new ol.expression.Not( + new ol.expression.BooleanLiteral(true)); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Not); + }); }); }); @@ -36,9 +42,11 @@ describe('ol.expression.Not', function() { describe('ol.expression.NullLiteral', function() { describe('constructor', function() { - var expr = new ol.expression.NullLiteral(); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.NullLiteral); + it('creates a new expression', function() { + var expr = new ol.expression.NullLiteral(); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.NullLiteral); + }); }); }); @@ -46,9 +54,11 @@ describe('ol.expression.NullLiteral', function() { describe('ol.expression.NumericLiteral', function() { describe('constructor', function() { - var expr = new ol.expression.NumericLiteral(42); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.NumericLiteral); + it('creates a new expression', function() { + var expr = new ol.expression.NumericLiteral(42); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.NumericLiteral); + }); }); }); @@ -56,9 +66,11 @@ describe('ol.expression.NumericLiteral', function() { describe('ol.expression.StringLiteral', function() { describe('constructor', function() { - var expr = new ol.expression.StringLiteral('bar'); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.StringLiteral); + it('creates a new expression', function() { + var expr = new ol.expression.StringLiteral('bar'); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.StringLiteral); + }); }); }); From 052b973b39c1aa71137b21b8afd9b79d17ce4f59 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 10:05:10 -0600 Subject: [PATCH 22/84] Reduce to a single literal --- src/ol/expression/expression.js | 138 +++++---------------- src/ol/expression/parser.js | 22 +--- test/spec/ol/expression/expression.test.js | 128 ++++++++++++------- 3 files changed, 117 insertions(+), 171 deletions(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index b309730135..cee19e79c3 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -1,10 +1,7 @@ -goog.provide('ol.expression.BooleanLiteral'); goog.provide('ol.expression.Expression'); goog.provide('ol.expression.Identifier'); +goog.provide('ol.expression.Literal'); goog.provide('ol.expression.Not'); -goog.provide('ol.expression.NullLiteral'); -goog.provide('ol.expression.NumericLiteral'); -goog.provide('ol.expression.StringLiteral'); /** @@ -28,40 +25,12 @@ ol.expression.Expression = function() {}; * * @param {Object} scope Evaluation scope. All properties of this object * will be available as variables when evaluating the expression. - * @return {string|number|boolean|null} Result of the expression. + * @return {*} Result of the expression. */ ol.expression.Expression.prototype.evaluate = goog.abstractMethod; -/** - * A boolean literal expression (e.g. `true`). - * - * @constructor - * @extends {ol.expression.Expression} - * @param {boolean} value A boolean value. - */ -ol.expression.BooleanLiteral = function(value) { - - /** - * @type {boolean} - * @private - */ - this.value_ = value; - -}; -goog.inherits(ol.expression.BooleanLiteral, ol.expression.Expression); - - -/** - * @inheritDoc - */ -ol.expression.BooleanLiteral.prototype.evaluate = function(scope) { - return this.value_; -}; - - - /** * An identifier expression (e.g. `foo`). * @@ -90,6 +59,34 @@ ol.expression.Identifier.prototype.evaluate = function(scope) { +/** + * A literal expression (e.g. `"chicken"`, `42`, `true`, `null`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {string|number|boolean|null} value A literal value. + */ +ol.expression.Literal = function(value) { + + /** + * @type {string|number|boolean|null} + * @private + */ + this.value_ = value; + +}; +goog.inherits(ol.expression.Literal, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.Literal.prototype.evaluate = function(scope) { + return this.value_; +}; + + + /** * A logical not expression (e.g. `!foo`). * @@ -116,78 +113,3 @@ ol.expression.Not.prototype.evaluate = function(scope) { return !this.expr_.evaluate(scope); }; - - -/** - * A numeric literal expression (e.g. `42`). - * - * @constructor - * @extends {ol.expression.Expression} - * @param {number} value A numeric value. - */ -ol.expression.NumericLiteral = function(value) { - - /** - * @type {number} - * @private - */ - this.value_ = value; - -}; -goog.inherits(ol.expression.NumericLiteral, ol.expression.Expression); - - -/** - * @inheritDoc - */ -ol.expression.NumericLiteral.prototype.evaluate = function(scope) { - return this.value_; -}; - - - -/** - * A null literal expression (i.e. `null`). - * - * @constructor - * @extends {ol.expression.Expression} - */ -ol.expression.NullLiteral = function() { -}; -goog.inherits(ol.expression.NullLiteral, ol.expression.Expression); - - -/** - * @inheritDoc - */ -ol.expression.NullLiteral.prototype.evaluate = function(scope) { - return null; -}; - - - -/** - * A string literal expression (e.g. `"chicken"`). - * - * @constructor - * @extends {ol.expression.Expression} - * @param {string} value A string. - */ -ol.expression.StringLiteral = function(value) { - - /** - * @type {string} - * @private - */ - this.value_ = value; - -}; -goog.inherits(ol.expression.StringLiteral, ol.expression.Expression); - - -/** - * @inheritDoc - */ -ol.expression.StringLiteral.prototype.evaluate = function(scope) { - return this.value_; -}; diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 632cf24674..33de610eb0 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -2,14 +2,11 @@ goog.provide('ol.expression.Parser'); goog.require('goog.asserts'); -goog.require('ol.expression.BooleanLiteral'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Lexer'); +goog.require('ol.expression.Literal'); goog.require('ol.expression.Not'); -goog.require('ol.expression.NullLiteral'); -goog.require('ol.expression.NumericLiteral'); -goog.require('ol.expression.StringLiteral'); goog.require('ol.expression.Token'); goog.require('ol.expression.TokenType'); @@ -159,24 +156,11 @@ ol.expression.Parser.prototype.createIdentifier_ = function(name) { * Create a literal expression. * * @param {string|number|boolean|null} value Literal value. - * @return {ol.expression.StringLiteral|ol.expression.NumericLiteral| - * ol.expression.BooleanLiteral|ol.expression.NullLiteral} The - * expression. + * @return {ol.expression.Literal} The literal expression. * @private */ ol.expression.Parser.prototype.createLiteral_ = function(value) { - var expr; - if (goog.isString(value)) { - expr = new ol.expression.StringLiteral(value); - } else if (goog.isNumber(value)) { - expr = new ol.expression.NumericLiteral(value); - } else if (goog.isBoolean(value)) { - expr = new ol.expression.BooleanLiteral(value); - } else { - goog.asserts.assert(goog.isNull(value)); - expr = new ol.expression.NullLiteral(); - } - return expr; + return new ol.expression.Literal(value); }; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 60a2838abc..99fbc234f9 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -1,19 +1,6 @@ goog.provide('ol.test.expression.Expression'); -describe('ol.expression.BooleanLiteral', function() { - - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.BooleanLiteral(true); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.BooleanLiteral); - }); - }); - -}); - - describe('ol.expression.Identifier', function() { describe('constructor', function() { @@ -24,62 +11,115 @@ describe('ol.expression.Identifier', function() { }); }); + describe('#evaluate()', function() { + it('returns a number from the scope', function() { + var expr = new ol.expression.Identifier('foo'); + expect(expr.evaluate({foo: 42})).to.be(42); + }); + + it('returns a string from the scope', function() { + var expr = new ol.expression.Identifier('foo'); + expect(expr.evaluate({foo: 'chicken'})).to.be('chicken'); + }); + + it('returns a boolean from the scope', function() { + var expr = new ol.expression.Identifier('bar'); + expect(expr.evaluate({bar: false})).to.be(false); + expect(expr.evaluate({bar: true})).to.be(true); + }); + + it('returns a null from the scope', function() { + var expr = new ol.expression.Identifier('nada'); + expect(expr.evaluate({nada: null})).to.be(null); + }); + + it('works for unicode identifiers', function() { + var expr = new ol.expression.Identifier('\u03c0'); + expect(expr.evaluate({'\u03c0': Math.PI})).to.be(Math.PI); + }); + }); + }); +describe('ol.expression.Literal', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expression.Literal(true); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Literal); + }); + }); + + describe('#evaluate()', function() { + it('works for numeric literal', function() { + var expr = new ol.expression.Literal(42e-11); + expect(expr.evaluate({})).to.be(4.2e-10); + }); + + it('works for string literal', function() { + var expr = new ol.expression.Literal('asdf'); + expect(expr.evaluate({})).to.be('asdf'); + }); + + it('works for boolean literal', function() { + var expr = new ol.expression.Literal(true); + expect(expr.evaluate({})).to.be(true); + }); + + it('works for null literal', function() { + var expr = new ol.expression.Literal(null); + expect(expr.evaluate({})).to.be(null); + }); + }); +}); + + describe('ol.expression.Not', function() { describe('constructor', function() { it('creates a new expression', function() { var expr = new ol.expression.Not( - new ol.expression.BooleanLiteral(true)); + new ol.expression.Literal(true)); expect(expr).to.be.a(ol.expression.Expression); expect(expr).to.be.a(ol.expression.Not); }); }); -}); + describe('#evaluate()', function() { + it('returns the logical complement', function() { + var expr = new ol.expression.Not(new ol.expression.Literal(true)); + expect(expr.evaluate({})).to.be(false); -describe('ol.expression.NullLiteral', function() { - - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.NullLiteral(); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.NullLiteral); + expr = new ol.expression.Not(new ol.expression.Literal(false)); + expect(expr.evaluate({})).to.be(true); }); - }); -}); - -describe('ol.expression.NumericLiteral', function() { - - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.NumericLiteral(42); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.NumericLiteral); + it('negates a truthy string', function() { + var expr = new ol.expression.Not(new ol.expression.Literal('asdf')); + expect(expr.evaluate({})).to.be(false); }); - }); -}); + it('negates a falsy string', function() { + var expr = new ol.expression.Not(new ol.expression.Literal('')); + expect(expr.evaluate({})).to.be(true); + }); -describe('ol.expression.StringLiteral', function() { + it('negates a truthy number', function() { + var expr = new ol.expression.Not(new ol.expression.Literal(42)); + expect(expr.evaluate({})).to.be(false); + }); - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.StringLiteral('bar'); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.StringLiteral); + it('negates a falsy number', function() { + var expr = new ol.expression.Not(new ol.expression.Literal(NaN)); + expect(expr.evaluate({})).to.be(true); }); }); }); -goog.require('ol.expression.BooleanLiteral'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); +goog.require('ol.expression.Literal'); goog.require('ol.expression.Not'); -goog.require('ol.expression.NullLiteral'); -goog.require('ol.expression.NumericLiteral'); -goog.require('ol.expression.StringLiteral'); From 7800e9b0cc1508a1b33c21cc4ce981434c8f6fea Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 10:51:37 -0600 Subject: [PATCH 23/84] Comments, links, copyrights --- src/ol/expression/expression.js | 16 +++++++--------- src/ol/expression/lexer.js | 21 +++++++++++++++++++-- src/ol/expression/parser.js | 15 +++++++++++++++ 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index cee19e79c3..c8f008a0b7 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -4,17 +4,15 @@ goog.provide('ol.expression.Literal'); goog.provide('ol.expression.Not'); + /** - * Support an extremely limited subset of ECMAScript 5.1 - * http://www.ecma-international.org/ecma-262/5.1/ + * Base class for all expressions. Instances of ol.Expression correspond to + * a limited set of ECMAScript 5.1 expressions. + * http://www.ecma-international.org/ecma-262/5.1/#sec-11 + * + * This base class should not be constructed directly. Instead, use one of + * the subclass constructors. * - * Inspired by Esprima (https://github.com/ariya/esprima) - * BSD Licensed - */ - - - -/** * @constructor */ ol.expression.Expression = function() {}; diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 0eef619bc5..cefb950cff 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -1,3 +1,18 @@ +/** + * The logic and naming of methods here are inspired by Esprima (BSD Licensed). + * Esprima (http://esprima.org) includes the following copyright notices: + * + * Copyright (C) 2013 Ariya Hidayat + * Copyright (C) 2013 Thaddee Tyl + * Copyright (C) 2012 Ariya Hidayat + * Copyright (C) 2012 Mathias Bynens + * Copyright (C) 2012 Joost-Wim Boekesteijn + * Copyright (C) 2012 Kris Kowal + * Copyright (C) 2012 Yusuke Suzuki + * Copyright (C) 2012 Arpad Borsos + * Copyright (C) 2011 Ariya Hidayat + */ + goog.provide('ol.expression.Lexer'); goog.provide('ol.expression.Token'); goog.provide('ol.expression.TokenType'); @@ -79,7 +94,8 @@ ol.expression.Token; /** - * Lexer constructor. + * Lexer constructor. Provides a tokenizer for a limited subset of ECMAScript + * 5.1 expressions (http://www.ecma-international.org/ecma-262/5.1/#sec-11). * * @constructor * @param {string} source Source code. @@ -226,9 +242,10 @@ ol.expression.Lexer.prototype.isIdentifierStart_ = function(code) { /** * Determine if the given identifier is an ECMAScript keyword. These cannot * be used as identifiers in programs. There is no real reason these could not - * be used in ol expressions - so it might be worth allowing them. + * be used in ol expressions - but they are reserved for future use. * * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.1 + * * @param {string} id Identifier. * @return {boolean} The identifier is a keyword. * @private diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 33de610eb0..ceb53d7f21 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -1,3 +1,18 @@ +/** + * The logic and naming of methods here are inspired by Esprima (BSD Licensed). + * Esprima (http://esprima.org) includes the following copyright notices: + * + * Copyright (C) 2013 Ariya Hidayat + * Copyright (C) 2013 Thaddee Tyl + * Copyright (C) 2012 Ariya Hidayat + * Copyright (C) 2012 Mathias Bynens + * Copyright (C) 2012 Joost-Wim Boekesteijn + * Copyright (C) 2012 Kris Kowal + * Copyright (C) 2012 Yusuke Suzuki + * Copyright (C) 2012 Arpad Borsos + * Copyright (C) 2011 Ariya Hidayat + */ + goog.provide('ol.expression.Parser'); goog.require('goog.asserts'); From c4867bafec58a0c71f7471be1a25bafdc061ea7b Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 10:53:01 -0600 Subject: [PATCH 24/84] Smaller subset of binary operators --- src/ol/expression/parser.js | 58 ++++++++++++++----------------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index ceb53d7f21..43da60aa77 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -65,7 +65,6 @@ ol.expression.Parser = function() { */ ol.expression.Parser.prototype.binaryPrecedence_ = function(token) { var precedence = 0; - if (token.type !== ol.expression.TokenType.PUNCTUATOR) { return precedence; } @@ -74,53 +73,24 @@ ol.expression.Parser.prototype.binaryPrecedence_ = function(token) { case '||': precedence = 1; break; - case '&&': precedence = 2; break; - - case '|': + case '==': case '!=': case '===': case '!==': precedence = 3; break; - - case '^': + case '<': case '>': case '<=': case '>=': precedence = 4; break; - - case '&': + case '<<': case '>>': precedence = 5; break; - - case '==': - case '!=': - case '===': - case '!==': + case '+': case '-': precedence = 6; break; - - case '<': - case '>': - case '<=': - case '>=': + case '*': case '/': case '%': precedence = 7; break; - - case '<<': - case '>>': - precedence = 8; - break; - - case '+': - case '-': - precedence = 9; - break; - - case '*': - case '/': - case '%': - precedence = 10; - break; - default: break; } @@ -246,8 +216,22 @@ ol.expression.Parser.prototype.parseArguments_ = function(lexer) { /** - * Parse a binary expression. - * http://www.ecma-international.org/ecma-262/5.1/#sec-11.11 + * Parse a binary expression. Supported binary expressions: + * + * - Multiplicative Operators (`*`, `/`, `%`) + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.5 + + * - Additive Operators (`+`, `-`) + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.6 + * + * - Relational Operators (`<`, `>`, `<=`, `>=`) + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.8 + * + * - Equality Operators (`==`, `!=`, `===`, `!==`) + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.9 + * + * - Binary Logical Operators (`&&`, `||`) + * http://www.ecma-international.org/ecma-262/5.1/#sec-11.11 * * @param {ol.expression.Lexer} lexer Lexer. * @return {ol.expression.Expression} Expression. From b2ff793ea16488359dbf9532c800b3f008e41d5f Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 11:52:58 -0600 Subject: [PATCH 25/84] Comparison expressions --- src/ol/expression/expression.js | 91 +++++++++++++++++ test/spec/ol/expression/expression.test.js | 111 +++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index c8f008a0b7..5015345afc 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -1,3 +1,5 @@ +goog.provide('ol.expression.Comparison'); +goog.provide('ol.expression.ComparisonOp'); goog.provide('ol.expression.Expression'); goog.provide('ol.expression.Identifier'); goog.provide('ol.expression.Literal'); @@ -28,6 +30,95 @@ ol.expression.Expression = function() {}; ol.expression.Expression.prototype.evaluate = goog.abstractMethod; +/** + * @enum {string} + */ +ol.expression.ComparisonOp = { + EQ: '==', + NEQ: '!=', + STRICT_EQ: '===', + STRICT_NEQ: '!==', + GT: '>', + LT: '<', + GTE: '>=', + LTE: '<=' +}; + + + +/** + * A comparison expression (e.g. `foo >= 42`, `bar != "chicken"`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {ol.expression.ComparisonOp} operator Comparison operator. + * @param {ol.expression.Expression} left Left expression. + * @param {ol.expression.Expression} right Right expression. + */ +ol.expression.Comparison = function(operator, left, right) { + + /** + * @type {ol.expression.ComparisonOp} + * @private + */ + this.operator_ = operator; + + /** + * @type {ol.expression.Expression} + * @private + */ + this.left_ = left; + + /** + * @type {ol.expression.Expression} + * @private + */ + this.right_ = right; + +}; +goog.inherits(ol.expression.Comparison, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.Comparison.prototype.evaluate = function(scope) { + var result; + var rightVal = this.right_.evaluate(scope); + var leftVal = this.left_.evaluate(scope); + + switch (this.operator_) { + case ol.expression.ComparisonOp.EQ: + result = leftVal == rightVal; + break; + case ol.expression.ComparisonOp.NEQ: + result = leftVal != rightVal; + break; + case ol.expression.ComparisonOp.STRICT_EQ: + result = leftVal === rightVal; + break; + case ol.expression.ComparisonOp.STRICT_NEQ: + result = leftVal !== rightVal; + break; + case ol.expression.ComparisonOp.GT: + result = leftVal > rightVal; + break; + case ol.expression.ComparisonOp.LT: + result = leftVal < rightVal; + break; + case ol.expression.ComparisonOp.GTE: + result = leftVal >= rightVal; + break; + case ol.expression.ComparisonOp.LTE: + result = leftVal <= rightVal; + break; + default: + throw new Error('Unsupported comparison operator: ' + this.operator_); + } + return result; +}; + + /** * An identifier expression (e.g. `foo`). diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 99fbc234f9..f9c579cc13 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -1,6 +1,115 @@ goog.provide('ol.test.expression.Expression'); +describe('ol.expression.Comparison', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.EQ, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Comparison); + }); + }); + + describe('#evaluate()', function() { + it('compares with ==', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.EQ, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: '42'})).to.be(true); + expect(expr.evaluate({foo: true})).to.be(false); + expect(expr.evaluate({bar: true})).to.be(false); + }); + + it('compares with !=', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.NEQ, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: '42'})).to.be(false); + expect(expr.evaluate({foo: true})).to.be(true); + expect(expr.evaluate({bar: true})).to.be(true); + }); + + it('compares with ===', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.STRICT_EQ, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: '42'})).to.be(false); + expect(expr.evaluate({foo: true})).to.be(false); + expect(expr.evaluate({bar: true})).to.be(false); + }); + + it('compares with !==', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.STRICT_NEQ, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: '42'})).to.be(true); + expect(expr.evaluate({foo: true})).to.be(true); + expect(expr.evaluate({bar: true})).to.be(true); + }); + + it('compares with >', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.GT, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: 41})).to.be(false); + expect(expr.evaluate({foo: 43})).to.be(true); + }); + + it('compares with <', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.LT, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: 41})).to.be(true); + expect(expr.evaluate({foo: 43})).to.be(false); + }); + + it('compares with >=', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.GTE, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: 41})).to.be(false); + expect(expr.evaluate({foo: 43})).to.be(true); + }); + + it('compares with <=', function() { + var expr = new ol.expression.Comparison( + ol.expression.ComparisonOp.LTE, + new ol.expression.Identifier('foo'), + new ol.expression.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: 41})).to.be(true); + expect(expr.evaluate({foo: 43})).to.be(false); + }); + }); + +}); + describe('ol.expression.Identifier', function() { describe('constructor', function() { @@ -119,6 +228,8 @@ describe('ol.expression.Not', function() { }); +goog.require('ol.expression.Comparison'); +goog.require('ol.expression.ComparisonOp'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Literal'); From 153df45f956f80becf076687e7a275361a062f03 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 12:06:31 -0600 Subject: [PATCH 26/84] Math expressions Just simple binary type expressions supported here. These can be serialized in a variety of formats. More complex operations to be supported by call expressions. --- src/ol/expression/expression.js | 84 +++++++++++++++++ test/spec/ol/expression/expression.test.js | 100 +++++++++++++++++++++ 2 files changed, 184 insertions(+) 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'); From f050546fe26e8484f20590d49607b08182936cc3 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 12:14:00 -0600 Subject: [PATCH 27/84] Logical expressions --- src/ol/expression/expression.js | 64 ++++++++++++++++++++++ test/spec/ol/expression/expression.test.js | 43 +++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index 12bb880a35..51e3aaab52 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.Logical'); +goog.provide('ol.expression.LogicalOp'); goog.provide('ol.expression.Math'); goog.provide('ol.expression.MathOp'); goog.provide('ol.expression.Not'); @@ -177,6 +179,68 @@ ol.expression.Literal.prototype.evaluate = function(scope) { }; +/** + * @enum {string} + */ +ol.expression.LogicalOp = { + AND: '&&', + OR: '||' +}; + + + +/** + * A binary logical expression (e.g. `foo && bar`, `bar || "chicken"`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {ol.expression.LogicalOp} operator Logical operator. + * @param {ol.expression.Expression} left Left expression. + * @param {ol.expression.Expression} right Right expression. + */ +ol.expression.Logical = function(operator, left, right) { + + /** + * @type {ol.expression.LogicalOp} + * @private + */ + this.operator_ = operator; + + /** + * @type {ol.expression.Expression} + * @private + */ + this.left_ = left; + + /** + * @type {ol.expression.Expression} + * @private + */ + this.right_ = right; + +}; +goog.inherits(ol.expression.Logical, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.Logical.prototype.evaluate = function(scope) { + var result; + var rightVal = this.right_.evaluate(scope); + var leftVal = this.left_.evaluate(scope); + + if (this.operator_ === ol.expression.LogicalOp.AND) { + result = leftVal && rightVal; + } else if (this.operator_ === ol.expression.LogicalOp.OR) { + result = leftVal || rightVal; + } else { + throw new Error('Unsupported logical operator: ' + this.operator_); + } + return result; +}; + + /** * @enum {string} */ diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index f9297b72a4..b88ea17f3c 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -184,6 +184,47 @@ describe('ol.expression.Literal', function() { }); +describe('ol.expression.Logical', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expression.Logical( + ol.expression.LogicalOp.OR, + new ol.expression.Identifier('foo'), + new ol.expression.Identifier('bar')); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Logical); + }); + }); + + describe('#evaluate()', function() { + it('applies || to resolved identifiers', function() { + var expr = new ol.expression.Logical( + ol.expression.LogicalOp.OR, + new ol.expression.Identifier('foo'), + new ol.expression.Identifier('bar')); + + expect(expr.evaluate({foo: true, bar: true})).to.be(true); + expect(expr.evaluate({foo: true, bar: false})).to.be(true); + expect(expr.evaluate({foo: false, bar: true})).to.be(true); + expect(expr.evaluate({foo: false, bar: false})).to.be(false); + }); + + it('applies && to resolved identifiers', function() { + var expr = new ol.expression.Logical( + ol.expression.LogicalOp.AND, + new ol.expression.Identifier('foo'), + new ol.expression.Identifier('bar')); + + expect(expr.evaluate({foo: true, bar: true})).to.be(true); + expect(expr.evaluate({foo: true, bar: false})).to.be(false); + expect(expr.evaluate({foo: false, bar: true})).to.be(false); + expect(expr.evaluate({foo: false, bar: false})).to.be(false); + }); + }); + +}); + describe('ol.expression.Math', function() { describe('constructor', function() { @@ -331,6 +372,8 @@ goog.require('ol.expression.ComparisonOp'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Literal'); +goog.require('ol.expression.Logical'); +goog.require('ol.expression.LogicalOp'); goog.require('ol.expression.Math'); goog.require('ol.expression.MathOp'); goog.require('ol.expression.Not'); From 2f7e74ab35a866b6a02583e9a6b7d2cd0a86fc74 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 12:18:46 -0600 Subject: [PATCH 28/84] Create binary expressions when parsing --- src/ol/expression/parser.js | 52 +++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 43da60aa77..a7d85db958 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -17,10 +17,16 @@ goog.provide('ol.expression.Parser'); goog.require('goog.asserts'); +goog.require('ol.expression.Comparison'); +goog.require('ol.expression.ComparisonOp'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Lexer'); goog.require('ol.expression.Literal'); +goog.require('ol.expression.Logical'); +goog.require('ol.expression.LogicalOp'); +goog.require('ol.expression.Math'); +goog.require('ol.expression.MathOp'); goog.require('ol.expression.Not'); goog.require('ol.expression.Token'); goog.require('ol.expression.TokenType'); @@ -70,28 +76,35 @@ ol.expression.Parser.prototype.binaryPrecedence_ = function(token) { } switch (token.value) { - case '||': + case ol.expression.LogicalOp.OR: precedence = 1; break; - case '&&': + case ol.expression.LogicalOp.AND: precedence = 2; break; - case '==': case '!=': case '===': case '!==': + case ol.expression.ComparisonOp.EQ: + case ol.expression.ComparisonOp.NEQ: + case ol.expression.ComparisonOp.STRICT_EQ: + case ol.expression.ComparisonOp.STRICT_NEQ: precedence = 3; break; - case '<': case '>': case '<=': case '>=': + case ol.expression.ComparisonOp.GT: + case ol.expression.ComparisonOp.LT: + case ol.expression.ComparisonOp.GTE: + case ol.expression.ComparisonOp.LTE: precedence = 4; break; - case '<<': case '>>': + case ol.expression.MathOp.ADD: + case ol.expression.MathOp.SUBTRACT: precedence = 5; break; - case '+': case '-': + case ol.expression.MathOp.MULTIPLY: + case ol.expression.MathOp.DIVIDE: + case ol.expression.MathOp.MOD: precedence = 6; break; - case '*': case '/': case '%': - precedence = 7; - break; default: + // punctuator is not a supported binary operator, that's fine break; } @@ -105,11 +118,28 @@ ol.expression.Parser.prototype.binaryPrecedence_ = function(token) { * @param {string} operator Operator. * @param {ol.expression.Expression} left Left expression. * @param {ol.expression.Expression} right Right expression. + * @return {ol.expression.Expression} The expression. * @private */ ol.expression.Parser.prototype.createBinaryExpression_ = function(operator, left, right) { - throw new Error('Not implemented'); + var expr; + if (ol.expression.ComparisonOp.hasOwnProperty(operator)) { + expr = new ol.expression.Comparison( + /** @type {ol.expression.ComparisonOp.} */ (operator), + left, right); + } else if (ol.expression.LogicalOp.hasOwnProperty(operator)) { + expr = new ol.expression.Logical( + /** @type {ol.expression.LogicalOp.} */ (operator), + left, right); + } else if (ol.expression.MathOp.hasOwnProperty(operator)) { + expr = new ol.expression.Math( + /** @type {ol.expression.MathOp.} */ (operator), + left, right); + } else { + throw new Error('Unsupported binary operator: ' + operator); + } + return expr; }; @@ -243,7 +273,7 @@ ol.expression.Parser.prototype.parseBinaryExpression_ = function(lexer) { var operator = lexer.peek(); var precedence = this.binaryPrecedence_(operator); if (precedence === 0) { - // not a binary operator + // not a supported binary operator return left; } lexer.skip(); From fbb028e15e85e78806884c9b1d390ae4556a1adb Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 16:17:21 -0600 Subject: [PATCH 29/84] Call expressions --- src/ol/expression/expression.js | 78 ++++++++++++++++++---- src/ol/expression/lexer.js | 1 - src/ol/expression/parser.js | 4 +- test/spec/ol/expression/expression.test.js | 70 +++++++++++++++++++ 4 files changed, 138 insertions(+), 15 deletions(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index 51e3aaab52..90f05219e0 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -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.} args Arguments. + */ +ol.expression.Call = function(expr, args) { + + /** + * @type {expr} + * @private + */ + this.expr_ = expr; + + /** + * @type {Array.} + * @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); }; - diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index cefb950cff..e4a2138515 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -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 = ''; diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index a7d85db958..8cddb0d177 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -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.} 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); }; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index b88ea17f3c..7080c55a5c 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -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'); From 29b77a2dac75f522c461478cd9fd78190d737be0 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 16:37:21 -0600 Subject: [PATCH 30/84] Member expressions --- src/ol/expression/expression.js | 38 ++++++++++++++++++++++ src/ol/expression/parser.js | 4 ++- test/spec/ol/expression/expression.test.js | 27 +++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index 90f05219e0..0215fc60ee 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -8,6 +8,7 @@ goog.provide('ol.expression.Logical'); goog.provide('ol.expression.LogicalOp'); goog.provide('ol.expression.Math'); goog.provide('ol.expression.MathOp'); +goog.provide('ol.expression.Member'); goog.provide('ol.expression.Not'); @@ -377,6 +378,43 @@ ol.expression.Math.prototype.evaluate = function(scope, opt_fns, opt_this) { +/** + * A member expression (e.g. `foo.bar`). + * + * @constructor + * @extends {ol.expression.Expression} + * @param {ol.expression.Expression} expr An expression that resolves to an + * object. + * @param {ol.expression.Identifier} property Identifier with name of property. + */ +ol.expression.Member = function(expr, property) { + + /** + * @type {ol.expression.Expression} + * @private + */ + this.expr_ = expr; + + /** + * @type {ol.expression.Identifier} + * @private + */ + this.property_ = property; + +}; +goog.inherits(ol.expression.Member, ol.expression.Expression); + + +/** + * @inheritDoc + */ +ol.expression.Member.prototype.evaluate = function(scope, opt_fns, opt_this) { + var obj = this.expr_.evaluate(scope, opt_fns, opt_this); + return this.property_.evaluate(obj); +}; + + + /** * A logical not expression (e.g. `!foo`). * diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 8cddb0d177..7c81b52536 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -28,6 +28,7 @@ goog.require('ol.expression.Logical'); goog.require('ol.expression.LogicalOp'); goog.require('ol.expression.Math'); goog.require('ol.expression.MathOp'); +goog.require('ol.expression.Member'); goog.require('ol.expression.Not'); goog.require('ol.expression.Token'); goog.require('ol.expression.TokenType'); @@ -187,11 +188,12 @@ ol.expression.Parser.prototype.createLiteral_ = function(value) { * // TODO: make exp {ol.expression.Member|ol.expression.Identifier} * @param {ol.expression.Expression} expr Expression. * @param {ol.expression.Identifier} property Member name. + * @return {ol.expression.Member} The member expression. * @private */ ol.expression.Parser.prototype.createMemberExpression_ = function(expr, property) { - throw new Error('Not implemented'); + return new ol.expression.Member(expr, property); }; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 7080c55a5c..e0d21b3349 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -392,6 +392,32 @@ describe('ol.expression.Math', function() { }); +describe('ol.expression.Member', function() { + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expression.Member( + new ol.expression.Identifier('foo'), + new ol.expression.Identifier('bar')); + + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Member); + }); + }); + + describe('#evaluate()', function() { + it('accesses an object property', function() { + + var expr = new ol.expression.Member( + new ol.expression.Identifier('foo'), + new ol.expression.Identifier('bar')); + + var scope = {foo: {bar: 42}}; + expect(expr.evaluate(scope)).to.be(42); + }); + }); +}); + + describe('ol.expression.Not', function() { describe('constructor', function() { @@ -446,4 +472,5 @@ goog.require('ol.expression.Logical'); goog.require('ol.expression.LogicalOp'); goog.require('ol.expression.Math'); goog.require('ol.expression.MathOp'); +goog.require('ol.expression.Member'); goog.require('ol.expression.Not'); From cb9725bdfb01dd49137000e86f0ecc9f03cec6c5 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 17:31:06 -0600 Subject: [PATCH 31/84] Provide static method to check for valid operators --- src/ol/expression/expression.js | 48 ++++++++++++++++++++++ src/ol/expression/parser.js | 6 +-- test/spec/ol/expression/expression.test.js | 48 ++++++++++++++++++++++ 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index 0215fc60ee..5ed75c7556 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -136,6 +136,22 @@ ol.expression.Comparison = function(operator, left, right) { goog.inherits(ol.expression.Comparison, ol.expression.Expression); +/** + * Determine if a given string is a valid comparison operator. + * @param {string} candidate Operator to test. + * @return {boolean} The operator is valid. + */ +ol.expression.Comparison.isValidOp = (function() { + var valid = {}; + for (var key in ol.expression.ComparisonOp) { + valid[ol.expression.ComparisonOp[key]] = true; + } + return function isValidOp(candidate) { + return !!valid[candidate]; + }; +}()); + + /** * @inheritDoc */ @@ -276,6 +292,22 @@ ol.expression.Logical = function(operator, left, right) { goog.inherits(ol.expression.Logical, ol.expression.Expression); +/** + * Determine if a given string is a valid logical operator. + * @param {string} candidate Operator to test. + * @return {boolean} The operator is valid. + */ +ol.expression.Logical.isValidOp = (function() { + var valid = {}; + for (var key in ol.expression.LogicalOp) { + valid[ol.expression.LogicalOp[key]] = true; + } + return function isValidOp(candidate) { + return !!valid[candidate]; + }; +}()); + + /** * @inheritDoc */ @@ -341,6 +373,22 @@ ol.expression.Math = function(operator, left, right) { goog.inherits(ol.expression.Math, ol.expression.Expression); +/** + * Determine if a given string is a valid math operator. + * @param {string} candidate Operator to test. + * @return {boolean} The operator is valid. + */ +ol.expression.Math.isValidOp = (function() { + var valid = {}; + for (var key in ol.expression.MathOp) { + valid[ol.expression.MathOp[key]] = true; + } + return function isValidOp(candidate) { + return !!valid[candidate]; + }; +}()); + + /** * @inheritDoc */ diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 7c81b52536..02b58bed5a 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -126,15 +126,15 @@ ol.expression.Parser.prototype.binaryPrecedence_ = function(token) { ol.expression.Parser.prototype.createBinaryExpression_ = function(operator, left, right) { var expr; - if (ol.expression.ComparisonOp.hasOwnProperty(operator)) { + if (ol.expression.Comparison.isValidOp(operator)) { expr = new ol.expression.Comparison( /** @type {ol.expression.ComparisonOp.} */ (operator), left, right); - } else if (ol.expression.LogicalOp.hasOwnProperty(operator)) { + } else if (ol.expression.Logical.isValidOp(operator)) { expr = new ol.expression.Logical( /** @type {ol.expression.LogicalOp.} */ (operator), left, right); - } else if (ol.expression.MathOp.hasOwnProperty(operator)) { + } else if (ol.expression.Math.isValidOp(operator)) { expr = new ol.expression.Math( /** @type {ol.expression.MathOp.} */ (operator), left, right); diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index e0d21b3349..0484895525 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -177,6 +177,24 @@ describe('ol.expression.Comparison', function() { }); }); + describe('#isValidOp()', function() { + it('determines if a string is a valid operator', function() { + expect(ol.expression.Comparison.isValidOp('<')).to.be(true); + expect(ol.expression.Comparison.isValidOp('<')).to.be(true); + expect(ol.expression.Comparison.isValidOp('<=')).to.be(true); + expect(ol.expression.Comparison.isValidOp('<=')).to.be(true); + expect(ol.expression.Comparison.isValidOp('==')).to.be(true); + expect(ol.expression.Comparison.isValidOp('!=')).to.be(true); + expect(ol.expression.Comparison.isValidOp('===')).to.be(true); + expect(ol.expression.Comparison.isValidOp('!==')).to.be(true); + + expect(ol.expression.Comparison.isValidOp('')).to.be(false); + expect(ol.expression.Comparison.isValidOp('+')).to.be(false); + expect(ol.expression.Comparison.isValidOp('-')).to.be(false); + expect(ol.expression.Comparison.isValidOp('&&')).to.be(false); + }); + }); + }); describe('ol.expression.Identifier', function() { @@ -292,6 +310,18 @@ describe('ol.expression.Logical', function() { }); }); + describe('#isValidOp()', function() { + it('determines if a string is a valid operator', function() { + expect(ol.expression.Logical.isValidOp('||')).to.be(true); + expect(ol.expression.Logical.isValidOp('&&')).to.be(true); + + expect(ol.expression.Logical.isValidOp('')).to.be(false); + expect(ol.expression.Logical.isValidOp('+')).to.be(false); + expect(ol.expression.Logical.isValidOp('<')).to.be(false); + expect(ol.expression.Logical.isValidOp('|')).to.be(false); + }); + }); + }); describe('ol.expression.Math', function() { @@ -390,6 +420,24 @@ describe('ol.expression.Math', function() { }); }); + describe('#isValidOp()', function() { + it('determines if a string is a valid operator', function() { + expect(ol.expression.Math.isValidOp('+')).to.be(true); + expect(ol.expression.Math.isValidOp('-')).to.be(true); + expect(ol.expression.Math.isValidOp('*')).to.be(true); + expect(ol.expression.Math.isValidOp('/')).to.be(true); + expect(ol.expression.Math.isValidOp('%')).to.be(true); + + expect(ol.expression.Math.isValidOp('')).to.be(false); + expect(ol.expression.Math.isValidOp('|')).to.be(false); + expect(ol.expression.Math.isValidOp('&')).to.be(false); + expect(ol.expression.Math.isValidOp('<')).to.be(false); + expect(ol.expression.Math.isValidOp('||')).to.be(false); + expect(ol.expression.Math.isValidOp('.')).to.be(false); + }); + }); + + }); describe('ol.expression.Member', function() { From 7a1e69e2886034dc36a94841d3428ae6efee4e40 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Jun 2013 17:33:51 -0600 Subject: [PATCH 32/84] Initial parsing tests --- src/ol/expression/parser.js | 15 +++++++++-- test/spec/ol/expression/parser.test.js | 35 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 test/spec/ol/expression/parser.test.js diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 02b58bed5a..b9ebcea859 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -433,8 +433,7 @@ ol.expression.Parser.prototype.parseUnaryExpression_ = function(lexer) { expr = this.parseUnaryExpression_(lexer); expr = this.createUnaryExpression_('!', expr); } else { - // TODO: add token.index - throw new Error('Unexpected token: ' + operator.value); + expr = this.parseLeftHandSideExpression_(lexer); } return expr; }; @@ -450,3 +449,15 @@ ol.expression.Parser.prototype.parseUnaryExpression_ = function(lexer) { ol.expression.Parser.prototype.parseExpression_ = function(lexer) { return this.parseBinaryExpression_(lexer); }; + + +/** + * Parse an expression + * @param {string} source The expression source (e.g. `'foo + 2'`). + * @return {ol.expression.Expression} An expression instance that can be + * evaluated within some scope to provide a value. + */ +ol.expression.parse = function(source) { + var parser = new ol.expression.Parser(); + return parser.parse(source); +}; diff --git a/test/spec/ol/expression/parser.test.js b/test/spec/ol/expression/parser.test.js new file mode 100644 index 0000000000..d423d1b75c --- /dev/null +++ b/test/spec/ol/expression/parser.test.js @@ -0,0 +1,35 @@ +goog.provide('ol.test.expression'); + +describe('ol.expression.Parser', function() { + + describe('constructor', function() { + it('creates a new expression parser', function() { + var parser = new ol.expression.Parser(); + expect(parser).to.be.a(ol.expression.Parser); + }); + }); + + describe('#parseGroupExpression_()', function() { + + function parse(source) { + var lexer = new ol.expression.Lexer(source); + var parser = new ol.expression.Parser(); + return parser.parseGroupExpression_(lexer); + } + + it('parses grouped expressions', function() { + var expr = parse('(3 * (foo + 2))'); + expect(expr).to.be.a(ol.expression.Expression); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 3})).to.be(15); + }); + + }); + +}); + + +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Lexer'); +goog.require('ol.expression.Math'); +goog.require('ol.expression.Parser'); From 737d669e4ad4c8cc9ad3e04a51c974f013ce2c02 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 10:19:12 -0600 Subject: [PATCH 33/84] Correct types --- src/ol/expression/expression.js | 13 +++++++++---- src/ol/expression/parser.js | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index 5ed75c7556..e51ca718ba 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -46,13 +46,14 @@ ol.expression.Expression.prototype.evaluate = goog.abstractMethod; * * @constructor * @extends {ol.expression.Expression} - * @param {expr} expr An expression that resolves to a function. + * @param {ol.expression.Expression} expr An expression that resolves to a + * function. * @param {Array.} args Arguments. */ ol.expression.Call = function(expr, args) { /** - * @type {expr} + * @type {ol.expression.Expression} * @private */ this.expr_ = expr; @@ -74,7 +75,7 @@ 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_); + throw new Error('Expected function but found ' + fn); } var thisArg = goog.isDef(opt_this) ? opt_this : {}; @@ -458,7 +459,11 @@ goog.inherits(ol.expression.Member, ol.expression.Expression); */ ol.expression.Member.prototype.evaluate = function(scope, opt_fns, opt_this) { var obj = this.expr_.evaluate(scope, opt_fns, opt_this); - return this.property_.evaluate(obj); + if (!goog.isObject(obj)) { + throw new Error('Expected member expression to evaluate to an object ' + + 'but got ' + obj); + } + return this.property_.evaluate(/** @type {Object} */ (obj)); }; diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index b9ebcea859..6762dc2f02 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -154,7 +154,7 @@ ol.expression.Parser.prototype.createBinaryExpression_ = function(operator, * @private */ ol.expression.Parser.prototype.createCallExpression_ = function(expr, args) { - return new ol.expressions.Call(expr, args); + return new ol.expression.Call(expr, args); }; From ce67aa2617f394127c2f380920f8920ec62e1b5d Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 10:31:11 -0600 Subject: [PATCH 34/84] Make way for convenience functions in ol.expression package --- src/ol/expression/{expression.js => expressions.js} | 0 .../ol/expression/{expression.test.js => expressions.test.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/ol/expression/{expression.js => expressions.js} (100%) rename test/spec/ol/expression/{expression.test.js => expressions.test.js} (100%) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expressions.js similarity index 100% rename from src/ol/expression/expression.js rename to src/ol/expression/expressions.js diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expressions.test.js similarity index 100% rename from test/spec/ol/expression/expression.test.js rename to test/spec/ol/expression/expressions.test.js From 3643ea164fcc4454959718c98962c333a882cdce Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 11:07:50 -0600 Subject: [PATCH 35/84] Cast boolean and null before creating literal expression --- src/ol/expression/parser.js | 10 +++++-- test/spec/ol/expression/parser.test.js | 38 ++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 6762dc2f02..7edee85e1d 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -404,10 +404,14 @@ ol.expression.Parser.prototype.parsePrimaryExpression_ = function(lexer) { if (type === ol.expression.TokenType.IDENTIFIER) { expr = this.createIdentifier_(/** @type {string} */ (token.value)); } else if (type === ol.expression.TokenType.STRING_LITERAL || - type === ol.expression.TokenType.NUMERIC_LITERAL || - type === ol.expression.TokenType.BOOLEAN_LITERAL || - type === ol.expression.TokenType.NULL_LITERAL) { + type === ol.expression.TokenType.NUMERIC_LITERAL) { + // numeric and string literals are already the correct type expr = this.createLiteral_(token.value); + } else if (type === ol.expression.TokenType.BOOLEAN_LITERAL) { + // because booleans are valid member properties, tokens are still string + expr = this.createLiteral_(token.value === 'true'); + } else if (type === ol.expression.TokenType.NULL_LITERAL) { + expr = this.createLiteral_(null); } else { throw new Error('Unexpected token: ' + token.value); } diff --git a/test/spec/ol/expression/parser.test.js b/test/spec/ol/expression/parser.test.js index d423d1b75c..4d7e478b5e 100644 --- a/test/spec/ol/expression/parser.test.js +++ b/test/spec/ol/expression/parser.test.js @@ -1,4 +1,4 @@ -goog.provide('ol.test.expression'); +goog.provide('ol.test.expression.Parser'); describe('ol.expression.Parser', function() { @@ -19,13 +19,47 @@ describe('ol.expression.Parser', function() { it('parses grouped expressions', function() { var expr = parse('(3 * (foo + 2))'); - expect(expr).to.be.a(ol.expression.Expression); expect(expr).to.be.a(ol.expression.Math); expect(expr.evaluate({foo: 3})).to.be(15); }); }); + describe('#parsePrimaryExpression_()', function() { + + function parse(source) { + var lexer = new ol.expression.Lexer(source); + var parser = new ol.expression.Parser(); + return parser.parsePrimaryExpression_(lexer); + } + + it('parses string literal', function() { + var expr = parse('"foo"'); + expect(expr).to.be.a(ol.expression.Literal); + expect(expr.evaluate({})).to.be('foo'); + }); + + it('parses numeric literal', function() { + var expr = parse('.42e2'); + expect(expr).to.be.a(ol.expression.Literal); + expect(expr.evaluate({})).to.be(42); + }); + + it('parses boolean literal', function() { + var expr = parse('.42e2'); + expect(expr).to.be.a(ol.expression.Literal); + expect(expr.evaluate({})).to.be(42); + }); + + it('parses null literal', function() { + var expr = parse('null'); + expect(expr).to.be.a(ol.expression.Literal); + expect(expr.evaluate({})).to.be(null); + }); + + }); + + }); From 13d0b8b084a53f4174ec855b6152475ed8dff532 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 11:09:23 -0600 Subject: [PATCH 36/84] Provide ol.expression.parse method This is the only method needed in the API. --- src/ol/expression/expression.js | 15 +++++ src/ol/expression/parser.js | 12 ---- test/spec/ol/expression/expression.test.js | 70 ++++++++++++++++++++++ 3 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 src/ol/expression/expression.js create mode 100644 test/spec/ol/expression/expression.test.js diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js new file mode 100644 index 0000000000..dd2cc27e2b --- /dev/null +++ b/src/ol/expression/expression.js @@ -0,0 +1,15 @@ +goog.provide('ol.expression'); + +goog.require('ol.expression.Parser'); + + +/** + * Parse an expression + * @param {string} source The expression source (e.g. `'foo + 2'`). + * @return {ol.expression.Expression} An expression instance that can be + * evaluated within some scope to provide a value. + */ +ol.expression.parse = function(source) { + var parser = new ol.expression.Parser(); + return parser.parse(source); +}; diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 7edee85e1d..0ec2808797 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -453,15 +453,3 @@ ol.expression.Parser.prototype.parseUnaryExpression_ = function(lexer) { ol.expression.Parser.prototype.parseExpression_ = function(lexer) { return this.parseBinaryExpression_(lexer); }; - - -/** - * Parse an expression - * @param {string} source The expression source (e.g. `'foo + 2'`). - * @return {ol.expression.Expression} An expression instance that can be - * evaluated within some scope to provide a value. - */ -ol.expression.parse = function(source) { - var parser = new ol.expression.Parser(); - return parser.parse(source); -}; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js new file mode 100644 index 0000000000..8f5f5cc212 --- /dev/null +++ b/test/spec/ol/expression/expression.test.js @@ -0,0 +1,70 @@ +goog.provide('ol.test.expression'); + + +describe('ol.expression.parse', function() { + + it('parses a string and returns an expression', function() { + var expr = ol.expression.parse('foo'); + expect(expr).to.be.a(ol.expression.Expression); + }); + + describe('primary expressions', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.1 + + it('parses identifier expressions', function() { + var expr = ol.expression.parse('foo'); + expect(expr).to.be.a(ol.expression.Identifier); + expect(expr.evaluate({foo: 'bar'})).to.be('bar'); + }); + + it('throws on invalid identifier expressions', function() { + expect(function() { + ol.expression.parse('3foo'); + }).throwException(); + }); + + it('parses string literal expressions', function() { + var expr = ol.expression.parse('"foo"'); + expect(expr).to.be.a(ol.expression.Literal); + expect(expr.evaluate({})).to.be('foo'); + }); + + it('throws on unterminated string', function() { + expect(function() { + ol.expression.parse('"foo'); + }).throwException(); + }); + + it('parses numeric literal expressions', function() { + var expr = ol.expression.parse('.42e+2'); + expect(expr).to.be.a(ol.expression.Literal); + expect(expr.evaluate({})).to.be(42); + }); + + it('throws on invalid number', function() { + expect(function() { + ol.expression.parse('.42eX'); + }).throwException(); + }); + + it('parses boolean literal expressions', function() { + var expr = ol.expression.parse('false'); + expect(expr).to.be.a(ol.expression.Literal); + expect(expr.evaluate({})).to.be(false); + }); + + it('parses null literal expressions', function() { + var expr = ol.expression.parse('null'); + expect(expr).to.be.a(ol.expression.Literal); + expect(expr.evaluate({})).to.be(null); + }); + + }); + +}); + + +goog.require('ol.expression'); +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Identifier'); +goog.require('ol.expression.Literal'); From d920d8e5788d93fe4e3c23dcc50cbfbf08a3e652 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 11:37:17 -0600 Subject: [PATCH 37/84] Test left-hand-side expression parsing --- src/ol/expression/parser.js | 13 +++++--- test/spec/ol/expression/expression.test.js | 38 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 0ec2808797..b0a0f68bf3 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -236,12 +236,14 @@ ol.expression.Parser.prototype.parseArguments_ = function(lexer) { lexer.expect('('); - while (!lexer.match(')')) { - args.push(this.parseBinaryExpression_(lexer)); - if (lexer.match(')')) { - break; + if (!lexer.match(')')) { + while (true) { + args.push(this.parseBinaryExpression_(lexer)); + if (lexer.match(')')) { + break; + } + lexer.expect(','); } - lexer.expect(','); } lexer.skip(); @@ -355,6 +357,7 @@ ol.expression.Parser.prototype.parseLeftHandSideExpression_ = function(lexer) { while (token.value === '.') { var property = this.parseNonComputedMember_(lexer); expr = this.createMemberExpression_(expr, property); + token = lexer.peek(); } } return expr; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 8f5f5cc212..dab53c249a 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -61,6 +61,44 @@ describe('ol.expression.parse', function() { }); + describe('left-hand-side expressions', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 + + it('parses member expressions with dot notation', function() { + var expr = ol.expression.parse('foo.bar.baz'); + expect(expr).to.be.a(ol.expression.Member); + var scope = {foo: {bar: {baz: 42}}}; + expect(expr.evaluate(scope)).to.be(42); + }); + + it('throws on invalid member expression', function() { + expect(function() { + ol.expression.parse('foo.4bar'); + }).throwException(); + }); + + it('parses call expressions with literal arguments', function() { + var expr = ol.expression.parse('foo(42, "bar")'); + expect(expr).to.be.a(ol.expression.Call); + var scope = { + foo: function(num, str) { + expect(num).to.be(42); + expect(str).to.be('bar'); + return str + num; + } + }; + expect(expr.evaluate(scope)).to.be('bar42'); + }); + + it('throws on calls with unterminated arguments', function() { + expect(function() { + ol.expression.parse('foo(42,)'); + }).throwException(); + }); + + }); + + }); From 6d0badcf2a88c5b1a74dafcd29c6290dc4d79ede Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 13:22:35 -0600 Subject: [PATCH 38/84] Optional scope (works for expressions without identifiers) --- src/ol/expression/expressions.js | 46 ++++++++++++--------- test/spec/ol/expression/expression.test.js | 8 ++-- test/spec/ol/expression/expressions.test.js | 24 +++++------ test/spec/ol/expression/parser.test.js | 8 ++-- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index e51ca718ba..98a608ca2c 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -29,8 +29,9 @@ ol.expression.Expression = function() {}; /** * Evaluate the expression and return the result. * - * @param {Object} scope Evaluation scope. All properties of this object - * will be available as variables when evaluating the expression. + * @param {Object=} opt_scope Evaluation scope. All properties of this object + * will be available as variables when evaluating the expression. If not + * provided, `null` will be used. * @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 @@ -71,8 +72,8 @@ 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; +ol.expression.Call.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { + var fnScope = goog.isDefAndNotNull(opt_fns) ? opt_fns : opt_scope; var fn = this.expr_.evaluate(fnScope); if (!fn || !goog.isFunction(fn)) { throw new Error('Expected function but found ' + fn); @@ -82,7 +83,7 @@ ol.expression.Call.prototype.evaluate = function(scope, opt_fns, 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); + values[i] = this.args_[i].evaluate(opt_scope, opt_fns, opt_this); } return fn.apply(thisArg, values); }; @@ -156,11 +157,11 @@ ol.expression.Comparison.isValidOp = (function() { /** * @inheritDoc */ -ol.expression.Comparison.prototype.evaluate = function(scope, opt_this, +ol.expression.Comparison.prototype.evaluate = function(opt_scope, opt_this, opt_fns) { var result; - var rightVal = this.right_.evaluate(scope, opt_fns, opt_this); - var leftVal = this.left_.evaluate(scope, opt_fns, opt_this); + var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this); + var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this); switch (this.operator_) { case ol.expression.ComparisonOp.EQ: @@ -217,8 +218,11 @@ goog.inherits(ol.expression.Identifier, ol.expression.Expression); /** * @inheritDoc */ -ol.expression.Identifier.prototype.evaluate = function(scope) { - return scope[this.name_]; +ol.expression.Identifier.prototype.evaluate = function(opt_scope) { + if (!goog.isDefAndNotNull(opt_scope)) { + throw new Error('Attempt to evaluate identifier with no scope'); + } + return opt_scope[this.name_]; }; @@ -312,10 +316,11 @@ ol.expression.Logical.isValidOp = (function() { /** * @inheritDoc */ -ol.expression.Logical.prototype.evaluate = function(scope, opt_fns, opt_this) { +ol.expression.Logical.prototype.evaluate = function(opt_scope, opt_fns, + opt_this) { var result; - var rightVal = this.right_.evaluate(scope, opt_fns, opt_this); - var leftVal = this.left_.evaluate(scope, opt_fns, opt_this); + var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this); + var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this); if (this.operator_ === ol.expression.LogicalOp.AND) { result = leftVal && rightVal; @@ -393,10 +398,10 @@ ol.expression.Math.isValidOp = (function() { /** * @inheritDoc */ -ol.expression.Math.prototype.evaluate = function(scope, opt_fns, opt_this) { +ol.expression.Math.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { var result; - var rightVal = this.right_.evaluate(scope, opt_fns, opt_this); - var leftVal = this.left_.evaluate(scope, opt_fns, opt_this); + var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this); + var leftVal = this.left_.evaluate(opt_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 @@ -457,8 +462,9 @@ goog.inherits(ol.expression.Member, ol.expression.Expression); /** * @inheritDoc */ -ol.expression.Member.prototype.evaluate = function(scope, opt_fns, opt_this) { - var obj = this.expr_.evaluate(scope, opt_fns, opt_this); +ol.expression.Member.prototype.evaluate = function(opt_scope, opt_fns, + opt_this) { + var obj = this.expr_.evaluate(opt_scope, opt_fns, opt_this); if (!goog.isObject(obj)) { throw new Error('Expected member expression to evaluate to an object ' + 'but got ' + obj); @@ -490,6 +496,6 @@ goog.inherits(ol.expression.Not, ol.expression.Expression); /** * @inheritDoc */ -ol.expression.Not.prototype.evaluate = function(scope, opt_fns, opt_this) { - return !this.expr_.evaluate(scope, opt_fns, opt_this); +ol.expression.Not.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { + return !this.expr_.evaluate(opt_scope, opt_fns, opt_this); }; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index dab53c249a..e1ef1d59c1 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -26,7 +26,7 @@ describe('ol.expression.parse', function() { it('parses string literal expressions', function() { var expr = ol.expression.parse('"foo"'); expect(expr).to.be.a(ol.expression.Literal); - expect(expr.evaluate({})).to.be('foo'); + expect(expr.evaluate()).to.be('foo'); }); it('throws on unterminated string', function() { @@ -38,7 +38,7 @@ describe('ol.expression.parse', function() { it('parses numeric literal expressions', function() { var expr = ol.expression.parse('.42e+2'); expect(expr).to.be.a(ol.expression.Literal); - expect(expr.evaluate({})).to.be(42); + expect(expr.evaluate()).to.be(42); }); it('throws on invalid number', function() { @@ -50,13 +50,13 @@ describe('ol.expression.parse', function() { it('parses boolean literal expressions', function() { var expr = ol.expression.parse('false'); expect(expr).to.be.a(ol.expression.Literal); - expect(expr.evaluate({})).to.be(false); + expect(expr.evaluate()).to.be(false); }); it('parses null literal expressions', function() { var expr = ol.expression.parse('null'); expect(expr).to.be.a(ol.expression.Literal); - expect(expr.evaluate({})).to.be(null); + expect(expr.evaluate()).to.be(null); }); }); diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index 0484895525..1e924fd8e7 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -250,22 +250,22 @@ describe('ol.expression.Literal', function() { describe('#evaluate()', function() { it('works for numeric literal', function() { var expr = new ol.expression.Literal(42e-11); - expect(expr.evaluate({})).to.be(4.2e-10); + expect(expr.evaluate()).to.be(4.2e-10); }); it('works for string literal', function() { var expr = new ol.expression.Literal('asdf'); - expect(expr.evaluate({})).to.be('asdf'); + expect(expr.evaluate()).to.be('asdf'); }); it('works for boolean literal', function() { var expr = new ol.expression.Literal(true); - expect(expr.evaluate({})).to.be(true); + expect(expr.evaluate()).to.be(true); }); it('works for null literal', function() { var expr = new ol.expression.Literal(null); - expect(expr.evaluate({})).to.be(null); + expect(expr.evaluate()).to.be(null); }); }); }); @@ -344,7 +344,7 @@ describe('ol.expression.Math', function() { new ol.expression.Literal(40), new ol.expression.Literal(2)); - expect(expr.evaluate({})).to.be(42); + expect(expr.evaluate()).to.be(42); }); it('does + with string literal (note: subject to change)', function() { @@ -353,7 +353,7 @@ describe('ol.expression.Math', function() { new ol.expression.Literal('foo'), new ol.expression.Literal('bar')); - expect(expr.evaluate({})).to.be('foobar'); + expect(expr.evaluate()).to.be('foobar'); }); it('does + with identifiers', function() { @@ -480,30 +480,30 @@ describe('ol.expression.Not', function() { describe('#evaluate()', function() { it('returns the logical complement', function() { var expr = new ol.expression.Not(new ol.expression.Literal(true)); - expect(expr.evaluate({})).to.be(false); + expect(expr.evaluate()).to.be(false); expr = new ol.expression.Not(new ol.expression.Literal(false)); - expect(expr.evaluate({})).to.be(true); + expect(expr.evaluate()).to.be(true); }); it('negates a truthy string', function() { var expr = new ol.expression.Not(new ol.expression.Literal('asdf')); - expect(expr.evaluate({})).to.be(false); + expect(expr.evaluate()).to.be(false); }); it('negates a falsy string', function() { var expr = new ol.expression.Not(new ol.expression.Literal('')); - expect(expr.evaluate({})).to.be(true); + expect(expr.evaluate()).to.be(true); }); it('negates a truthy number', function() { var expr = new ol.expression.Not(new ol.expression.Literal(42)); - expect(expr.evaluate({})).to.be(false); + expect(expr.evaluate()).to.be(false); }); it('negates a falsy number', function() { var expr = new ol.expression.Not(new ol.expression.Literal(NaN)); - expect(expr.evaluate({})).to.be(true); + expect(expr.evaluate()).to.be(true); }); }); diff --git a/test/spec/ol/expression/parser.test.js b/test/spec/ol/expression/parser.test.js index 4d7e478b5e..1ff0e8c9cd 100644 --- a/test/spec/ol/expression/parser.test.js +++ b/test/spec/ol/expression/parser.test.js @@ -36,25 +36,25 @@ describe('ol.expression.Parser', function() { it('parses string literal', function() { var expr = parse('"foo"'); expect(expr).to.be.a(ol.expression.Literal); - expect(expr.evaluate({})).to.be('foo'); + expect(expr.evaluate()).to.be('foo'); }); it('parses numeric literal', function() { var expr = parse('.42e2'); expect(expr).to.be.a(ol.expression.Literal); - expect(expr.evaluate({})).to.be(42); + expect(expr.evaluate()).to.be(42); }); it('parses boolean literal', function() { var expr = parse('.42e2'); expect(expr).to.be.a(ol.expression.Literal); - expect(expr.evaluate({})).to.be(42); + expect(expr.evaluate()).to.be(42); }); it('parses null literal', function() { var expr = parse('null'); expect(expr).to.be.a(ol.expression.Literal); - expect(expr.evaluate({})).to.be(null); + expect(expr.evaluate()).to.be(null); }); }); From 38c8927ae2558cbf073f63a9a4774f8e33c978fc Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 13:24:01 -0600 Subject: [PATCH 39/84] Doc and method reorg --- src/ol/expression/lexer.js | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index e4a2138515..5ced440e0a 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -134,7 +134,7 @@ ol.expression.Lexer = function(source) { /** - * Scan the next token and throw if it doesn't match. + * Scan the next token and throw if it isn't a punctuator that matches input. * @param {string} value Token value. */ ol.expression.Lexer.prototype.expect = function(value) { @@ -426,6 +426,20 @@ ol.expression.Lexer.prototype.next = function() { }; +/** + * Peek at the next token, but don't advance the index. + * + * @return {ol.expression.Token} The upcoming token. + */ +ol.expression.Lexer.prototype.peek = function() { + var currentIndex = this.index_; + var token = this.next(); + this.nextIndex_ = this.index_; + this.index_ = currentIndex; + return token; +}; + + /** * Scan hex literal as numeric token. * @@ -792,6 +806,14 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { }; +/** + * After peeking, skip may be called to advance the cursor without re-scanning. + */ +ol.expression.Lexer.prototype.skip = function() { + this.index_ = this.nextIndex_; +}; + + /** * Skip all whitespace. * @return {number} The character code of the first non-whitespace character. @@ -809,25 +831,3 @@ ol.expression.Lexer.prototype.skipWhitespace_ = function() { } return code; }; - - -/** - * Peek at the next token, but don't advance the index. - * - * @return {ol.expression.Token} The upcoming token. - */ -ol.expression.Lexer.prototype.peek = function() { - var currentIndex = this.index_; - var token = this.next(); - this.nextIndex_ = this.index_; - this.index_ = currentIndex; - return token; -}; - - -/** - * After peeking, skip may be called to advance the cursor without re-scanning. - */ -ol.expression.Lexer.prototype.skip = function() { - this.index_ = this.nextIndex_; -}; From 379ac8268844b176258a49c8d8cf93c58b3b1128 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 13:28:10 -0600 Subject: [PATCH 40/84] Test argument parsing --- test/spec/ol/expression/parser.test.js | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/spec/ol/expression/parser.test.js b/test/spec/ol/expression/parser.test.js index 1ff0e8c9cd..dd0df0be11 100644 --- a/test/spec/ol/expression/parser.test.js +++ b/test/spec/ol/expression/parser.test.js @@ -9,6 +9,42 @@ describe('ol.expression.Parser', function() { }); }); + describe('#parseArguments_()', function() { + + function parse(source) { + var lexer = new ol.expression.Lexer(source); + var parser = new ol.expression.Parser(); + return parser.parseArguments_(lexer); + } + + it('parses comma separated expressions in parens', function() { + var args = parse('(1/3, "foo", true)'); + expect(args).length(3); + + expect(args[0]).to.be.a(ol.expression.Math); + expect(args[0].evaluate()).to.be(1 / 3); + + expect(args[1]).to.be.a(ol.expression.Literal); + expect(args[1].evaluate()).to.be('foo'); + + expect(args[2]).to.be.a(ol.expression.Literal); + expect(args[2].evaluate()).to.be(true); + }); + + it('throws on invalid arg expression', function() { + expect(function() { + parse('(6e)'); + }).throwException(); + }); + + it('throws on unterminated args', function() { + expect(function() { + parse('("foo", 42, )'); + }).throwException(); + }); + + }); + describe('#parseGroupExpression_()', function() { function parse(source) { From 40b12410f097bec64d742dcf5cac3f05c66109d6 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 14:37:46 -0600 Subject: [PATCH 41/84] Increment after scanning >= and <= (and expect EOF in tests) --- src/ol/expression/lexer.js | 1 + test/spec/ol/expression/lexer.test.js | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 5ced440e0a..fab87330e9 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -721,6 +721,7 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { if (code === ol.expression.Char.GREATER || code === ol.expression.Char.LESS) { + this.increment_(2); return { type: ol.expression.TokenType.PUNCTUATOR, value: String.fromCharCode(code) + '=' diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 435d421d37..370b48ae2a 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -93,9 +93,13 @@ describe('ol.expression.Lexer', function() { describe('#scanIdentifier_()', function() { - function scan(source) { + function scan(source, part) { var lexer = new ol.expression.Lexer(source); - return lexer.scanIdentifier_(lexer.getCurrentCharCode_()); + var token = lexer.scanIdentifier_(lexer.getCurrentCharCode_()); + if (!part) { + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + } + return token; } it('works for short identifiers', function() { @@ -171,7 +175,7 @@ describe('ol.expression.Lexer', function() { }); it('only scans valid identifier part', function() { - var token = scan('foo>bar'); + var token = scan('foo>bar', true); expect(token.value).to.be('foo'); expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); }); @@ -182,7 +186,9 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); - return lexer.scanNumericLiteral_(lexer.getCurrentCharCode_()); + var token = lexer.scanNumericLiteral_(lexer.getCurrentCharCode_()); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return token; } it('works for integers', function() { @@ -233,7 +239,9 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); - return lexer.scanPunctuator_(lexer.getCurrentCharCode_()); + var token = lexer.scanPunctuator_(lexer.getCurrentCharCode_()); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return token; } it('works for dot', function() { @@ -320,7 +328,9 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); - return lexer.scanStringLiteral_(lexer.getCurrentCharCode_()); + var token = lexer.scanStringLiteral_(lexer.getCurrentCharCode_()); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return token; } it('parses double quoted string', function() { From 803b3a3f7db7470f01219c48f9852ba8b102bcd3 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 14:38:04 -0600 Subject: [PATCH 42/84] Binary expression parsing --- test/spec/ol/expression/parser.test.js | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/test/spec/ol/expression/parser.test.js b/test/spec/ol/expression/parser.test.js index dd0df0be11..4df0663581 100644 --- a/test/spec/ol/expression/parser.test.js +++ b/test/spec/ol/expression/parser.test.js @@ -45,6 +45,70 @@ describe('ol.expression.Parser', function() { }); + describe('#parseBinaryExpression_()', function() { + + function parse(source) { + var lexer = new ol.expression.Lexer(source); + var parser = new ol.expression.Parser(); + return parser.parseBinaryExpression_(lexer); + } + + it('works with multiplicitave operators', function() { + var expr = parse('4 * 1e4'); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate()).to.be(40000); + + expect(parse('10/3').evaluate()).to.be(10 / 3); + }); + + it('works with additive operators', function() { + var expr = parse('4 +1e4'); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate()).to.be(10004); + + expect(parse('10-3').evaluate()).to.be(7); + }); + + it('works with relational operators', function() { + var expr = parse('4 < 1e4'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate()).to.be(true); + + expect(parse('10<3').evaluate()).to.be(false); + expect(parse('10 <= "10"').evaluate()).to.be(true); + expect(parse('10 > "10"').evaluate()).to.be(false); + expect(parse('10 >= 9').evaluate()).to.be(true); + }); + + it('works with equality operators', function() { + var expr = parse('4 == 1e4'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate()).to.be(false); + + expect(parse('10!=3').evaluate()).to.be(true); + expect(parse('10 == "10"').evaluate()).to.be(true); + expect(parse('10 === "10"').evaluate()).to.be(false); + expect(parse('10 !== "10"').evaluate()).to.be(true); + }); + + it('works with binary logical operators', function() { + var expr = parse('true && false'); + expect(expr).to.be.a(ol.expression.Logical); + expect(expr.evaluate()).to.be(false); + + expect(parse('false||true').evaluate()).to.be(true); + expect(parse('false || false').evaluate()).to.be(false); + expect(parse('true &&true').evaluate()).to.be(true); + }); + + it('throws for invalid binary expression', function() { + expect(function() { + parse('4 * / 2'); + }).throwException(); + }); + + }); + describe('#parseGroupExpression_()', function() { function parse(source) { From a0bd736948246582afed6b66c3d3acc5fdec90ee Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 15:37:36 -0600 Subject: [PATCH 43/84] Left-hand-side expression parsing --- src/ol/expression/parser.js | 3 +- test/spec/ol/expression/parser.test.js | 77 ++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index b0a0f68bf3..e9f0397331 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -333,7 +333,8 @@ ol.expression.Parser.prototype.parseGroupExpression_ = function(lexer) { /** - * Parse left-hand-side expression. + * Parse left-hand-side expression. Limited to Member Expressions + * and Call Expressions. * http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 * * @param {ol.expression.Lexer} lexer Lexer. diff --git a/test/spec/ol/expression/parser.test.js b/test/spec/ol/expression/parser.test.js index 4df0663581..708e6d0018 100644 --- a/test/spec/ol/expression/parser.test.js +++ b/test/spec/ol/expression/parser.test.js @@ -14,7 +14,9 @@ describe('ol.expression.Parser', function() { function parse(source) { var lexer = new ol.expression.Lexer(source); var parser = new ol.expression.Parser(); - return parser.parseArguments_(lexer); + var expr = parser.parseArguments_(lexer); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return expr; } it('parses comma separated expressions in parens', function() { @@ -50,7 +52,9 @@ describe('ol.expression.Parser', function() { function parse(source) { var lexer = new ol.expression.Lexer(source); var parser = new ol.expression.Parser(); - return parser.parseBinaryExpression_(lexer); + var expr = parser.parseBinaryExpression_(lexer); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return expr; } it('works with multiplicitave operators', function() { @@ -105,6 +109,15 @@ describe('ol.expression.Parser', function() { expect(function() { parse('4 * / 2'); }).throwException(); + + expect(function() { + parse('4 < / 2'); + }).throwException(); + + expect(function() { + parse('4 * && 2'); + }).throwException(); + }); }); @@ -114,7 +127,9 @@ describe('ol.expression.Parser', function() { function parse(source) { var lexer = new ol.expression.Lexer(source); var parser = new ol.expression.Parser(); - return parser.parseGroupExpression_(lexer); + var expr = parser.parseGroupExpression_(lexer); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return expr; } it('parses grouped expressions', function() { @@ -125,12 +140,60 @@ describe('ol.expression.Parser', function() { }); + describe('#parseLeftHandSideExpression_()', function() { + + function parse(source) { + var lexer = new ol.expression.Lexer(source); + var parser = new ol.expression.Parser(); + var expr = parser.parseLeftHandSideExpression_(lexer); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return expr; + } + + it('parses member expressions', function() { + var expr = parse('foo.bar.bam'); + expect(expr).to.be.a(ol.expression.Member); + }); + + it('throws on invalid member expression', function() { + expect(function() { + parse('foo.4'); + }).throwException(); + }); + + it('parses call expressions', function() { + var expr = parse('foo(bar)'); + expect(expr).to.be.a(ol.expression.Call); + var fns = { + foo: function(arg) { + expect(arguments).length(1); + expect(arg).to.be('chicken'); + return 'got ' + arg; + } + }; + var scope = { + bar: 'chicken' + }; + expect(expr.evaluate(scope, fns)).to.be('got chicken'); + }); + + it('throws on invalid call expression', function() { + expect(function() { + parse('foo(*)'); + }).throwException(); + }); + + }); + + describe('#parsePrimaryExpression_()', function() { function parse(source) { var lexer = new ol.expression.Lexer(source); var parser = new ol.expression.Parser(); - return parser.parsePrimaryExpression_(lexer); + var expr = parser.parsePrimaryExpression_(lexer); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return expr; } it('parses string literal', function() { @@ -164,6 +227,12 @@ describe('ol.expression.Parser', function() { goog.require('ol.expression.Expression'); +goog.require('ol.expression.Call'); +goog.require('ol.expression.Comparison'); goog.require('ol.expression.Lexer'); +goog.require('ol.expression.Literal'); +goog.require('ol.expression.Logical'); goog.require('ol.expression.Math'); +goog.require('ol.expression.Member'); goog.require('ol.expression.Parser'); +goog.require('ol.expression.TokenType'); From 26a7d907a239b4057d1f3f78f30d5dc4c985cec8 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 15:45:16 -0600 Subject: [PATCH 44/84] Test unary expression parsing --- src/ol/expression/parser.js | 2 +- test/spec/ol/expression/parser.test.js | 37 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index e9f0397331..056f6ddafd 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -424,7 +424,7 @@ ol.expression.Parser.prototype.parsePrimaryExpression_ = function(lexer) { /** - * Parse expression with a unary operator. + * Parse expression with a unary operator. Limited to logical not operator. * http://www.ecma-international.org/ecma-262/5.1/#sec-11.4 * * @param {ol.expression.Lexer} lexer Lexer. diff --git a/test/spec/ol/expression/parser.test.js b/test/spec/ol/expression/parser.test.js index 708e6d0018..d832dd60e6 100644 --- a/test/spec/ol/expression/parser.test.js +++ b/test/spec/ol/expression/parser.test.js @@ -222,6 +222,42 @@ describe('ol.expression.Parser', function() { }); + describe('#parseUnaryExpression_()', function() { + + function parse(source) { + var lexer = new ol.expression.Lexer(source); + var parser = new ol.expression.Parser(); + var expr = parser.parseUnaryExpression_(lexer); + expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + return expr; + } + + it('parses logical not', function() { + var expr = parse('!foo'); + expect(expr).to.be.a(ol.expression.Not); + expect(expr.evaluate({foo: true})).to.be(false); + }); + + it('works with string literal', function() { + var expr = parse('!"foo"'); + expect(expr).to.be.a(ol.expression.Not); + expect(expr.evaluate()).to.be(false); + }); + + it('works with empty string', function() { + var expr = parse('!""'); + expect(expr).to.be.a(ol.expression.Not); + expect(expr.evaluate()).to.be(true); + }); + + it('works with null', function() { + var expr = parse('!null'); + expect(expr).to.be.a(ol.expression.Not); + expect(expr.evaluate()).to.be(true); + }); + + }); + }); @@ -234,5 +270,6 @@ goog.require('ol.expression.Literal'); goog.require('ol.expression.Logical'); goog.require('ol.expression.Math'); goog.require('ol.expression.Member'); +goog.require('ol.expression.Not'); goog.require('ol.expression.Parser'); goog.require('ol.expression.TokenType'); From e6f03c8f221e492448052658c1b83038e8990ed8 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 16:09:26 -0600 Subject: [PATCH 45/84] Confirm whitespace is consumed as expected --- test/spec/ol/expression/expression.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index e1ef1d59c1..196b34c3dd 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -17,6 +17,12 @@ describe('ol.expression.parse', function() { expect(expr.evaluate({foo: 'bar'})).to.be('bar'); }); + it('consumes whitespace as expected', function() { + var expr = ol.expression.parse(' foo '); + expect(expr).to.be.a(ol.expression.Identifier); + expect(expr.evaluate({foo: 'bar'})).to.be('bar'); + }); + it('throws on invalid identifier expressions', function() { expect(function() { ol.expression.parse('3foo'); @@ -71,6 +77,13 @@ describe('ol.expression.parse', function() { expect(expr.evaluate(scope)).to.be(42); }); + it('consumes whitespace as expected', function() { + var expr = ol.expression.parse(' foo . bar . baz '); + expect(expr).to.be.a(ol.expression.Member); + var scope = {foo: {bar: {baz: 42}}}; + expect(expr.evaluate(scope)).to.be(42); + }); + it('throws on invalid member expression', function() { expect(function() { ol.expression.parse('foo.4bar'); From 2492515e8f3dbe93804d71bc590e1e854fb1e304 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 16:10:18 -0600 Subject: [PATCH 46/84] Integration testing for unary operators Only supporting ! at the moment. --- test/spec/ol/expression/expression.test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 196b34c3dd..39c9ac5b43 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -111,6 +111,26 @@ describe('ol.expression.parse', function() { }); + describe('unary operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.4 + + it('parses logical not operator', function() { + var expr = ol.expression.parse('!foo'); + expect(expr).to.be.a(ol.expression.Not); + expect(expr.evaluate({foo: true})).to.be(false); + expect(expr.evaluate({foo: false})).to.be(true); + expect(expr.evaluate({foo: ''})).to.be(true); + expect(expr.evaluate({foo: 'foo'})).to.be(false); + }); + + it('consumes whitespace as expected', function() { + var expr = ol.expression.parse(' ! foo'); + expect(expr).to.be.a(ol.expression.Not); + expect(expr.evaluate({foo: true})).to.be(false); + expect(expr.evaluate({foo: false})).to.be(true); + }); + + }); }); @@ -119,3 +139,4 @@ goog.require('ol.expression'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Literal'); +goog.require('ol.expression.Not'); From 7c2550fefae43121b60703b4d6e6329c883be8fe Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 16:11:06 -0600 Subject: [PATCH 47/84] Integration testing for multiplicative operators --- test/spec/ol/expression/expression.test.js | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 39c9ac5b43..5bfc6dc091 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -132,6 +132,46 @@ describe('ol.expression.parse', function() { }); + describe('multiplicitave operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.5 + + it('parses * operator', function() { + var expr = ol.expression.parse('foo*bar'); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 10, bar: 20})).to.be(200); + }); + + it('consumes whitespace as expected with *', function() { + var expr = ol.expression.parse(' foo * bar '); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 15, bar: 2})).to.be(30); + }); + + it('parses / operator', function() { + var expr = ol.expression.parse('foo/12'); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 10})).to.be(10 / 12); + }); + + it('consumes whitespace as expected with /', function() { + var expr = ol.expression.parse(' 4 / bar '); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({bar: 3})).to.be(4 / 3); + }); + + it('parses % operator', function() { + var expr = ol.expression.parse('12%foo'); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 10})).to.be(2); + }); + + it('consumes whitespace as expected with %', function() { + var expr = ol.expression.parse(' 4 %bar '); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({bar: 3})).to.be(1); + }); + + }); }); @@ -139,4 +179,5 @@ goog.require('ol.expression'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Literal'); +goog.require('ol.expression.Math'); goog.require('ol.expression.Not'); From 9d5424236c957541c91bb56e3513487e15789958 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 16:11:35 -0600 Subject: [PATCH 48/84] Integration testing for additive operators --- test/spec/ol/expression/expression.test.js | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 5bfc6dc091..d7e7c9147d 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -172,6 +172,35 @@ describe('ol.expression.parse', function() { }); }); + + describe('additive operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.6 + + it('parses + operator', function() { + var expr = ol.expression.parse('foo+bar'); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 10, bar: 20})).to.be(30); + }); + + it('consumes whitespace as expected with +', function() { + var expr = ol.expression.parse(' foo +10 '); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 15})).to.be(25); + }); + + it('parses - operator', function() { + var expr = ol.expression.parse('foo-bar'); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 10, bar: 20})).to.be(-10); + }); + + it('consumes whitespace as expected with -', function() { + var expr = ol.expression.parse(' foo- 10 '); + expect(expr).to.be.a(ol.expression.Math); + expect(expr.evaluate({foo: 15})).to.be(5); + }); + + }); }); From 3a3f9bc20b4e3f4332e5893d2e04e1f2ace6dd12 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 16:12:13 -0600 Subject: [PATCH 49/84] Integration testing for relational operators No `instanceof` or `in` operator support. --- test/spec/ol/expression/expression.test.js | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index d7e7c9147d..b3c965a76b 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -201,12 +201,93 @@ describe('ol.expression.parse', function() { }); }); + + describe('relational operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.8 + + it('parses < operator', function() { + var expr = ol.expression.parse('foo operator', function() { + var expr = ol.expression.parse('foo>bar'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 10, bar: 20})).to.be(false); + expect(expr.evaluate({foo: 100, bar: 20})).to.be(true); + }); + + it('consumes whitespace as expected with >', function() { + var expr = ol.expression.parse(' foo> 10 '); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 15})).to.be(true); + expect(expr.evaluate({foo: 5})).to.be(false); + }); + + it('parses <= operator', function() { + var expr = ol.expression.parse('foo<=bar'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 10, bar: 20})).to.be(true); + expect(expr.evaluate({foo: 100, bar: 20})).to.be(false); + expect(expr.evaluate({foo: 20, bar: 20})).to.be(true); + }); + + it('consumes whitespace as expected with <=', function() { + var expr = ol.expression.parse(' foo<= 10 '); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 15})).to.be(false); + expect(expr.evaluate({foo: 5})).to.be(true); + expect(expr.evaluate({foo: 10})).to.be(true); + }); + + it('throws for invalid spacing with <=', function() { + expect(function() { + ol.expression.parse(' foo< = 10 '); + }).throwException(); + }); + + it('parses >= operator', function() { + var expr = ol.expression.parse('foo>=bar'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 10, bar: 20})).to.be(false); + expect(expr.evaluate({foo: 100, bar: 20})).to.be(true); + expect(expr.evaluate({foo: 20, bar: 20})).to.be(true); + }); + + it('consumes whitespace as expected with >=', function() { + var expr = ol.expression.parse(' foo >=10 '); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 15})).to.be(true); + expect(expr.evaluate({foo: 5})).to.be(false); + expect(expr.evaluate({foo: 10})).to.be(true); + }); + + it('throws for invalid spacing with >=', function() { + expect(function() { + ol.expression.parse(' 10 > =foo '); + }).throwException(); + }); + + }); + }); goog.require('ol.expression'); +goog.require('ol.expression.Call'); +goog.require('ol.expression.Comparison'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Literal'); goog.require('ol.expression.Math'); +goog.require('ol.expression.Member'); goog.require('ol.expression.Not'); From d5e133b7d8bf1bd0b7562a1d66d6faaeaaec199f Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 18:14:48 -0600 Subject: [PATCH 50/84] Satisfy the `build/check-requires-timestamp` task (see #785) I think this line should not be required, as `ol.expression.Char` is only used internally in this file. --- src/ol/expression/lexer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index fab87330e9..11a5eec3b0 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -13,6 +13,7 @@ * Copyright (C) 2011 Ariya Hidayat */ +goog.provide('ol.expression.Char'); // TODO: remove this - see #785 goog.provide('ol.expression.Lexer'); goog.provide('ol.expression.Token'); goog.provide('ol.expression.TokenType'); From 973606e67a664795e6fee417e0d84f03836fe6eb Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 18:22:50 -0600 Subject: [PATCH 51/84] Integration tests for equality operators --- src/ol/expression/parser.js | 8 +- test/spec/ol/expression/expression.test.js | 92 ++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 056f6ddafd..ca0ae678d7 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -219,7 +219,13 @@ ol.expression.Parser.prototype.createUnaryExpression_ = function(op, expr) { */ ol.expression.Parser.prototype.parse = function(source) { var lexer = new ol.expression.Lexer(source); - return this.parseExpression_(lexer); + var expr = this.parseExpression_(lexer); + var token = lexer.peek(); + if (token.type !== ol.expression.TokenType.EOF) { + // TODO: token.index + throw new Error('Unexpected token: ' + token.value); + } + return expr; }; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index b3c965a76b..704c7e0d3e 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -279,6 +279,98 @@ describe('ol.expression.parse', function() { }); + describe('equality operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.9 + + it('parses == operator', function() { + var expr = ol.expression.parse('foo==42'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: 41})).to.be(false); + expect(expr.evaluate({foo: '42'})).to.be(true); + }); + + it('consumes whitespace as expected with ==', function() { + var expr = ol.expression.parse(' 42 ==foo '); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: 41})).to.be(false); + expect(expr.evaluate({foo: '42'})).to.be(true); + }); + + it('throws for invalid spacing with ==', function() { + expect(function() { + ol.expression.parse(' 10 = =foo '); + }).throwException(); + }); + + it('parses != operator', function() { + var expr = ol.expression.parse('foo!=42'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: 41})).to.be(true); + expect(expr.evaluate({foo: '42'})).to.be(false); + }); + + it('consumes whitespace as expected with !=', function() { + var expr = ol.expression.parse(' 42 !=foo '); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: 41})).to.be(true); + expect(expr.evaluate({foo: '42'})).to.be(false); + }); + + it('throws for invalid spacing with !=', function() { + expect(function() { + ol.expression.parse(' 10! =foo '); + }).throwException(); + }); + + it('parses === operator', function() { + var expr = ol.expression.parse('42===foo'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: 41})).to.be(false); + expect(expr.evaluate({foo: '42'})).to.be(false); + }); + + it('consumes whitespace as expected with ===', function() { + var expr = ol.expression.parse(' foo ===42 '); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: 41})).to.be(false); + expect(expr.evaluate({foo: '42'})).to.be(false); + }); + + it('throws for invalid spacing with ===', function() { + expect(function() { + ol.expression.parse(' 10 = == foo '); + }).throwException(); + }); + + it('parses !== operator', function() { + var expr = ol.expression.parse('foo!==42'); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: 41})).to.be(true); + expect(expr.evaluate({foo: '42'})).to.be(true); + }); + + it('consumes whitespace as expected with !==', function() { + var expr = ol.expression.parse(' 42 !== foo '); + expect(expr).to.be.a(ol.expression.Comparison); + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: 41})).to.be(true); + expect(expr.evaluate({foo: '42'})).to.be(true); + }); + + it('throws for invalid spacing with !==', function() { + expect(function() { + ol.expression.parse(' 10 != = foo '); + }).throwException(); + }); + }); + }); From 2fd8f4cb0099947f84b5193c8f474fa71c3c0388 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 21:42:00 -0600 Subject: [PATCH 52/84] Integration testing for binary logical operators --- test/spec/ol/expression/expression.test.js | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 704c7e0d3e..44256b923a 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -371,6 +371,59 @@ describe('ol.expression.parse', function() { }); }); + describe('binary logical operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.11 + + it('parses && operator', function() { + var expr = ol.expression.parse('foo&&bar'); + expect(expr).to.be.a(ol.expression.Logical); + expect(expr.evaluate({foo: true, bar: true})).to.be(true); + expect(expr.evaluate({foo: true, bar: false})).to.be(false); + expect(expr.evaluate({foo: false, bar: true})).to.be(false); + expect(expr.evaluate({foo: false, bar: false})).to.be(false); + }); + + it('consumes space as expected with &&', function() { + var expr = ol.expression.parse(' foo && bar '); + expect(expr).to.be.a(ol.expression.Logical); + expect(expr.evaluate({foo: true, bar: true})).to.be(true); + expect(expr.evaluate({foo: true, bar: false})).to.be(false); + expect(expr.evaluate({foo: false, bar: true})).to.be(false); + expect(expr.evaluate({foo: false, bar: false})).to.be(false); + }); + + it('throws for invalid spacing with &&', function() { + expect(function() { + ol.expression.parse('true & & false'); + }).throwException(); + }); + + it('parses || operator', function() { + var expr = ol.expression.parse('foo||bar'); + expect(expr).to.be.a(ol.expression.Logical); + expect(expr.evaluate({foo: true, bar: true})).to.be(true); + expect(expr.evaluate({foo: true, bar: false})).to.be(true); + expect(expr.evaluate({foo: false, bar: true})).to.be(true); + expect(expr.evaluate({foo: false, bar: false})).to.be(false); + }); + + it('consumes space as expected with ||', function() { + var expr = ol.expression.parse(' foo || bar '); + expect(expr).to.be.a(ol.expression.Logical); + expect(expr.evaluate({foo: true, bar: true})).to.be(true); + expect(expr.evaluate({foo: true, bar: false})).to.be(true); + expect(expr.evaluate({foo: false, bar: true})).to.be(true); + expect(expr.evaluate({foo: false, bar: false})).to.be(false); + }); + + it('throws for invalid spacing with ||', function() { + expect(function() { + ol.expression.parse('true | | false'); + }).throwException(); + }); + + }); + }); @@ -380,6 +433,7 @@ goog.require('ol.expression.Comparison'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Identifier'); goog.require('ol.expression.Literal'); +goog.require('ol.expression.Logical'); goog.require('ol.expression.Math'); goog.require('ol.expression.Member'); goog.require('ol.expression.Not'); From 6458f989962a13431f0ce5224e96157230b71dfd Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:05:32 -0600 Subject: [PATCH 53/84] Describing the extent of support for ES-5 expressions --- src/ol/expression/expression.exports | 1 + test/spec/ol/expression/expression.test.js | 49 ++++++++++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 src/ol/expression/expression.exports diff --git a/src/ol/expression/expression.exports b/src/ol/expression/expression.exports new file mode 100644 index 0000000000..8f9155e23f --- /dev/null +++ b/src/ol/expression/expression.exports @@ -0,0 +1 @@ +@exportSymbol ol.expression.parse diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 44256b923a..5f2cc57369 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -3,12 +3,12 @@ goog.provide('ol.test.expression'); describe('ol.expression.parse', function() { - it('parses a string and returns an expression', function() { + it('parses a subset of ECMAScript 5.1 expressions', function() { var expr = ol.expression.parse('foo'); expect(expr).to.be.a(ol.expression.Expression); }); - describe('primary expressions', function() { + describe('11.1 - primary expressions', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.1 it('parses identifier expressions', function() { @@ -67,7 +67,7 @@ describe('ol.expression.parse', function() { }); - describe('left-hand-side expressions', function() { + describe('11.2 - left-hand-side expressions', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 it('parses member expressions with dot notation', function() { @@ -111,7 +111,13 @@ describe('ol.expression.parse', function() { }); - describe('unary operators', function() { + describe('11.3 - postfix expressions', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.3 + it('not supported'); + }); + + + describe('11.4 - unary operators', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.4 it('parses logical not operator', function() { @@ -132,7 +138,7 @@ describe('ol.expression.parse', function() { }); - describe('multiplicitave operators', function() { + describe('11.5 - multiplicitave operators', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.5 it('parses * operator', function() { @@ -173,7 +179,7 @@ describe('ol.expression.parse', function() { }); - describe('additive operators', function() { + describe('11.6 - additive operators', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.6 it('parses + operator', function() { @@ -202,7 +208,12 @@ describe('ol.expression.parse', function() { }); - describe('relational operators', function() { + describe('11.7 - bitwise shift operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.7 + it('not supported'); + }); + + describe('11.8 - relational operators', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.8 it('parses < operator', function() { @@ -279,7 +290,7 @@ describe('ol.expression.parse', function() { }); - describe('equality operators', function() { + describe('11.9 - equality operators', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.9 it('parses == operator', function() { @@ -371,7 +382,12 @@ describe('ol.expression.parse', function() { }); }); - describe('binary logical operators', function() { + describe('11.10 - binary bitwise operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.10 + it('not supported'); + }); + + describe('11.11 - binary logical operators', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.11 it('parses && operator', function() { @@ -424,6 +440,21 @@ describe('ol.expression.parse', function() { }); + describe('11.12 - conditional operator', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.12 + it('not supported'); + }); + + describe('11.13 - assignment operators', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.13 + it('not supported'); + }); + + describe('11.14 - comma operator', function() { + // http://www.ecma-international.org/ecma-262/5.1/#sec-11.14 + it('not supported'); + }); + }); From be636d7f4685cd3d0247fc4f798ca228a69ae4c8 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:52:20 -0600 Subject: [PATCH 54/84] Getters for call expression properties --- src/ol/expression/expressions.js | 26 +++++++++++++++++---- test/spec/ol/expression/expressions.test.js | 16 +++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index 98a608ca2c..44c4435b5d 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -47,17 +47,17 @@ ol.expression.Expression.prototype.evaluate = goog.abstractMethod; * * @constructor * @extends {ol.expression.Expression} - * @param {ol.expression.Expression} expr An expression that resolves to a + * @param {ol.expression.Expression} callee An expression that resolves to a * function. * @param {Array.} args Arguments. */ -ol.expression.Call = function(expr, args) { +ol.expression.Call = function(callee, args) { /** * @type {ol.expression.Expression} * @private */ - this.expr_ = expr; + this.callee_ = callee; /** * @type {Array.} @@ -74,7 +74,7 @@ goog.inherits(ol.expression.Call, ol.expression.Expression); */ ol.expression.Call.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { var fnScope = goog.isDefAndNotNull(opt_fns) ? opt_fns : opt_scope; - var fn = this.expr_.evaluate(fnScope); + var fn = this.callee_.evaluate(fnScope); if (!fn || !goog.isFunction(fn)) { throw new Error('Expected function but found ' + fn); } @@ -89,6 +89,24 @@ ol.expression.Call.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { }; +/** + * Get the argument list. + * @return {Array.} The argument. + */ +ol.expression.Call.prototype.getArgs = function() { + return this.args_; +}; + + +/** + * Get the callee expression. + * @return {ol.expression.Expression} The callee expression. + */ +ol.expression.Call.prototype.getCallee = function() { + return this.callee_; +}; + + /** * @enum {string} */ diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index 1e924fd8e7..7d589f7bae 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -67,6 +67,22 @@ describe('ol.expression.Call', function() { }); + var callee = new ol.expression.Identifier('sqrt'); + var args = [new ol.expression.Literal(42)]; + var expr = new ol.expression.Call(callee, args); + + describe('#getArgs()', function() { + it('gets the callee expression', function() { + expect(expr.getArgs()).to.be(args); + }); + }); + + describe('#getCallee()', function() { + it('gets the callee expression', function() { + expect(expr.getCallee()).to.be(callee); + }); + }); + }); From 582a52849c5ac186b7c2321dd7d0bb0c3f756f72 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:53:04 -0600 Subject: [PATCH 55/84] Getters for comparison expression properties --- src/ol/expression/expressions.js | 27 +++++++++++++++++++++ test/spec/ol/expression/expressions.test.js | 23 ++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index 44c4435b5d..cb3d77c009 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -213,6 +213,33 @@ ol.expression.Comparison.prototype.evaluate = function(opt_scope, opt_this, }; +/** + * Get the comparison operator. + * @return {string} The comparison operator. + */ +ol.expression.Comparison.prototype.getOperator = function() { + return this.operator_; +}; + + +/** + * Get the left expression. + * @return {ol.expression.Expression} The left expression. + */ +ol.expression.Comparison.prototype.getLeft = function() { + return this.left_; +}; + + +/** + * Get the right expression. + * @return {ol.expression.Expression} The right expression. + */ +ol.expression.Comparison.prototype.getRight = function() { + return this.right_; +}; + + /** * An identifier expression (e.g. `foo`). diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index 7d589f7bae..7b6fc80ebe 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -211,6 +211,29 @@ describe('ol.expression.Comparison', function() { }); }); + var op = ol.expression.ComparisonOp.LTE; + var left = new ol.expression.Identifier('foo'); + var right = new ol.expression.Literal(42); + var expr = new ol.expression.Comparison(op, left, right); + + describe('#getOperator()', function() { + it('gets the operator', function() { + expect(expr.getOperator()).to.be(op); + }); + }); + + describe('#getLeft()', function() { + it('gets the left expression', function() { + expect(expr.getLeft()).to.be(left); + }); + }); + + describe('#getRight()', function() { + it('gets the right expression', function() { + expect(expr.getRight()).to.be(right); + }); + }); + }); describe('ol.expression.Identifier', function() { From 6d6ec357e17438e2c1798785d3f7a7beda8b9ad6 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:53:39 -0600 Subject: [PATCH 56/84] Getter for identifier expression name --- src/ol/expression/expressions.js | 9 +++++++++ test/spec/ol/expression/expressions.test.js | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index cb3d77c009..7156af2acb 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -271,6 +271,15 @@ ol.expression.Identifier.prototype.evaluate = function(opt_scope) { }; +/** + * Get the identifier name. + * @return {string} The identifier name. + */ +ol.expression.Identifier.prototype.getName = function() { + return this.name_; +}; + + /** * A literal expression (e.g. `"chicken"`, `42`, `true`, `null`). diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index 7b6fc80ebe..2820c6d020 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -274,6 +274,11 @@ describe('ol.expression.Identifier', function() { }); }); + describe('#getName()', function() { + var expr = new ol.expression.Identifier('asdf'); + expect(expr.getName()).to.be('asdf'); + }); + }); describe('ol.expression.Literal', function() { From 3de330eb99a7083bee27d0decfe01db400bb2a57 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:54:07 -0600 Subject: [PATCH 57/84] Getter for literal expression value --- src/ol/expression/expressions.js | 9 +++++++++ test/spec/ol/expression/expressions.test.js | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index 7156af2acb..f3bbbfb5e9 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -308,6 +308,15 @@ ol.expression.Literal.prototype.evaluate = function() { }; +/** + * Get the literal value. + * @return {string|number|boolean|null} The literal value. + */ +ol.expression.Literal.prototype.getValue = function() { + return this.value_; +}; + + /** * @enum {string} */ diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index 2820c6d020..2fd8efcf87 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -312,6 +312,12 @@ describe('ol.expression.Literal', function() { expect(expr.evaluate()).to.be(null); }); }); + + describe('#getValue()', function() { + var expr = new ol.expression.Literal('asdf'); + expect(expr.getValue()).to.be('asdf'); + }); + }); From 4a617871da453d90c2b6040527c418fa499c9449 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:54:37 -0600 Subject: [PATCH 58/84] Getters for logical expression properties --- src/ol/expression/expressions.js | 27 +++++++++++++++++++++ test/spec/ol/expression/expressions.test.js | 23 ++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index f3bbbfb5e9..e0c384f5e1 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -396,6 +396,33 @@ ol.expression.Logical.prototype.evaluate = function(opt_scope, opt_fns, }; +/** + * Get the logical operator. + * @return {string} The logical operator. + */ +ol.expression.Logical.prototype.getOperator = function() { + return this.operator_; +}; + + +/** + * Get the left expression. + * @return {ol.expression.Expression} The left expression. + */ +ol.expression.Logical.prototype.getLeft = function() { + return this.left_; +}; + + +/** + * Get the right expression. + * @return {ol.expression.Expression} The right expression. + */ +ol.expression.Logical.prototype.getRight = function() { + return this.right_; +}; + + /** * @enum {string} */ diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index 2fd8efcf87..189f42d3d5 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -372,6 +372,29 @@ describe('ol.expression.Logical', function() { }); }); + var op = ol.expression.LogicalOp.AND; + var left = new ol.expression.Identifier('foo'); + var right = new ol.expression.Literal(false); + var expr = new ol.expression.Logical(op, left, right); + + describe('#getOperator()', function() { + it('gets the operator', function() { + expect(expr.getOperator()).to.be(op); + }); + }); + + describe('#getLeft()', function() { + it('gets the left expression', function() { + expect(expr.getLeft()).to.be(left); + }); + }); + + describe('#getRight()', function() { + it('gets the right expression', function() { + expect(expr.getRight()).to.be(right); + }); + }); + }); describe('ol.expression.Math', function() { From 2528581642116f4eb685d61e901a6a258aac25d9 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:55:13 -0600 Subject: [PATCH 59/84] Getters for math expression properties --- src/ol/expression/expressions.js | 27 +++++++++++++++++++++ test/spec/ol/expression/expressions.test.js | 22 +++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index e0c384f5e1..a8675d1365 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -521,6 +521,33 @@ ol.expression.Math.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { }; +/** + * Get the math operator. + * @return {string} The math operator. + */ +ol.expression.Math.prototype.getOperator = function() { + return this.operator_; +}; + + +/** + * Get the left expression. + * @return {ol.expression.Expression} The left expression. + */ +ol.expression.Math.prototype.getLeft = function() { + return this.left_; +}; + + +/** + * Get the right expression. + * @return {ol.expression.Expression} The right expression. + */ +ol.expression.Math.prototype.getRight = function() { + return this.right_; +}; + + /** * A member expression (e.g. `foo.bar`). diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index 189f42d3d5..ecb85a29fc 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -510,6 +510,28 @@ describe('ol.expression.Math', function() { }); }); + var op = ol.expression.MathOp.MOD; + var left = new ol.expression.Identifier('foo'); + var right = new ol.expression.Literal(20); + var expr = new ol.expression.Math(op, left, right); + + describe('#getOperator()', function() { + it('gets the operator', function() { + expect(expr.getOperator()).to.be(op); + }); + }); + + describe('#getLeft()', function() { + it('gets the left expression', function() { + expect(expr.getLeft()).to.be(left); + }); + }); + + describe('#getRight()', function() { + it('gets the right expression', function() { + expect(expr.getRight()).to.be(right); + }); + }); }); From c05fb8c61ba2cbf3029a99b7b10eeb46133c492a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:56:17 -0600 Subject: [PATCH 60/84] Getters for member expression properties --- src/ol/expression/expressions.js | 26 +++++++++++++++++---- test/spec/ol/expression/expressions.test.js | 13 +++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index a8675d1365..7f51393d74 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -554,17 +554,17 @@ ol.expression.Math.prototype.getRight = function() { * * @constructor * @extends {ol.expression.Expression} - * @param {ol.expression.Expression} expr An expression that resolves to an + * @param {ol.expression.Expression} object An expression that resolves to an * object. * @param {ol.expression.Identifier} property Identifier with name of property. */ -ol.expression.Member = function(expr, property) { +ol.expression.Member = function(object, property) { /** * @type {ol.expression.Expression} * @private */ - this.expr_ = expr; + this.object_ = object; /** * @type {ol.expression.Identifier} @@ -581,7 +581,7 @@ goog.inherits(ol.expression.Member, ol.expression.Expression); */ ol.expression.Member.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { - var obj = this.expr_.evaluate(opt_scope, opt_fns, opt_this); + var obj = this.object_.evaluate(opt_scope, opt_fns, opt_this); if (!goog.isObject(obj)) { throw new Error('Expected member expression to evaluate to an object ' + 'but got ' + obj); @@ -590,6 +590,24 @@ ol.expression.Member.prototype.evaluate = function(opt_scope, opt_fns, }; +/** + * Get the object expression. + * @return {ol.expression.Expression} The object. + */ +ol.expression.Member.prototype.getObject = function() { + return this.object_; +}; + + +/** + * Get the property expression. + * @return {ol.expression.Identifier} The property. + */ +ol.expression.Member.prototype.getProperty = function() { + return this.property_; +}; + + /** * A logical not expression (e.g. `!foo`). diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index ecb85a29fc..588ac2220c 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -558,6 +558,19 @@ describe('ol.expression.Member', function() { expect(expr.evaluate(scope)).to.be(42); }); }); + + var object = new ol.expression.Identifier('foo'); + var property = new ol.expression.Identifier('bar'); + var expr = new ol.expression.Member(object, property); + + describe('#getObject()', function() { + expect(expr.getObject()).to.be(object); + }); + + describe('#getProperty()', function() { + expect(expr.getProperty()).to.be(property); + }); + }); From bb1b0cba9527264a285d65b139862a4c12353e5e Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 11 Jun 2013 22:56:48 -0600 Subject: [PATCH 61/84] Getter for not expression argument --- src/ol/expression/expressions.js | 17 +++++++++++++---- test/spec/ol/expression/expressions.test.js | 6 ++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index 7f51393d74..ebd01dbf9e 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -614,15 +614,15 @@ ol.expression.Member.prototype.getProperty = function() { * * @constructor * @extends {ol.expression.Expression} - * @param {ol.expression.Expression} expr Expression to negate. + * @param {ol.expression.Expression} argument Expression to negate. */ -ol.expression.Not = function(expr) { +ol.expression.Not = function(argument) { /** * @type {ol.expression.Expression} * @private */ - this.expr_ = expr; + this.argument_ = argument; }; goog.inherits(ol.expression.Not, ol.expression.Expression); @@ -632,5 +632,14 @@ goog.inherits(ol.expression.Not, ol.expression.Expression); * @inheritDoc */ ol.expression.Not.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { - return !this.expr_.evaluate(opt_scope, opt_fns, opt_this); + return !this.argument_.evaluate(opt_scope, opt_fns, opt_this); +}; + + +/** + * Get the argument (the negated expression). + * @return {ol.expression.Expression} The argument. + */ +ol.expression.Not.prototype.getArgument = function() { + return this.argument_; }; diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js index 588ac2220c..fa49ec31a4 100644 --- a/test/spec/ol/expression/expressions.test.js +++ b/test/spec/ol/expression/expressions.test.js @@ -615,6 +615,12 @@ describe('ol.expression.Not', function() { }); }); + describe('#getArgument()', function() { + var argument = new ol.expression.Literal(true); + var expr = new ol.expression.Not(argument); + expect(expr.getArgument()).to.be(argument); + }); + }); From 62eb0dd72baf45d390d950379188ae738979309b Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 12 Jun 2013 14:52:31 -0600 Subject: [PATCH 62/84] Give token an index and throw unexpected token from a common place --- src/ol/expression/lexer.js | 128 +++++++++++++++------ src/ol/expression/parser.js | 31 +++-- test/spec/ol/expression/expression.test.js | 105 ++++++++++++++--- test/spec/ol/expression/lexer.test.js | 16 ++- 4 files changed, 213 insertions(+), 67 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 11a5eec3b0..7d1e0edd7f 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -82,13 +82,15 @@ ol.expression.TokenType = { NULL_LITERAL: 'Null', NUMERIC_LITERAL: 'Numeric', PUNCTUATOR: 'Punctuator', - STRING_LITERAL: 'String' + STRING_LITERAL: 'String', + UNKNOWN: 'Unknown' }; /** * @typedef {{type: (ol.expression.TokenType), - * value: (string|number|boolean|null)}} + * value: (string|number|boolean|null), + * index: (number)}} */ ol.expression.Token; @@ -140,11 +142,14 @@ ol.expression.Lexer = function(source) { */ ol.expression.Lexer.prototype.expect = function(value) { var match = this.match(value); - this.skip(); if (!match) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + this.getCurrentChar_()); + this.throwUnexpected({ + type: ol.expression.TokenType.UNKNOWN, + value: this.getCurrentChar_(), + index: this.index_ + }); } + this.skip(); }; @@ -388,7 +393,8 @@ ol.expression.Lexer.prototype.next = function() { if (this.index_ >= this.length_) { return { type: ol.expression.TokenType.EOF, - value: null + value: null, + index: this.index_ }; } @@ -450,6 +456,7 @@ ol.expression.Lexer.prototype.peek = function() { */ ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { var str = ''; + var start = this.index_; while (this.index_ < this.length_) { if (!this.isHexDigit_(code)) { @@ -460,21 +467,20 @@ ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { code = this.getCurrentCharCode_(); } - if (str.length === 0) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + String.fromCharCode(code)); - } - - if (this.isIdentifierStart_(code)) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + String.fromCharCode(code)); + if (str.length === 0 || this.isIdentifierStart_(code)) { + this.throwUnexpected({ + type: ol.expression.TokenType.UNKNOWN, + value: String.fromCharCode(code), + index: this.index_ + }); } goog.asserts.assert(!isNaN(parseInt('0x' + str, 16)), 'Valid hex: ' + str); return { type: ol.expression.TokenType.NUMERIC_LITERAL, - value: parseInt('0x' + str, 16) + value: parseInt('0x' + str, 16), + index: start }; }; @@ -518,7 +524,8 @@ ol.expression.Lexer.prototype.scanIdentifier_ = function(code) { return { type: type, - value: id + value: id, + index: start }; }; @@ -538,6 +545,7 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { // start assembling numeric string var str = ''; + var start = this.index_; if (code !== ol.expression.Char.DOT) { @@ -559,8 +567,11 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { // numbers like 09 not allowed if (this.isDecimalDigit_(nextCode)) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + String.fromCharCode(nextCode)); + this.throwUnexpected({ + type: ol.expression.TokenType.UNKNOWN, + value: String.fromCharCode(nextCode), + index: this.index_ + }); } } @@ -601,8 +612,11 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { } if (!this.isDecimalDigit_(code)) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + String.fromCharCode(code)); + this.throwUnexpected({ + type: ol.expression.TokenType.UNKNOWN, + value: String.fromCharCode(code), + index: this.index_ + }); } // scan all decimal chars (TODO: unduplicate this) @@ -614,15 +628,19 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { } if (this.isIdentifierStart_(code)) { - throw new Error('Unexpected token at index ' + this.index_ + - ': ' + String.fromCharCode(code)); + this.throwUnexpected({ + type: ol.expression.TokenType.UNKNOWN, + value: String.fromCharCode(code), + index: this.index_ + }); } goog.asserts.assert(!isNaN(parseFloat(str)), 'Valid number: ' + str); return { type: ol.expression.TokenType.NUMERIC_LITERAL, - value: parseFloat(str) + value: parseFloat(str), + index: start }; }; @@ -639,6 +657,7 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { goog.asserts.assert(this.isOctalDigit_(code)); var str = '0' + String.fromCharCode(code); + var start = this.index_; this.increment_(1); while (this.index_ < this.length_) { @@ -653,15 +672,19 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { code = this.getCurrentCharCode_(); if (this.isIdentifierStart_(code) || this.isDecimalDigit_(code)) { - throw new Error('Unexpected token at index ' + (this.index_ - 1) + - ': ' + String.fromCharCode(code)); + this.throwUnexpected({ + type: ol.expression.TokenType.UNKNOWN, + value: String.fromCharCode(code), + index: this.index_ - 1 + }); } goog.asserts.assert(!isNaN(parseInt(str, 8)), 'Valid octal: ' + str); return { type: ol.expression.TokenType.NUMERIC_LITERAL, - value: parseInt(str, 8) + value: parseInt(str, 8), + index: start }; }; @@ -674,6 +697,7 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { * @private */ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { + var start = this.index_; // single char punctuation that also doesn't start longer punctuation // (we disallow assignment, so no += etc.) @@ -691,7 +715,8 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { this.increment_(1); return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(code) + value: String.fromCharCode(code), + index: start }; } @@ -709,13 +734,15 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { this.increment_(1); return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(code) + '==' + value: String.fromCharCode(code) + '==', + index: start }; } else { // != or == return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(code) + '=' + value: String.fromCharCode(code) + '=', + index: start }; } } @@ -725,7 +752,8 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { this.increment_(2); return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(code) + '=' + value: String.fromCharCode(code) + '=', + index: start }; } } @@ -739,7 +767,8 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { var str = String.fromCharCode(code); return { type: ol.expression.TokenType.PUNCTUATOR, - value: str + str + value: str + str, + index: start }; } @@ -756,12 +785,24 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { this.increment_(1); return { type: ol.expression.TokenType.PUNCTUATOR, - value: String.fromCharCode(code) + value: String.fromCharCode(code), + index: start }; } - throw new Error('Unexpected token at index ' + (this.index_ - 1) + - ': ' + String.fromCharCode(code)); + this.throwUnexpected({ + type: ol.expression.TokenType.UNKNOWN, + value: String.fromCharCode(code), + index: this.index_ + }); + + // This code is unreachable, but the compiler complains with + // JSC_MISSING_RETURN_STATEMENT without it. + return { + type: ol.expression.TokenType.UNKNOWN, + value: '', + index: 0 + }; }; @@ -780,6 +821,7 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { this.increment_(1); var str = ''; + var start = this.index_; var code; while (this.index_ < this.length_) { code = this.getCurrentCharCode_(); @@ -798,12 +840,14 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { } if (quote !== 0) { - throw new Error('Unterminated string literal'); + // unterminated string literal + this.throwUnexpected(this.peek()); } return { type: ol.expression.TokenType.STRING_LITERAL, - value: str + value: str, + index: start }; }; @@ -833,3 +877,15 @@ ol.expression.Lexer.prototype.skipWhitespace_ = function() { } return code; }; + + +/** + * Throw an error about an unexpected token. + * @param {ol.expression.Token} token The unexpected token. + */ +ol.expression.Lexer.prototype.throwUnexpected = function(token) { + var error = new Error('Unexpected token at ' + token.index + ' : ' + + token.value); + error.token = token; + throw error; +}; diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index ca0ae678d7..615aca3a61 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -148,13 +148,13 @@ ol.expression.Parser.prototype.createBinaryExpression_ = function(operator, /** * Create a call expression. * - * @param {ol.expression.Identifier} expr Identifier expression for function. + * @param {ol.expression.Expression} callee Expression for function. * @param {Array.} args Arguments array. * @return {ol.expression.Call} Call expression. * @private */ -ol.expression.Parser.prototype.createCallExpression_ = function(expr, args) { - return new ol.expression.Call(expr, args); +ol.expression.Parser.prototype.createCallExpression_ = function(callee, args) { + return new ol.expression.Call(callee, args); }; @@ -186,14 +186,14 @@ ol.expression.Parser.prototype.createLiteral_ = function(value) { * Create a member expression. * * // TODO: make exp {ol.expression.Member|ol.expression.Identifier} - * @param {ol.expression.Expression} expr Expression. + * @param {ol.expression.Expression} object Expression. * @param {ol.expression.Identifier} property Member name. * @return {ol.expression.Member} The member expression. * @private */ -ol.expression.Parser.prototype.createMemberExpression_ = function(expr, +ol.expression.Parser.prototype.createMemberExpression_ = function(object, property) { - return new ol.expression.Member(expr, property); + return new ol.expression.Member(object, property); }; @@ -201,13 +201,13 @@ ol.expression.Parser.prototype.createMemberExpression_ = function(expr, * Create a unary expression. * * @param {string} op Operator. - * @param {ol.expression.Expression} expr Expression. + * @param {ol.expression.Expression} argument Expression. * @return {ol.expression.Not} The logical not of the input expression. * @private */ -ol.expression.Parser.prototype.createUnaryExpression_ = function(op, expr) { +ol.expression.Parser.prototype.createUnaryExpression_ = function(op, argument) { goog.asserts.assert(op === '!'); - return new ol.expression.Not(expr); + return new ol.expression.Not(argument); }; @@ -222,8 +222,7 @@ ol.expression.Parser.prototype.parse = function(source) { var expr = this.parseExpression_(lexer); var token = lexer.peek(); if (token.type !== ol.expression.TokenType.EOF) { - // TODO: token.index - throw new Error('Unexpected token: ' + token.value); + lexer.throwUnexpected(token); } return expr; }; @@ -353,9 +352,8 @@ ol.expression.Parser.prototype.parseLeftHandSideExpression_ = function(lexer) { if (token.value === '(') { // only allow calls on identifiers (e.g. `foo()` not `foo.bar()`) if (!(expr instanceof ol.expression.Identifier)) { - // TODO: token.index // TODO: more helpful error messages for restricted syntax - throw new Error('Unexpected token: ('); + lexer.throwUnexpected(token); } var args = this.parseArguments_(lexer); expr = this.createCallExpression_(expr, args); @@ -387,8 +385,7 @@ ol.expression.Parser.prototype.parseNonComputedMember_ = function(lexer) { token.type !== ol.expression.TokenType.KEYWORD && token.type !== ol.expression.TokenType.BOOLEAN_LITERAL && token.type !== ol.expression.TokenType.NULL_LITERAL) { - // TODO: token.index - throw new Error('Unexpected token: ' + token.value); + lexer.throwUnexpected(token); } return this.createIdentifier_(String(token.value)); @@ -423,8 +420,10 @@ ol.expression.Parser.prototype.parsePrimaryExpression_ = function(lexer) { } else if (type === ol.expression.TokenType.NULL_LITERAL) { expr = this.createLiteral_(null); } else { - throw new Error('Unexpected token: ' + token.value); + lexer.throwUnexpected(token); } + // the compiler doesn't recognize that we have covered all cases here + goog.asserts.assert(goog.isDef(expr)); return expr; }; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 5f2cc57369..d07f7c1129 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -26,7 +26,13 @@ describe('ol.expression.parse', function() { it('throws on invalid identifier expressions', function() { expect(function() { ol.expression.parse('3foo'); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('f'); + expect(token.index).to.be(1); + }); }); it('parses string literal expressions', function() { @@ -38,7 +44,13 @@ describe('ol.expression.parse', function() { it('throws on unterminated string', function() { expect(function() { ol.expression.parse('"foo'); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.type).to.be(ol.expression.TokenType.EOF); + expect(token.index).to.be(4); + }); }); it('parses numeric literal expressions', function() { @@ -50,7 +62,13 @@ describe('ol.expression.parse', function() { it('throws on invalid number', function() { expect(function() { ol.expression.parse('.42eX'); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('X'); + expect(token.index).to.be(4); + }); }); it('parses boolean literal expressions', function() { @@ -87,7 +105,13 @@ describe('ol.expression.parse', function() { it('throws on invalid member expression', function() { expect(function() { ol.expression.parse('foo.4bar'); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('b'); + expect(token.index).to.be(5); + }); }); it('parses call expressions with literal arguments', function() { @@ -106,7 +130,13 @@ describe('ol.expression.parse', function() { it('throws on calls with unterminated arguments', function() { expect(function() { ol.expression.parse('foo(42,)'); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be(')'); + expect(token.index).to.be(7); + }); }); }); @@ -263,7 +293,13 @@ describe('ol.expression.parse', function() { it('throws for invalid spacing with <=', function() { expect(function() { ol.expression.parse(' foo< = 10 '); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('='); + expect(token.index).to.be(6); + }); }); it('parses >= operator', function() { @@ -285,7 +321,13 @@ describe('ol.expression.parse', function() { it('throws for invalid spacing with >=', function() { expect(function() { ol.expression.parse(' 10 > =foo '); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('='); + expect(token.index).to.be(6); + }); }); }); @@ -312,7 +354,13 @@ describe('ol.expression.parse', function() { it('throws for invalid spacing with ==', function() { expect(function() { ol.expression.parse(' 10 = =foo '); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('='); + expect(token.index).to.be(4); + }); }); it('parses != operator', function() { @@ -334,7 +382,13 @@ describe('ol.expression.parse', function() { it('throws for invalid spacing with !=', function() { expect(function() { ol.expression.parse(' 10! =foo '); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('!'); + expect(token.index).to.be(3); + }); }); it('parses === operator', function() { @@ -356,7 +410,13 @@ describe('ol.expression.parse', function() { it('throws for invalid spacing with ===', function() { expect(function() { ol.expression.parse(' 10 = == foo '); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('='); + expect(token.index).to.be(4); + }); }); it('parses !== operator', function() { @@ -378,7 +438,13 @@ describe('ol.expression.parse', function() { it('throws for invalid spacing with !==', function() { expect(function() { ol.expression.parse(' 10 != = foo '); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('='); + expect(token.index).to.be(7); + }); }); }); @@ -411,7 +477,13 @@ describe('ol.expression.parse', function() { it('throws for invalid spacing with &&', function() { expect(function() { ol.expression.parse('true & & false'); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('&'); + expect(token.index).to.be(5); + }); }); it('parses || operator', function() { @@ -435,7 +507,13 @@ describe('ol.expression.parse', function() { it('throws for invalid spacing with ||', function() { expect(function() { ol.expression.parse('true | | false'); - }).throwException(); + }).throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.value).to.be('|'); + expect(token.index).to.be(5); + }); }); }); @@ -468,3 +546,4 @@ goog.require('ol.expression.Logical'); goog.require('ol.expression.Math'); goog.require('ol.expression.Member'); goog.require('ol.expression.Not'); +goog.require('ol.expression.TokenType'); diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index 370b48ae2a..bf0977d1ce 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -378,7 +378,13 @@ describe('ol.expression.Lexer', function() { it('throws on unterminated double quote', function() { expect(function() { scan('"never \'ending\' string'); - }).to.throwException(); + }).to.throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.type).to.be(ol.expression.TokenType.EOF); + expect(token.index).to.be(22); + }); }); it('parses single quoted string', function() { @@ -426,7 +432,13 @@ describe('ol.expression.Lexer', function() { it('throws on unterminated single quote', function() { expect(function() { scan('\'never "ending" string'); - }).to.throwException(); + }).to.throwException(function(err) { + expect(err).to.be.an(Error); + var token = err.token; + expect(token).not.to.be(undefined); + expect(token.type).to.be(ol.expression.TokenType.EOF); + expect(token.index).to.be(22); + }); }); }); From 36d10bef11581cc6c1c9afe15550b2aab548dc50 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 12 Jun 2013 16:55:02 -0600 Subject: [PATCH 63/84] Custom error for unexpected token This makes it clearer to the compiler where we are throwing. --- src/ol/expression/lexer.js | 53 ++++++++++++++++++++++--------------- src/ol/expression/parser.js | 11 ++++---- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 7d1e0edd7f..9b17e00845 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -17,8 +17,10 @@ goog.provide('ol.expression.Char'); // TODO: remove this - see #785 goog.provide('ol.expression.Lexer'); goog.provide('ol.expression.Token'); goog.provide('ol.expression.TokenType'); +goog.provide('ol.expression.UnexpectedToken'); goog.require('goog.asserts'); +goog.require('goog.debug.Error'); /** @@ -143,7 +145,7 @@ ol.expression.Lexer = function(source) { ol.expression.Lexer.prototype.expect = function(value) { var match = this.match(value); if (!match) { - this.throwUnexpected({ + throw new ol.expression.UnexpectedToken({ type: ol.expression.TokenType.UNKNOWN, value: this.getCurrentChar_(), index: this.index_ @@ -468,7 +470,7 @@ ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { } if (str.length === 0 || this.isIdentifierStart_(code)) { - this.throwUnexpected({ + throw new ol.expression.UnexpectedToken({ type: ol.expression.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ @@ -567,7 +569,7 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { // numbers like 09 not allowed if (this.isDecimalDigit_(nextCode)) { - this.throwUnexpected({ + throw new ol.expression.UnexpectedToken({ type: ol.expression.TokenType.UNKNOWN, value: String.fromCharCode(nextCode), index: this.index_ @@ -612,7 +614,7 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { } if (!this.isDecimalDigit_(code)) { - this.throwUnexpected({ + throw new ol.expression.UnexpectedToken({ type: ol.expression.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ @@ -628,7 +630,7 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { } if (this.isIdentifierStart_(code)) { - this.throwUnexpected({ + throw new ol.expression.UnexpectedToken({ type: ol.expression.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ @@ -672,7 +674,7 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { code = this.getCurrentCharCode_(); if (this.isIdentifierStart_(code) || this.isDecimalDigit_(code)) { - this.throwUnexpected({ + throw new ol.expression.UnexpectedToken({ type: ol.expression.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ - 1 @@ -790,19 +792,11 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { }; } - this.throwUnexpected({ + throw new ol.expression.UnexpectedToken({ type: ol.expression.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ }); - - // This code is unreachable, but the compiler complains with - // JSC_MISSING_RETURN_STATEMENT without it. - return { - type: ol.expression.TokenType.UNKNOWN, - value: '', - index: 0 - }; }; @@ -841,7 +835,7 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { if (quote !== 0) { // unterminated string literal - this.throwUnexpected(this.peek()); + throw new ol.expression.UnexpectedToken(this.peek()); } return { @@ -879,13 +873,28 @@ ol.expression.Lexer.prototype.skipWhitespace_ = function() { }; + /** - * Throw an error about an unexpected token. + * Error object for unexpected tokens. * @param {ol.expression.Token} token The unexpected token. + * @param {string=} opt_message Custom error message. + * @constructor + * @extends {goog.debug.Error} */ -ol.expression.Lexer.prototype.throwUnexpected = function(token) { - var error = new Error('Unexpected token at ' + token.index + ' : ' + - token.value); - error.token = token; - throw error; +ol.expression.UnexpectedToken = function(token, opt_message) { + var message = goog.isDef(opt_message) ? opt_message : + 'Unexpected token ' + token.value + ' at index ' + token.index; + + goog.debug.Error.call(this, message); + + /** + * @type {ol.expression.Token} + */ + this.token = token; + }; +goog.inherits(ol.expression.UnexpectedToken, goog.debug.Error); + + +/** @override */ +ol.expression.UnexpectedToken.prototype.name = 'UnexpectedToken'; diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 615aca3a61..57627039b8 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -32,6 +32,7 @@ goog.require('ol.expression.Member'); goog.require('ol.expression.Not'); goog.require('ol.expression.Token'); goog.require('ol.expression.TokenType'); +goog.require('ol.expression.UnexpectedToken'); @@ -222,7 +223,7 @@ ol.expression.Parser.prototype.parse = function(source) { var expr = this.parseExpression_(lexer); var token = lexer.peek(); if (token.type !== ol.expression.TokenType.EOF) { - lexer.throwUnexpected(token); + throw new ol.expression.UnexpectedToken(token); } return expr; }; @@ -353,7 +354,7 @@ ol.expression.Parser.prototype.parseLeftHandSideExpression_ = function(lexer) { // only allow calls on identifiers (e.g. `foo()` not `foo.bar()`) if (!(expr instanceof ol.expression.Identifier)) { // TODO: more helpful error messages for restricted syntax - lexer.throwUnexpected(token); + throw new ol.expression.UnexpectedToken(token); } var args = this.parseArguments_(lexer); expr = this.createCallExpression_(expr, args); @@ -385,7 +386,7 @@ ol.expression.Parser.prototype.parseNonComputedMember_ = function(lexer) { token.type !== ol.expression.TokenType.KEYWORD && token.type !== ol.expression.TokenType.BOOLEAN_LITERAL && token.type !== ol.expression.TokenType.NULL_LITERAL) { - lexer.throwUnexpected(token); + throw new ol.expression.UnexpectedToken(token); } return this.createIdentifier_(String(token.value)); @@ -420,10 +421,8 @@ ol.expression.Parser.prototype.parsePrimaryExpression_ = function(lexer) { } else if (type === ol.expression.TokenType.NULL_LITERAL) { expr = this.createLiteral_(null); } else { - lexer.throwUnexpected(token); + throw new ol.expression.UnexpectedToken(token); } - // the compiler doesn't recognize that we have covered all cases here - goog.asserts.assert(goog.isDef(expr)); return expr; }; From f0567f505335d685d3285bb2fe8901664e42359a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 12 Jun 2013 17:00:16 -0600 Subject: [PATCH 64/84] Expect the unexpected --- test/spec/ol/expression/expression.test.js | 39 ++++++++-------------- test/spec/ol/expression/lexer.test.js | 7 ++-- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index d07f7c1129..f353a6b004 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -27,9 +27,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse('3foo'); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('f'); expect(token.index).to.be(1); }); @@ -45,9 +44,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse('"foo'); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.type).to.be(ol.expression.TokenType.EOF); expect(token.index).to.be(4); }); @@ -63,9 +61,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse('.42eX'); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('X'); expect(token.index).to.be(4); }); @@ -106,9 +103,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse('foo.4bar'); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('b'); expect(token.index).to.be(5); }); @@ -131,9 +127,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse('foo(42,)'); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be(')'); expect(token.index).to.be(7); }); @@ -294,9 +289,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse(' foo< = 10 '); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('='); expect(token.index).to.be(6); }); @@ -322,9 +316,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse(' 10 > =foo '); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('='); expect(token.index).to.be(6); }); @@ -355,9 +348,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse(' 10 = =foo '); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('='); expect(token.index).to.be(4); }); @@ -383,9 +375,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse(' 10! =foo '); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('!'); expect(token.index).to.be(3); }); @@ -411,9 +402,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse(' 10 = == foo '); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('='); expect(token.index).to.be(4); }); @@ -439,9 +429,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse(' 10 != = foo '); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('='); expect(token.index).to.be(7); }); @@ -478,9 +467,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse('true & & false'); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('&'); expect(token.index).to.be(5); }); @@ -508,9 +496,8 @@ describe('ol.expression.parse', function() { expect(function() { ol.expression.parse('true | | false'); }).throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.value).to.be('|'); expect(token.index).to.be(5); }); diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index bf0977d1ce..bc586cbe44 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -379,9 +379,8 @@ describe('ol.expression.Lexer', function() { expect(function() { scan('"never \'ending\' string'); }).to.throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.type).to.be(ol.expression.TokenType.EOF); expect(token.index).to.be(22); }); @@ -433,9 +432,8 @@ describe('ol.expression.Lexer', function() { expect(function() { scan('\'never "ending" string'); }).to.throwException(function(err) { - expect(err).to.be.an(Error); + expect(err).to.be.an(ol.expression.UnexpectedToken); var token = err.token; - expect(token).not.to.be(undefined); expect(token.type).to.be(ol.expression.TokenType.EOF); expect(token.index).to.be(22); }); @@ -447,3 +445,4 @@ describe('ol.expression.Lexer', function() { goog.require('ol.expression.Lexer'); goog.require('ol.expression.TokenType'); +goog.require('ol.expression.UnexpectedToken'); From 72d32ec71a3ce1ddb46e3d8460b7bcaa94667db2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 12 Jun 2013 17:00:43 -0600 Subject: [PATCH 65/84] Correct index for tokens --- src/ol/expression/lexer.js | 8 ++-- test/spec/ol/expression/lexer.test.js | 61 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/ol/expression/lexer.js b/src/ol/expression/lexer.js index 9b17e00845..4389997290 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expression/lexer.js @@ -458,7 +458,7 @@ ol.expression.Lexer.prototype.peek = function() { */ ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { var str = ''; - var start = this.index_; + var start = this.index_ - 2; while (this.index_ < this.length_) { if (!this.isHexDigit_(code)) { @@ -659,7 +659,7 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { goog.asserts.assert(this.isOctalDigit_(code)); var str = '0' + String.fromCharCode(code); - var start = this.index_; + var start = this.index_ - 1; this.increment_(1); while (this.index_ < this.length_) { @@ -677,7 +677,7 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { throw new ol.expression.UnexpectedToken({ type: ol.expression.TokenType.UNKNOWN, value: String.fromCharCode(code), - index: this.index_ - 1 + index: this.index_ }); } @@ -812,10 +812,10 @@ 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 = ''; - var start = this.index_; var code; while (this.index_ < this.length_) { code = this.getCurrentCharCode_(); diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index bc586cbe44..a9615ec897 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -97,6 +97,7 @@ describe('ol.expression.Lexer', function() { var lexer = new ol.expression.Lexer(source); var token = lexer.scanIdentifier_(lexer.getCurrentCharCode_()); if (!part) { + expect(token.index).to.be(0); expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); } return token; @@ -187,6 +188,7 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); var token = lexer.scanNumericLiteral_(lexer.getCurrentCharCode_()); + expect(token.index).to.be(0); expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); return token; } @@ -197,12 +199,34 @@ describe('ol.expression.Lexer', function() { expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); + it('throws for bogus integer', function() { + expect(function() { + scan('123z'); + }).throwException(function(err) { + expect(err).to.be.an(ol.expression.UnexpectedToken); + var token = err.token; + expect(token.value).to.be('z'); + expect(token.index).to.be(3); + }); + }); + it('works for float', function() { var token = scan('123.456'); expect(token.value).to.be(123.456); expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); + it('throws for bogus float', function() { + expect(function() { + scan('123.4x4'); + }).throwException(function(err) { + expect(err).to.be.an(ol.expression.UnexpectedToken); + var token = err.token; + expect(token.value).to.be('x'); + expect(token.index).to.be(5); + }); + }); + it('works with exponent', function() { var token = scan('1.234e5'); expect(token.value).to.be(1.234e5); @@ -221,18 +245,53 @@ describe('ol.expression.Lexer', function() { expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); + it('throws for bogus float', function() { + expect(function() { + scan('1.234eo4'); + }).throwException(function(err) { + expect(err).to.be.an(ol.expression.UnexpectedToken); + var token = err.token; + expect(token.value).to.be('o'); + expect(token.index).to.be(6); + }); + }); + it('works with octals', function() { var token = scan('02322'); expect(token.value).to.be(1234); expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); + it('throws for bogus octal', function() { + // note that this is more strict than most es5 engines + expect(function() { + scan('02392'); + }).throwException(function(err) { + expect(err).to.be.an(ol.expression.UnexpectedToken); + var token = err.token; + expect(token.value).to.be('9'); + expect(token.index).to.be(3); + }); + }); + it('works with hex', function() { var token = scan('0x4d2'); expect(token.value).to.be(1234); expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); }); + it('throws for bogus hex', function() { + // note that this is more strict than most es5 engines + expect(function() { + scan('0x4G'); + }).throwException(function(err) { + expect(err).to.be.an(ol.expression.UnexpectedToken); + var token = err.token; + expect(token.value).to.be('G'); + expect(token.index).to.be(3); + }); + }); + }); describe('#scanPunctuator_()', function() { @@ -240,6 +299,7 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); var token = lexer.scanPunctuator_(lexer.getCurrentCharCode_()); + expect(token.index).to.be(0); expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); return token; } @@ -329,6 +389,7 @@ describe('ol.expression.Lexer', function() { function scan(source) { var lexer = new ol.expression.Lexer(source); var token = lexer.scanStringLiteral_(lexer.getCurrentCharCode_()); + expect(token.index).to.be(0); expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); return token; } From 53abedaada9f2ab8970f330df045769c4cdc8da5 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 12 Jun 2013 17:43:57 -0600 Subject: [PATCH 66/84] Correct doc and arg order --- src/ol/expression/expressions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ol/expression/expressions.js b/src/ol/expression/expressions.js index ebd01dbf9e..a37ade846d 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expression/expressions.js @@ -14,8 +14,8 @@ goog.provide('ol.expression.Not'); /** - * Base class for all expressions. Instances of ol.Expression correspond to - * a limited set of ECMAScript 5.1 expressions. + * Base class for all expressions. Instances of ol.expression.Expression + * correspond to a limited set of ECMAScript 5.1 expressions. * http://www.ecma-international.org/ecma-262/5.1/#sec-11 * * This base class should not be constructed directly. Instead, use one of @@ -175,8 +175,8 @@ ol.expression.Comparison.isValidOp = (function() { /** * @inheritDoc */ -ol.expression.Comparison.prototype.evaluate = function(opt_scope, opt_this, - opt_fns) { +ol.expression.Comparison.prototype.evaluate = function(opt_scope, opt_fns, + opt_this) { var result; var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this); var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this); From 5e309e244bda1edc0b802117e5fce75efd6e45ad Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 12 Jun 2013 17:45:17 -0600 Subject: [PATCH 67/84] Using ol.expression.parse --- examples/style-rules.js | 6 +- src/objectliterals.jsdoc | 57 +++++++++--------- src/ol/expression.exports | 1 - src/ol/expression.js | 80 -------------------------- src/ol/style/icon.js | 49 ++++++++-------- src/ol/style/line.js | 34 +++++------ src/ol/style/polygon.js | 44 +++++++------- src/ol/style/shape.js | 54 ++++++++--------- test/spec/ol/expression.test.js | 78 ------------------------- test/spec/ol/layer/vectorlayer.test.js | 6 +- test/spec/ol/style/line.test.js | 10 ++-- test/spec/ol/style/polygon.test.js | 10 ++-- test/spec/ol/style/shape.test.js | 14 ++--- 13 files changed, 145 insertions(+), 298 deletions(-) delete mode 100644 src/ol/expression.exports delete mode 100644 src/ol/expression.js delete mode 100644 test/spec/ol/expression.test.js diff --git a/examples/style-rules.js b/examples/style-rules.js index de8839a7b7..82d14b86e3 100644 --- a/examples/style-rules.js +++ b/examples/style-rules.js @@ -1,8 +1,8 @@ -goog.require('ol.Expression'); goog.require('ol.Map'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); goog.require('ol.control.defaults'); +goog.require('ol.expression'); goog.require('ol.filter.Filter'); goog.require('ol.filter.Geometry'); goog.require('ol.geom.GeometryType'); @@ -24,7 +24,7 @@ var style = new ol.style.Style({rules: [ }), symbolizers: [ new ol.style.Line({ - strokeColor: new ol.Expression('color'), + strokeColor: ol.expression.parse('color'), strokeWidth: 4, opacity: 1 }) @@ -41,7 +41,7 @@ var style = new ol.style.Style({rules: [ opacity: 1 }), new ol.style.Line({ - strokeColor: new ol.Expression('color'), + strokeColor: ol.expression.parse('color'), strokeWidth: 2, opacity: 1 }) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index fa584f9332..b7b5a2e460 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -519,34 +519,35 @@ /** * @typedef {Object} ol.style.IconOptions - * @property {string|ol.Expression} url Icon image url. - * @property {number|ol.Expression|undefined} width Width of the icon in pixels. - * Default is the width of the icon image. - * @property {number|ol.Expression|undefined} height Height of the icon in - * pixels. Default is the height of the icon image. - * @property {number|ol.Expression|undefined} opacity Icon opacity (0-1). - * @property {number|ol.Expression|undefined} rotation Rotation in degrees - * (0-360). + * @property {string|ol.expression.Expression} url Icon image url. + * @property {number|ol.expression.Expression|undefined} width Width of the icon + * in pixels. Default is the width of the icon image. + * @property {number|ol.expression.Expression|undefined} height Height of the + * icon in pixels. Default is the height of the icon image. + * @property {number|ol.expression.Expression|undefined} opacity Icon opacity + * (0-1). + * @property {number|ol.expression.Expression|undefined} rotation Rotation in + * degrees (0-360). */ /** * @typedef {Object} ol.style.LineOptions - * @property {string|ol.Expression|undefined} strokeColor Stroke color as hex - * color code. - * @property {number|ol.Expression|undefined} strokeWidth Stroke width in - * pixels. - * @property {number|ol.Expression|undefined} opacity Opacity (0-1). + * @property {string|ol.expression.Expression|undefined} strokeColor Stroke + * color as hex color code. + * @property {number|ol.expression.Expression|undefined} strokeWidth Stroke + * width in pixels. + * @property {number|ol.expression.Expression|undefined} opacity Opacity (0-1). */ /** * @typedef {Object} ol.style.PolygonOptions - * @property {string|ol.Expression|undefined} fillColor Fill color as hex color - * code. - * @property {string|ol.Expression|undefined} strokeColor Stroke color as hex - * color code. - * @property {number|ol.Expression|undefined} strokeWidth Stroke width in - * pixels. - * @property {number|ol.Expression|undefined} opacity Opacity (0-1). + * @property {string|ol.expression.Expression|undefined} fillColor Fill color as + * hex color code. + * @property {string|ol.expression.Expression|undefined} strokeColor Stroke + * color as hex color code. + * @property {number|ol.expression.Expression|undefined} strokeWidth Stroke + * width in pixels. + * @property {number|ol.expression.Expression|undefined} opacity Opacity (0-1). */ /** @@ -558,14 +559,14 @@ /** * @typedef {Object} ol.style.ShapeOptions * @property {ol.style.ShapeType|undefined} type Type. - * @property {number|ol.Expression|undefined} size Size in pixels. - * @property {string|ol.Expression|undefined} fillColor Fill color as hex color - * code. - * @property {string|ol.Expression|undefined} strokeColor Stroke color as hex - * color code. - * @property {number|ol.Expression|undefined} strokeWidth Stroke width in - * pixels. - * @property {number|ol.Expression|undefined} opacity Opacity (0-1). + * @property {number|ol.expression.Expression|undefined} size Size in pixels. + * @property {string|ol.expression.Expression|undefined} fillColor Fill color as + * hex color code. + * @property {string|ol.expression.Expression|undefined} strokeColor Stroke + * color as hex color code. + * @property {number|ol.expression.Expression|undefined} strokeWidth Stroke + * width in pixels. + * @property {number|ol.expression.Expression|undefined} opacity Opacity (0-1). */ /** diff --git a/src/ol/expression.exports b/src/ol/expression.exports deleted file mode 100644 index 911b8a12c6..0000000000 --- a/src/ol/expression.exports +++ /dev/null @@ -1 +0,0 @@ -@exportSymbol ol.Expression diff --git a/src/ol/expression.js b/src/ol/expression.js deleted file mode 100644 index 7b1370ae0d..0000000000 --- a/src/ol/expression.js +++ /dev/null @@ -1,80 +0,0 @@ -goog.provide('ol.Expression'); -goog.provide('ol.ExpressionLiteral'); - - - -/** - * Create a new expression. Expressions are used for instance to bind - * symbolizer properties to feature attributes. - * - * Example: - * - * // take the color from the color attribute - * color: new ol.Expression('color'); - * // take the strokeWidth from the width attribute and multiply by 2. - * strokeWidth: new ol.Expression('width*2'); - * - * @constructor - * @param {string} source Expression to be evaluated. - */ -ol.Expression = function(source) { - - /** - * @type {string} - * @private - */ - this.source_ = source; - -}; - - -/** - * Evaluate the expression and return the result. - * - * @param {Object=} opt_thisArg Object to use as this when evaluating the - * expression. If not provided, the global object will be used. - * @param {Object=} opt_scope Evaluation scope. All properties of this object - * will be available as variables when evaluating the expression. If not - * provided, the global object will be used. - * @return {*} Result of the expression. - */ -ol.Expression.prototype.evaluate = function(opt_thisArg, opt_scope) { - var thisArg = goog.isDef(opt_thisArg) ? opt_thisArg : goog.global, - scope = goog.isDef(opt_scope) ? opt_scope : goog.global, - names = [], - values = []; - - for (var name in scope) { - names.push(name); - values.push(scope[name]); - } - - var evaluator = new Function(names.join(','), 'return ' + this.source_); - return evaluator.apply(thisArg, values); -}; - - - -/** - * @constructor - * @extends {ol.Expression} - * @param {*} value Literal value. - */ -ol.ExpressionLiteral = function(value) { - - /** - * @type {*} - * @private - */ - this.value_ = value; - -}; -goog.inherits(ol.ExpressionLiteral, ol.Expression); - - -/** - * @inheritDoc - */ -ol.ExpressionLiteral.prototype.evaluate = function(opt_thisArg, opt_scope) { - return this.value_; -}; diff --git a/src/ol/style/icon.js b/src/ol/style/icon.js index 2be1658c24..4a3b148a10 100644 --- a/src/ol/style/icon.js +++ b/src/ol/style/icon.js @@ -3,8 +3,8 @@ goog.provide('ol.style.IconLiteral'); goog.provide('ol.style.IconType'); goog.require('goog.asserts'); -goog.require('ol.Expression'); -goog.require('ol.ExpressionLiteral'); +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Literal'); goog.require('ol.style.Point'); goog.require('ol.style.PointLiteral'); @@ -69,47 +69,47 @@ ol.style.Icon = function(options) { goog.asserts.assert(options.url, 'url must be set'); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ - this.url_ = (options.url instanceof ol.Expression) ? - options.url : new ol.ExpressionLiteral(options.url); + this.url_ = (options.url instanceof ol.expression.Expression) ? + options.url : new ol.expression.Literal(options.url); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.width_ = !goog.isDef(options.width) ? null : - (options.width instanceof ol.Expression) ? - options.width : new ol.ExpressionLiteral(options.width); + (options.width instanceof ol.expression.Expression) ? + options.width : new ol.expression.Literal(options.width); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.height_ = !goog.isDef(options.height) ? null : - (options.height instanceof ol.Expression) ? - options.height : new ol.ExpressionLiteral(options.height); + (options.height instanceof ol.expression.Expression) ? + options.height : new ol.expression.Literal(options.height); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.ExpressionLiteral(ol.style.IconDefaults.opacity) : - (options.opacity instanceof ol.Expression) ? - options.opacity : new ol.ExpressionLiteral(options.opacity); + new ol.expression.Literal(ol.style.IconDefaults.opacity) : + (options.opacity instanceof ol.expression.Expression) ? + options.opacity : new ol.expression.Literal(options.opacity); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.rotation_ = !goog.isDef(options.rotation) ? - new ol.ExpressionLiteral(ol.style.IconDefaults.rotation) : - (options.rotation instanceof ol.Expression) ? - options.rotation : new ol.ExpressionLiteral(options.rotation); + new ol.expression.Literal(ol.style.IconDefaults.rotation) : + (options.rotation instanceof ol.expression.Expression) ? + options.rotation : new ol.expression.Literal(options.rotation); }; @@ -121,24 +121,25 @@ ol.style.Icon = function(options) { ol.style.Icon.prototype.createLiteral = function(feature) { var attrs = feature && feature.getAttributes(); - var url = /** @type {string} */ (this.url_.evaluate(feature, attrs)); + var url = /** @type {string} */ (this.url_.evaluate(attrs, null, feature)); goog.asserts.assert(goog.isString(url) && url != '#', 'url must be a string'); var width = /** @type {number|undefined} */ (goog.isNull(this.width_) ? - undefined : this.width_.evaluate(feature, attrs)); + undefined : this.width_.evaluate(attrs, null, feature)); goog.asserts.assert(!goog.isDef(width) || goog.isNumber(width), 'width must be undefined or a number'); var height = /** @type {number|undefined} */ (goog.isNull(this.height_) ? - undefined : this.height_.evaluate(feature, attrs)); + undefined : this.height_.evaluate(attrs, null, feature)); goog.asserts.assert(!goog.isDef(height) || goog.isNumber(height), 'height must be undefined or a number'); - var opacity = /** {@type {number} */ (this.opacity_.evaluate(feature, attrs)); + var opacity = /** {@type {number} */ (this.opacity_.evaluate(attrs, null, + feature)); goog.asserts.assertNumber(opacity, 'opacity must be a number'); var rotation = - /** {@type {number} */ (this.rotation_.evaluate(feature, attrs)); + /** {@type {number} */ (this.rotation_.evaluate(attrs, null, feature)); goog.asserts.assertNumber(rotation, 'rotation must be a number'); return new ol.style.IconLiteral({ diff --git a/src/ol/style/line.js b/src/ol/style/line.js index 95460b8d45..e226ea085c 100644 --- a/src/ol/style/line.js +++ b/src/ol/style/line.js @@ -2,8 +2,8 @@ goog.provide('ol.style.Line'); goog.provide('ol.style.LineLiteral'); goog.require('goog.asserts'); -goog.require('ol.Expression'); -goog.require('ol.ExpressionLiteral'); +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Literal'); goog.require('ol.style.Symbolizer'); goog.require('ol.style.SymbolizerLiteral'); @@ -63,31 +63,31 @@ ol.style.Line = function(options) { goog.base(this); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.strokeColor_ = !goog.isDef(options.strokeColor) ? - new ol.ExpressionLiteral(ol.style.LineDefaults.strokeColor) : - (options.strokeColor instanceof ol.Expression) ? - options.strokeColor : new ol.ExpressionLiteral(options.strokeColor); + new ol.expression.Literal(ol.style.LineDefaults.strokeColor) : + (options.strokeColor instanceof ol.expression.Expression) ? + options.strokeColor : new ol.expression.Literal(options.strokeColor); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.strokeWidth_ = !goog.isDef(options.strokeWidth) ? - new ol.ExpressionLiteral(ol.style.LineDefaults.strokeWidth) : - (options.strokeWidth instanceof ol.Expression) ? - options.strokeWidth : new ol.ExpressionLiteral(options.strokeWidth); + new ol.expression.Literal(ol.style.LineDefaults.strokeWidth) : + (options.strokeWidth instanceof ol.expression.Expression) ? + options.strokeWidth : new ol.expression.Literal(options.strokeWidth); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.ExpressionLiteral(ol.style.LineDefaults.opacity) : - (options.opacity instanceof ol.Expression) ? - options.opacity : new ol.ExpressionLiteral(options.opacity); + new ol.expression.Literal(ol.style.LineDefaults.opacity) : + (options.opacity instanceof ol.expression.Expression) ? + options.opacity : new ol.expression.Literal(options.opacity); }; goog.inherits(ol.style.Line, ol.style.Symbolizer); @@ -104,13 +104,13 @@ ol.style.Line.prototype.createLiteral = function(opt_feature) { attrs = feature.getAttributes(); } - var strokeColor = this.strokeColor_.evaluate(feature, attrs); + var strokeColor = this.strokeColor_.evaluate(attrs, null, feature); goog.asserts.assertString(strokeColor, 'strokeColor must be a string'); - var strokeWidth = this.strokeWidth_.evaluate(feature, attrs); + var strokeWidth = this.strokeWidth_.evaluate(attrs, null, feature); goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number'); - var opacity = this.opacity_.evaluate(feature, attrs); + var opacity = this.opacity_.evaluate(attrs, null, feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.LineLiteral({ diff --git a/src/ol/style/polygon.js b/src/ol/style/polygon.js index b207a1ebec..6c1a296ccb 100644 --- a/src/ol/style/polygon.js +++ b/src/ol/style/polygon.js @@ -2,8 +2,8 @@ goog.provide('ol.style.Polygon'); goog.provide('ol.style.PolygonLiteral'); goog.require('goog.asserts'); -goog.require('ol.Expression'); -goog.require('ol.ExpressionLiteral'); +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Literal'); goog.require('ol.style.Symbolizer'); goog.require('ol.style.SymbolizerLiteral'); @@ -80,13 +80,13 @@ ol.style.Polygon = function(options) { goog.base(this); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.fillColor_ = !goog.isDefAndNotNull(options.fillColor) ? null : - (options.fillColor instanceof ol.Expression) ? - options.fillColor : new ol.ExpressionLiteral(options.fillColor); + (options.fillColor instanceof ol.expression.Expression) ? + options.fillColor : new ol.expression.Literal(options.fillColor); // stroke handling - if any stroke property is supplied, use defaults var strokeColor = null, @@ -96,24 +96,26 @@ ol.style.Polygon = function(options) { goog.isDefAndNotNull(options.strokeWidth)) { strokeColor = !goog.isDefAndNotNull(options.strokeColor) ? - new ol.ExpressionLiteral(ol.style.PolygonDefaults.strokeColor) : - (options.strokeColor instanceof ol.Expression) ? - options.strokeColor : new ol.ExpressionLiteral(options.strokeColor); + new ol.expression.Literal(ol.style.PolygonDefaults.strokeColor) : + (options.strokeColor instanceof ol.expression.Expression) ? + options.strokeColor : + new ol.expression.Literal(options.strokeColor); strokeWidth = !goog.isDef(options.strokeWidth) ? - new ol.ExpressionLiteral(ol.style.PolygonDefaults.strokeWidth) : - (options.strokeWidth instanceof ol.Expression) ? - options.strokeWidth : new ol.ExpressionLiteral(options.strokeWidth); + new ol.expression.Literal(ol.style.PolygonDefaults.strokeWidth) : + (options.strokeWidth instanceof ol.expression.Expression) ? + options.strokeWidth : + new ol.expression.Literal(options.strokeWidth); } /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.strokeColor_ = strokeColor; /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.strokeWidth_ = strokeWidth; @@ -124,13 +126,13 @@ ol.style.Polygon = function(options) { 'Stroke or fill properties must be provided'); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.ExpressionLiteral(ol.style.PolygonDefaults.opacity) : - (options.opacity instanceof ol.Expression) ? - options.opacity : new ol.ExpressionLiteral(options.opacity); + new ol.expression.Literal(ol.style.PolygonDefaults.opacity) : + (options.opacity instanceof ol.expression.Expression) ? + options.opacity : new ol.expression.Literal(options.opacity); }; goog.inherits(ol.style.Polygon, ol.style.Symbolizer); @@ -149,17 +151,17 @@ ol.style.Polygon.prototype.createLiteral = function(opt_feature) { var fillColor = goog.isNull(this.fillColor_) ? undefined : - /** @type {string} */ (this.fillColor_.evaluate(feature, attrs)); + /** @type {string} */ (this.fillColor_.evaluate(attrs, null, feature)); goog.asserts.assert(!goog.isDef(fillColor) || goog.isString(fillColor)); var strokeColor = goog.isNull(this.strokeColor_) ? undefined : - /** @type {string} */ (this.strokeColor_.evaluate(feature, attrs)); + /** @type {string} */ (this.strokeColor_.evaluate(attrs, null, feature)); goog.asserts.assert(!goog.isDef(strokeColor) || goog.isString(strokeColor)); var strokeWidth = goog.isNull(this.strokeWidth_) ? undefined : - /** @type {number} */ (this.strokeWidth_.evaluate(feature, attrs)); + /** @type {number} */ (this.strokeWidth_.evaluate(attrs, null, feature)); goog.asserts.assert(!goog.isDef(strokeWidth) || goog.isNumber(strokeWidth)); goog.asserts.assert( @@ -167,7 +169,7 @@ ol.style.Polygon.prototype.createLiteral = function(opt_feature) { (goog.isDef(strokeColor) && goog.isDef(strokeWidth)), 'either fill style or strokeColor and strokeWidth must be defined'); - var opacity = this.opacity_.evaluate(feature, attrs); + var opacity = this.opacity_.evaluate(attrs, null, feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.PolygonLiteral({ diff --git a/src/ol/style/shape.js b/src/ol/style/shape.js index acc8a98f8d..a2b2ea9063 100644 --- a/src/ol/style/shape.js +++ b/src/ol/style/shape.js @@ -3,8 +3,8 @@ goog.provide('ol.style.ShapeLiteral'); goog.provide('ol.style.ShapeType'); goog.require('goog.asserts'); -goog.require('ol.Expression'); -goog.require('ol.ExpressionLiteral'); +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Literal'); goog.require('ol.style.Point'); goog.require('ol.style.PointLiteral'); @@ -106,22 +106,22 @@ ol.style.Shape = function(options) { options.type : ol.style.ShapeDefaults.type); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.size_ = !goog.isDef(options.size) ? - new ol.ExpressionLiteral(ol.style.ShapeDefaults.size) : - (options.size instanceof ol.Expression) ? - options.size : new ol.ExpressionLiteral(options.size); + new ol.expression.Literal(ol.style.ShapeDefaults.size) : + (options.size instanceof ol.expression.Expression) ? + options.size : new ol.expression.Literal(options.size); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.fillColor_ = !goog.isDefAndNotNull(options.fillColor) ? null : - (options.fillColor instanceof ol.Expression) ? - options.fillColor : new ol.ExpressionLiteral(options.fillColor); + (options.fillColor instanceof ol.expression.Expression) ? + options.fillColor : new ol.expression.Literal(options.fillColor); // stroke handling - if any stroke property is supplied, use defaults var strokeColor = null, @@ -131,24 +131,26 @@ ol.style.Shape = function(options) { goog.isDefAndNotNull(options.strokeWidth)) { strokeColor = !goog.isDefAndNotNull(options.strokeColor) ? - new ol.ExpressionLiteral(ol.style.ShapeDefaults.strokeColor) : - (options.strokeColor instanceof ol.Expression) ? - options.strokeColor : new ol.ExpressionLiteral(options.strokeColor); + new ol.expression.Literal(ol.style.ShapeDefaults.strokeColor) : + (options.strokeColor instanceof ol.expression.Expression) ? + options.strokeColor : + new ol.expression.Literal(options.strokeColor); strokeWidth = !goog.isDef(options.strokeWidth) ? - new ol.ExpressionLiteral(ol.style.ShapeDefaults.strokeWidth) : - (options.strokeWidth instanceof ol.Expression) ? - options.strokeWidth : new ol.ExpressionLiteral(options.strokeWidth); + new ol.expression.Literal(ol.style.ShapeDefaults.strokeWidth) : + (options.strokeWidth instanceof ol.expression.Expression) ? + options.strokeWidth : + new ol.expression.Literal(options.strokeWidth); } /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.strokeColor_ = strokeColor; /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.strokeWidth_ = strokeWidth; @@ -159,13 +161,13 @@ ol.style.Shape = function(options) { 'Stroke or fill properties must be provided'); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.ExpressionLiteral(ol.style.ShapeDefaults.opacity) : - (options.opacity instanceof ol.Expression) ? - options.opacity : new ol.ExpressionLiteral(options.opacity); + new ol.expression.Literal(ol.style.ShapeDefaults.opacity) : + (options.opacity instanceof ol.expression.Expression) ? + options.opacity : new ol.expression.Literal(options.opacity); }; @@ -181,22 +183,22 @@ ol.style.Shape.prototype.createLiteral = function(opt_feature) { attrs = feature.getAttributes(); } - var size = this.size_.evaluate(feature, attrs); + var size = this.size_.evaluate(attrs, null, feature); goog.asserts.assertNumber(size, 'size must be a number'); var fillColor = goog.isNull(this.fillColor_) ? undefined : - /** @type {string} */ (this.fillColor_.evaluate(feature, attrs)); + /** @type {string} */ (this.fillColor_.evaluate(attrs, null, feature)); goog.asserts.assert(!goog.isDef(fillColor) || goog.isString(fillColor)); var strokeColor = goog.isNull(this.strokeColor_) ? undefined : - /** @type {string} */ (this.strokeColor_.evaluate(feature, attrs)); + /** @type {string} */ (this.strokeColor_.evaluate(attrs, null, feature)); goog.asserts.assert(!goog.isDef(strokeColor) || goog.isString(strokeColor)); var strokeWidth = goog.isNull(this.strokeWidth_) ? undefined : - /** @type {number} */ (this.strokeWidth_.evaluate(feature, attrs)); + /** @type {number} */ (this.strokeWidth_.evaluate(attrs, null, feature)); goog.asserts.assert(!goog.isDef(strokeWidth) || goog.isNumber(strokeWidth)); goog.asserts.assert( @@ -204,7 +206,7 @@ ol.style.Shape.prototype.createLiteral = function(opt_feature) { (goog.isDef(strokeColor) && goog.isDef(strokeWidth)), 'either fill style or strokeColor and strokeWidth must be defined'); - var opacity = this.opacity_.evaluate(feature, attrs); + var opacity = this.opacity_.evaluate(attrs, null, feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.ShapeLiteral({ diff --git a/test/spec/ol/expression.test.js b/test/spec/ol/expression.test.js deleted file mode 100644 index fb9735d1d2..0000000000 --- a/test/spec/ol/expression.test.js +++ /dev/null @@ -1,78 +0,0 @@ -goog.provide('ol.test.Expression'); - -describe('ol.Expression', function() { - - describe('constructor', function() { - it('creates an expression', function() { - var exp = new ol.Expression('foo'); - expect(exp).to.be.a(ol.Expression); - }); - }); - - describe('#evaluate()', function() { - - it('evaluates and returns the result', function() { - // test cases here with unique values only (lack of messages in expect) - var cases = [{ - source: '42', result: 42 - }, { - source: '10 + 10', result: 20 - }, { - source: '"a" + "b"', result: 'ab' - }, { - source: 'Math.floor(Math.PI)', result: 3 - }, { - source: 'ol', result: ol - }, { - source: 'this', result: goog.global - }]; - - var c, exp; - for (var i = 0, ii = cases.length; i < ii; ++i) { - c = cases[i]; - exp = new ol.Expression(c.source); - expect(exp.evaluate()).to.be(c.result); - } - }); - - it('accepts an optional this argument', function() { - function Thing() { - this.works = true; - }; - - var exp = new ol.Expression('this.works ? "yes" : "no"'); - expect(exp.evaluate(new Thing())).to.be('yes'); - expect(exp.evaluate({})).to.be('no'); - }); - - it('accepts an optional scope argument', function() { - var exp; - var scope = { - greeting: 'hello world', - punctuation: '!', - pick: function(array, index) { - return array[index]; - } - }; - - // access two members in the scope - exp = new ol.Expression('greeting + punctuation'); - expect(exp.evaluate({}, scope)).to.be('hello world!'); - - // call a function in the scope - exp = new ol.Expression( - 'pick([10, 42, "chicken"], 2) + Math.floor(Math.PI)'); - expect(exp.evaluate({}, scope)).to.be('chicken3'); - - }); - - it('throws on error', function() { - var exp = new ol.Expression('@*)$(&'); - expect(function() {exp.evaluate()}).to.throwException(); - }); - - }); - -}); - -goog.require('ol.Expression'); diff --git a/test/spec/ol/layer/vectorlayer.test.js b/test/spec/ol/layer/vectorlayer.test.js index 243750fb35..5deba1c925 100644 --- a/test/spec/ol/layer/vectorlayer.test.js +++ b/test/spec/ol/layer/vectorlayer.test.js @@ -107,7 +107,7 @@ describe('ol.layer.Vector', function() { symbolizers: [ new ol.style.Line({ strokeWidth: 2, - strokeColor: new ol.Expression('colorProperty'), + strokeColor: ol.expression.parse('colorProperty'), opacity: 1 }) ] @@ -144,7 +144,7 @@ describe('ol.layer.Vector', function() { it('groups equal symbolizers also when defined on features', function() { var symbolizer = new ol.style.Line({ strokeWidth: 3, - strokeColor: new ol.Expression('colorProperty'), + strokeColor: ol.expression.parse('colorProperty'), opacity: 1 }); var anotherSymbolizer = new ol.style.Line({ @@ -177,8 +177,8 @@ describe('ol.layer.Vector', function() { }); goog.require('goog.dispose'); -goog.require('ol.Expression'); goog.require('ol.Feature'); +goog.require('ol.expression'); goog.require('ol.filter.Extent'); goog.require('ol.filter.Geometry'); goog.require('ol.filter.Logical'); diff --git a/test/spec/ol/style/line.test.js b/test/spec/ol/style/line.test.js index eaf2471dfb..f57d4a71fa 100644 --- a/test/spec/ol/style/line.test.js +++ b/test/spec/ol/style/line.test.js @@ -42,8 +42,8 @@ describe('ol.style.Line', function() { it('accepts expressions', function() { var symbolizer = new ol.style.Line({ - opacity: new ol.Expression('value / 100'), - strokeWidth: ol.Expression('widthAttr') + opacity: ol.expression.parse('value / 100'), + strokeWidth: ol.expression.parse('widthAttr') }); expect(symbolizer).to.be.a(ol.style.Line); }); @@ -54,8 +54,8 @@ describe('ol.style.Line', function() { it('evaluates expressions with the given feature', function() { var symbolizer = new ol.style.Line({ - opacity: new ol.Expression('value / 100'), - strokeWidth: ol.Expression('widthAttr') + opacity: ol.expression.parse('value / 100'), + strokeWidth: ol.expression.parse('widthAttr') }); var feature = new ol.Feature({ @@ -73,7 +73,7 @@ describe('ol.style.Line', function() { }); -goog.require('ol.Expression'); goog.require('ol.Feature'); +goog.require('ol.expression'); goog.require('ol.style.Line'); goog.require('ol.style.LineLiteral'); diff --git a/test/spec/ol/style/polygon.test.js b/test/spec/ol/style/polygon.test.js index b56bb1b160..e8a9453899 100644 --- a/test/spec/ol/style/polygon.test.js +++ b/test/spec/ol/style/polygon.test.js @@ -45,8 +45,8 @@ describe('ol.style.Polygon', function() { it('accepts expressions', function() { var symbolizer = new ol.style.Polygon({ - opacity: new ol.Expression('value / 100'), - fillColor: new ol.Expression('fillAttr') + opacity: ol.expression.parse('value / 100'), + fillColor: ol.expression.parse('fillAttr') }); expect(symbolizer).to.be.a(ol.style.Polygon); }); @@ -57,8 +57,8 @@ describe('ol.style.Polygon', function() { it('evaluates expressions with the given feature', function() { var symbolizer = new ol.style.Polygon({ - opacity: new ol.Expression('value / 100'), - fillColor: new ol.Expression('fillAttr') + opacity: ol.expression.parse('value / 100'), + fillColor: ol.expression.parse('fillAttr') }); var feature = new ol.Feature({ @@ -89,7 +89,7 @@ describe('ol.style.Polygon', function() { }); -goog.require('ol.Expression'); goog.require('ol.Feature'); +goog.require('ol.expression'); goog.require('ol.style.Polygon'); goog.require('ol.style.PolygonLiteral'); diff --git a/test/spec/ol/style/shape.test.js b/test/spec/ol/style/shape.test.js index 361ddc6727..90a1158f06 100644 --- a/test/spec/ol/style/shape.test.js +++ b/test/spec/ol/style/shape.test.js @@ -51,8 +51,8 @@ describe('ol.style.Shape', function() { it('accepts expressions', function() { var symbolizer = new ol.style.Shape({ - size: new ol.Expression('sizeAttr'), - strokeColor: new ol.Expression('color') + size: ol.expression.parse('sizeAttr'), + strokeColor: ol.expression.parse('color') }); expect(symbolizer).to.be.a(ol.style.Shape); }); @@ -63,8 +63,8 @@ describe('ol.style.Shape', function() { it('evaluates expressions with the given feature', function() { var symbolizer = new ol.style.Shape({ - size: new ol.Expression('sizeAttr'), - opacity: new ol.Expression('opacityAttr'), + size: ol.expression.parse('sizeAttr'), + opacity: ol.expression.parse('opacityAttr'), fillColor: '#BADA55' }); @@ -99,8 +99,8 @@ describe('ol.style.Shape', function() { it('applies default type if none provided', function() { var symbolizer = new ol.style.Shape({ - size: new ol.Expression('sizeAttr'), - opacity: new ol.Expression('opacityAttr'), + size: ol.expression.parse('sizeAttr'), + opacity: ol.expression.parse('opacityAttr'), fillColor: '#BADA55' }); @@ -119,8 +119,8 @@ describe('ol.style.Shape', function() { }); -goog.require('ol.Expression'); goog.require('ol.Feature'); +goog.require('ol.expression'); goog.require('ol.style.Shape'); goog.require('ol.style.ShapeLiteral'); goog.require('ol.style.ShapeType'); From bd5d9d572ee9c1222858c808ea1908dbdc2c2b70 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 12 Jun 2013 21:42:12 -0600 Subject: [PATCH 68/84] Lint --- src/ol/style/polygon.js | 26 +++++++++++++-------- src/ol/style/shape.js | 27 ++++++++++++++-------- test/spec/ol/expression/expression.test.js | 1 + test/spec/ol/expression/lexer.test.js | 4 ++-- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/ol/style/polygon.js b/src/ol/style/polygon.js index 6c1a296ccb..6819fff56f 100644 --- a/src/ol/style/polygon.js +++ b/src/ol/style/polygon.js @@ -95,17 +95,23 @@ ol.style.Polygon = function(options) { if (goog.isDefAndNotNull(options.strokeColor) || goog.isDefAndNotNull(options.strokeWidth)) { - strokeColor = !goog.isDefAndNotNull(options.strokeColor) ? - new ol.expression.Literal(ol.style.PolygonDefaults.strokeColor) : - (options.strokeColor instanceof ol.expression.Expression) ? - options.strokeColor : - new ol.expression.Literal(options.strokeColor); + if (goog.isDefAndNotNull(options.strokeColor)) { + strokeColor = (options.strokeColor instanceof ol.expression.Expression) ? + options.strokeColor : + new ol.expression.Literal(options.strokeColor); + } else { + strokeColor = new ol.expression.Literal( + /** @type {string} */ (ol.style.PolygonDefaults.strokeColor)); + } - strokeWidth = !goog.isDef(options.strokeWidth) ? - new ol.expression.Literal(ol.style.PolygonDefaults.strokeWidth) : - (options.strokeWidth instanceof ol.expression.Expression) ? - options.strokeWidth : - new ol.expression.Literal(options.strokeWidth); + if (goog.isDefAndNotNull(options.strokeWidth)) { + strokeWidth = (options.strokeWidth instanceof ol.expression.Expression) ? + options.strokeWidth : + new ol.expression.Literal(options.strokeWidth); + } else { + strokeWidth = new ol.expression.Literal( + /** @type {number} */ (ol.style.PolygonDefaults.strokeWidth)); + } } /** diff --git a/src/ol/style/shape.js b/src/ol/style/shape.js index a2b2ea9063..b1ff0e1bf3 100644 --- a/src/ol/style/shape.js +++ b/src/ol/style/shape.js @@ -130,17 +130,24 @@ ol.style.Shape = function(options) { if (goog.isDefAndNotNull(options.strokeColor) || goog.isDefAndNotNull(options.strokeWidth)) { - strokeColor = !goog.isDefAndNotNull(options.strokeColor) ? - new ol.expression.Literal(ol.style.ShapeDefaults.strokeColor) : - (options.strokeColor instanceof ol.expression.Expression) ? - options.strokeColor : - new ol.expression.Literal(options.strokeColor); + if (goog.isDefAndNotNull(options.strokeColor)) { + strokeColor = (options.strokeColor instanceof ol.expression.Expression) ? + options.strokeColor : + new ol.expression.Literal(options.strokeColor); + } else { + strokeColor = new ol.expression.Literal( + /** @type {string} */ (ol.style.ShapeDefaults.strokeColor)); + } + + if (goog.isDefAndNotNull(options.strokeWidth)) { + strokeWidth = (options.strokeWidth instanceof ol.expression.Expression) ? + options.strokeWidth : + new ol.expression.Literal(options.strokeWidth); + } else { + strokeWidth = new ol.expression.Literal( + /** @type {number} */ (ol.style.ShapeDefaults.strokeWidth)); + } - strokeWidth = !goog.isDef(options.strokeWidth) ? - new ol.expression.Literal(ol.style.ShapeDefaults.strokeWidth) : - (options.strokeWidth instanceof ol.expression.Expression) ? - options.strokeWidth : - new ol.expression.Literal(options.strokeWidth); } /** diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index f353a6b004..1c73a46ee5 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -534,3 +534,4 @@ goog.require('ol.expression.Math'); goog.require('ol.expression.Member'); goog.require('ol.expression.Not'); goog.require('ol.expression.TokenType'); +goog.require('ol.expression.UnexpectedToken'); diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expression/lexer.test.js index a9615ec897..828a58726c 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expression/lexer.test.js @@ -9,7 +9,7 @@ describe('ol.expression.Lexer', function() { }); }); - describe.only('#next()', function() { + describe('#next()', function() { it('returns one token at a time', function() { var source = 'foo === "bar"'; @@ -41,7 +41,7 @@ describe('ol.expression.Lexer', function() { }); - describe.only('#peek()', function() { + describe('#peek()', function() { var lexer; beforeEach(function() { From d70a9eba0150940e2cd56164ebfdd3f2f2c9dc27 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 14 Jun 2013 17:32:54 -0600 Subject: [PATCH 69/84] Lib for well-known functions This reveals a lexer bug that needs addressing. --- src/ol/expression/expression.js | 50 ++++++++++++++++++++++ test/spec/ol/expression/expression.test.js | 42 ++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index dd2cc27e2b..a773b6cd8f 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -1,6 +1,10 @@ goog.provide('ol.expression'); +goog.require('ol.Extent'); +goog.require('ol.Feature'); goog.require('ol.expression.Parser'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); /** @@ -13,3 +17,49 @@ ol.expression.parse = function(source) { var parser = new ol.expression.Parser(); return parser.parse(source); }; + + +/** + * Library of well-known functions. These are available to expressions parsed + * with `ol.expression.parse`. + * + * @type {Object} + */ +ol.expression.lib = { + + /** + * Determine if a feature's extent intersects the provided extent. + * @param {number} minX Minimum x-coordinate value. + * @param {number} maxX Maximum x-coordinate value. + * @param {number} minY Minimum y-coordinate value. + * @param {number} maxY Maximum y-coordinate value. + * @return {boolean} The provided extent intersects the feature's extent. + * @this {ol.Feature} + */ + 'extent': function(minX, maxX, minY, maxY) { + var intersects = false; + var geometry = this.getGeometry(); + if (geometry) { + intersects = ol.extent.intersects(geometry.getBounds(), + [minX, maxX, minY, maxY]); + } + return intersects; + }, + + + /** + * Determine if a feature's default geometry is of the given type. + * @param {ol.geom.GeometryType} type Geometry type. + * @return {boolean} The feature's default geometry is of the given type. + * @this {ol.Feature} + */ + 'geometryType': function(type) { + var same = false; + var geometry = this.getGeometry(); + if (geometry) { + same = geometry.getType() === type; + } + return same; + } + +}; diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index 1c73a46ee5..d29700f31b 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -522,6 +522,48 @@ describe('ol.expression.parse', function() { }); +describe('ol.expression.lib', function() { + + var lib = ol.expression.lib; + var parse = ol.expression.parse; + + function evaluate(expression, feature) { + return expression.evaluate(feature.getAttributes(), lib, feature); + } + + describe('extent()', function() { + + var nw = new ol.Feature({ + geom: new ol.geom.Polygon([[ + [-180, 90], [0, 90], [0, 0], [-180, 0], [-180, 90] + ]]) + }); + + var se = new ol.Feature({ + geom: new ol.geom.Polygon([[ + [180, -90], [0, -90], [0, 0], [180, 0], [180, -90] + ]]) + }); + + var north = parse('extent(-100, 100, 40, 60)'); + var south = parse('extent(-100, 100, -60, -40)'); + var east = parse('extent(80, 100, -50, 50)'); + var west = parse('extent(-100, -80, -50, 50)'); + + expect(evaluate(north, nw), true); + expect(evaluate(south, nw), false); + expect(evaluate(east, nw), false); + expect(evaluate(west, nw), true); + + expect(evaluate(north, se), false); + expect(evaluate(south, se), true); + expect(evaluate(east, se), true); + expect(evaluate(west, se), false); + + }); + +}); + goog.require('ol.expression'); goog.require('ol.expression.Call'); From 38b784d672ea6131b632072e7505cc3922536c48 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 17 Jun 2013 16:17:09 -0600 Subject: [PATCH 70/84] Support +/- unary operators for literals --- src/ol/expression/expression.js | 16 +++++ src/ol/expression/parser.js | 32 +++++++-- test/spec/ol/expression/expression.test.js | 77 ++++++++++++++++++---- 3 files changed, 105 insertions(+), 20 deletions(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index a773b6cd8f..be55f011be 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -2,11 +2,27 @@ goog.provide('ol.expression'); goog.require('ol.Extent'); goog.require('ol.Feature'); +goog.require('ol.expression.Expression'); goog.require('ol.expression.Parser'); goog.require('ol.extent'); goog.require('ol.geom.GeometryType'); +/** + * Evaluate an expression with a feature. The feature attributes will be used + * as the evaluation scope. The `ol.expression.lib` functions will be used as + * function scope. The feature itself will be used as the `this` argument. + * + * @param {ol.expression.Expression} expr The expression. + * @param {ol.Feature} feature The feature. + * @return {*} The result of the expression. + */ +ol.expression.evaluateFeature = function(expr, feature) { + return expr.evaluate( + feature.getAttributes(), ol.expression.lib, feature); +}; + + /** * Parse an expression * @param {string} source The expression source (e.g. `'foo + 2'`). diff --git a/src/ol/expression/parser.js b/src/ol/expression/parser.js index 57627039b8..5b1544d496 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expression/parser.js @@ -199,16 +199,33 @@ ol.expression.Parser.prototype.createMemberExpression_ = function(object, /** - * Create a unary expression. + * Create a unary expression. The only true unary operator supported here is + * "!". For +/-, we apply the operator to literal expressions and return + * another literal. * - * @param {string} op Operator. + * @param {ol.expression.Token} op Operator. * @param {ol.expression.Expression} argument Expression. - * @return {ol.expression.Not} The logical not of the input expression. + * @return {ol.expression.Expression} The unary expression. * @private */ ol.expression.Parser.prototype.createUnaryExpression_ = function(op, argument) { - goog.asserts.assert(op === '!'); - return new ol.expression.Not(argument); + goog.asserts.assert(op.value === '!' || op.value === '+' || op.value === '-'); + var expr; + if (op.value === '!') { + expr = new ol.expression.Not(argument); + } else if (!(argument instanceof ol.expression.Literal)) { + throw new ol.expression.UnexpectedToken(op); + } else { + // we've got +/- literal + if (op.value === '+') { + expr = this.createLiteral_( + + /** @type {number|string|boolean|null} */ (argument.evaluate())); + } else { + expr = this.createLiteral_( + - /** @type {number|string|boolean|null} */ (argument.evaluate())); + } + } + return expr; }; @@ -440,10 +457,11 @@ ol.expression.Parser.prototype.parseUnaryExpression_ = function(lexer) { var operator = lexer.peek(); if (operator.type !== ol.expression.TokenType.PUNCTUATOR) { expr = this.parseLeftHandSideExpression_(lexer); - } else if (operator.value === '!') { + } else if (operator.value === '!' || operator.value === '-' || + operator.value === '+') { lexer.skip(); expr = this.parseUnaryExpression_(lexer); - expr = this.createUnaryExpression_('!', expr); + expr = this.createUnaryExpression_(operator, expr); } else { expr = this.parseLeftHandSideExpression_(lexer); } diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index d29700f31b..ab184ef344 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -524,12 +524,8 @@ describe('ol.expression.parse', function() { describe('ol.expression.lib', function() { - var lib = ol.expression.lib; var parse = ol.expression.parse; - - function evaluate(expression, feature) { - return expression.evaluate(feature.getAttributes(), lib, feature); - } + var evaluate = ol.expression.evaluateFeature; describe('extent()', function() { @@ -550,21 +546,73 @@ describe('ol.expression.lib', function() { var east = parse('extent(80, 100, -50, 50)'); var west = parse('extent(-100, -80, -50, 50)'); - expect(evaluate(north, nw), true); - expect(evaluate(south, nw), false); - expect(evaluate(east, nw), false); - expect(evaluate(west, nw), true); + it('evaluates to true for features within given extent', function() { - expect(evaluate(north, se), false); - expect(evaluate(south, se), true); - expect(evaluate(east, se), true); - expect(evaluate(west, se), false); + expect(evaluate(north, nw), true); + expect(evaluate(south, nw), false); + expect(evaluate(east, nw), false); + expect(evaluate(west, nw), true); + + expect(evaluate(north, se), false); + expect(evaluate(south, se), true); + expect(evaluate(east, se), true); + expect(evaluate(west, se), false); + + }); + + }); + + describe('geometryType()', function() { + + var point = new ol.Feature({ + geom: new ol.geom.Point([0, 0]) + }); + + var line = new ol.Feature({ + geom: new ol.geom.LineString([[180, -90], [-180, 90]]) + }); + + var poly = new ol.Feature({ + geom: new ol.geom.Polygon([[ + [180, -90], [0, -90], [0, 0], [180, 0], [180, -90] + ]]) + }); + + var isPoint = parse('geometryType("point")'); + var isLine = parse('geometryType("linestring")'); + var isPoly = parse('geometryType("polygon")'); + var pointOrPoly = parse('geometryType("point") || geometryType("polygon")'); + + it('distinguishes point features', function() { + expect(evaluate(isPoint, point), true); + expect(evaluate(isPoint, line), false); + expect(evaluate(isPoint, poly), false); + }); + + it('distinguishes line features', function() { + expect(evaluate(isLine, point), false); + expect(evaluate(isLine, line), true); + expect(evaluate(isLine, poly), false); + }); + + it('distinguishes polygon features', function() { + expect(evaluate(isPoly, point), false); + expect(evaluate(isPoly, line), false); + expect(evaluate(isPoly, poly), true); + }); + + it('can be composed in a logical expression', function() { + expect(evaluate(pointOrPoly, point), true); + expect(evaluate(pointOrPoly, line), false); + expect(evaluate(pointOrPoly, poly), true); + }); }); }); +goog.require('ol.Feature'); goog.require('ol.expression'); goog.require('ol.expression.Call'); goog.require('ol.expression.Comparison'); @@ -577,3 +625,6 @@ goog.require('ol.expression.Member'); goog.require('ol.expression.Not'); goog.require('ol.expression.TokenType'); goog.require('ol.expression.UnexpectedToken'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); From a663d8fcaeaa857eb63d9bdf415d9e6b25aa180e Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 17 Jun 2013 17:30:44 -0600 Subject: [PATCH 71/84] Accept an expression string for rule filter --- examples/style-rules.js | 13 +++---------- src/objectliterals.jsdoc | 2 +- src/ol/expression.jsdoc | 3 +++ src/ol/style/rule.js | 23 +++++++++++++++++++---- test/spec/ol/style/rule.test.js | 6 +++--- test/spec/ol/style/style.test.js | 5 +---- 6 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 src/ol/expression.jsdoc diff --git a/examples/style-rules.js b/examples/style-rules.js index 82d14b86e3..07a3a35a5c 100644 --- a/examples/style-rules.js +++ b/examples/style-rules.js @@ -3,9 +3,6 @@ goog.require('ol.RendererHint'); goog.require('ol.View2D'); goog.require('ol.control.defaults'); goog.require('ol.expression'); -goog.require('ol.filter.Filter'); -goog.require('ol.filter.Geometry'); -goog.require('ol.geom.GeometryType'); goog.require('ol.layer.Vector'); goog.require('ol.parser.GeoJSON'); goog.require('ol.proj'); @@ -19,9 +16,7 @@ goog.require('ol.style.Text'); var style = new ol.style.Style({rules: [ new ol.style.Rule({ - filter: new ol.filter.Filter(function(feature) { - return feature.get('where') == 'outer'; - }), + filter: 'where == "outer"', symbolizers: [ new ol.style.Line({ strokeColor: ol.expression.parse('color'), @@ -31,9 +26,7 @@ var style = new ol.style.Style({rules: [ ] }), new ol.style.Rule({ - filter: new ol.filter.Filter(function(feature) { - return feature.get('where') == 'inner'; - }), + filter: 'where == "inner"', symbolizers: [ new ol.style.Line({ strokeColor: '#013', @@ -48,7 +41,7 @@ var style = new ol.style.Style({rules: [ ] }), new ol.style.Rule({ - filter: new ol.filter.Geometry(ol.geom.GeometryType.POINT), + filter: 'geometryType("point")', symbolizers: [ new ol.style.Shape({ size: 40, diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index b7b5a2e460..0f5f852fee 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -552,7 +552,7 @@ /** * @typedef {Object} ol.style.RuleOptions - * @property {ol.filter.Filter|undefined} filter Filter. + * @property {ol.expression.Expression|string|undefined} filter Filter. * @property {Array.|undefined} symbolizers Symbolizers. */ diff --git a/src/ol/expression.jsdoc b/src/ol/expression.jsdoc new file mode 100644 index 0000000000..93f3016f63 --- /dev/null +++ b/src/ol/expression.jsdoc @@ -0,0 +1,3 @@ +/** + * @namespace ol.expression + */ diff --git a/src/ol/style/rule.js b/src/ol/style/rule.js index 0234fd9540..4410fdfc15 100644 --- a/src/ol/style/rule.js +++ b/src/ol/style/rule.js @@ -1,7 +1,10 @@ goog.provide('ol.style.Rule'); +goog.require('goog.asserts'); + goog.require('ol.Feature'); -goog.require('ol.filter.Filter'); +goog.require('ol.expression'); +goog.require('ol.expression.Expression'); goog.require('ol.style.Symbolizer'); @@ -12,11 +15,22 @@ goog.require('ol.style.Symbolizer'); */ ol.style.Rule = function(options) { + + var filter = null; + if (goog.isDef(options.filter)) { + if (goog.isString(options.filter)) { + filter = ol.expression.parse(options.filter); + } else { + goog.asserts.assert(options.filter instanceof ol.expression.Expression); + filter = options.filter; + } + } + /** - * @type {ol.filter.Filter} + * @type {ol.expression.Expression} * @private */ - this.filter_ = goog.isDef(options.filter) ? options.filter : null; + this.filter_ = filter; /** * @type {Array.} @@ -33,7 +47,8 @@ ol.style.Rule = function(options) { * @return {boolean} Does the rule apply to the feature? */ ol.style.Rule.prototype.applies = function(feature) { - return goog.isNull(this.filter_) ? true : this.filter_.applies(feature); + return goog.isNull(this.filter_) ? + true : !!ol.expression.evaluateFeature(this.filter_, feature); }; diff --git a/test/spec/ol/style/rule.test.js b/test/spec/ol/style/rule.test.js index d4649eda6c..789d9de889 100644 --- a/test/spec/ol/style/rule.test.js +++ b/test/spec/ol/style/rule.test.js @@ -13,14 +13,14 @@ describe('ol.style.Rule', function() { it('returns false when the rule does not apply', function() { rule = new ol.style.Rule({ - filter: new ol.filter.Filter(function() { return false; }) + filter: new ol.expression.Literal(false) }); expect(rule.applies(feature)).to.be(false); }); it('returns true when the rule applies', function() { rule = new ol.style.Rule({ - filter: new ol.filter.Filter(function() { return true; }) + filter: new ol.expression.Literal(true) }); expect(rule.applies(feature)).to.be(true); }); @@ -29,5 +29,5 @@ describe('ol.style.Rule', function() { }); goog.require('ol.Feature'); -goog.require('ol.filter.Filter'); +goog.require('ol.expression.Literal'); goog.require('ol.style.Rule'); diff --git a/test/spec/ol/style/style.test.js b/test/spec/ol/style/style.test.js index 685c09f3f3..6a35416548 100644 --- a/test/spec/ol/style/style.test.js +++ b/test/spec/ol/style/style.test.js @@ -9,9 +9,7 @@ describe('ol.style.Style', function() { var style = new ol.style.Style({ rules: [ new ol.style.Rule({ - filter: new ol.filter.Filter(function(feature) { - return feature.get('foo') == 'bar'; - }), + filter: 'foo == "bar"', symbolizers: [ new ol.style.Shape({ size: 4, @@ -66,7 +64,6 @@ goog.require('ol.Feature'); goog.require('ol.geom.LineString'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); -goog.require('ol.filter.Filter'); goog.require('ol.style.Rule'); goog.require('ol.style.Shape'); goog.require('ol.style.ShapeLiteral'); From b04a36ede769f0db35b2a8dae45e1e0629d99201 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 18 Jun 2013 13:37:06 -0600 Subject: [PATCH 72/84] Replace use of filter with expressions The canvas vector layer still has the (API candidate) `getFeatures` method that accepts an arbitrary expression (was filter). This, and the `getFeaturesObject` method under it are only used in the tests. The rendering code that was using filters is now calling `layer.getFeaturesObjectForExtent` with an explicit extent and optional geometry type. --- src/ol/expression/expression.js | 26 ++++ src/ol/filter.exports | 7 - src/ol/filter.jsdoc | 3 - src/ol/filter/extentfilter.js | 39 ----- src/ol/filter/filter.js | 36 ----- src/ol/filter/geometryfilter.js | 42 ----- src/ol/filter/logicalfilter.js | 128 ---------------- src/ol/layer/vectorlayer.js | 136 +++++++++++------ .../canvas/canvasvectorlayerrenderer.js | 44 +++--- test/spec/ol/filter/extentfilter.test.js | 36 ----- test/spec/ol/filter/geometryfilter.test.js | 51 ------- test/spec/ol/filter/logicalfilter.test.js | 144 ------------------ test/spec/ol/layer/vectorlayer.test.js | 45 +++--- 13 files changed, 158 insertions(+), 579 deletions(-) delete mode 100644 src/ol/filter.exports delete mode 100644 src/ol/filter.jsdoc delete mode 100644 src/ol/filter/extentfilter.js delete mode 100644 src/ol/filter/filter.js delete mode 100644 src/ol/filter/geometryfilter.js delete mode 100644 src/ol/filter/logicalfilter.js delete mode 100644 test/spec/ol/filter/extentfilter.test.js delete mode 100644 test/spec/ol/filter/geometryfilter.test.js delete mode 100644 test/spec/ol/filter/logicalfilter.test.js diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index be55f011be..1c3300e708 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -2,7 +2,9 @@ goog.provide('ol.expression'); goog.require('ol.Extent'); goog.require('ol.Feature'); +goog.require('ol.expression.Call'); goog.require('ol.expression.Expression'); +goog.require('ol.expression.Identifier'); goog.require('ol.expression.Parser'); goog.require('ol.extent'); goog.require('ol.geom.GeometryType'); @@ -35,6 +37,30 @@ ol.expression.parse = function(source) { }; +/** + * Determines whether an expression is a call expression that calls one of the + * `ol.expression.lib` functions. + * + * @param {ol.expression.Expression} expr The candidate expression. + * @return {string|undefined} If the candidate expression is a call to a lib + * function, the return will be the function name. If not, the return will be + * `undefined`. + */ +ol.expression.isLibCall = function(expr) { + var name; + if (expr instanceof ol.expression.Call) { + var callee = expr.getCallee(); + if (callee instanceof ol.expression.Identifier) { + name = callee.getName(); + if (!ol.expression.lib.hasOwnProperty(name)) { + name = undefined; + } + } + } + return name; +}; + + /** * Library of well-known functions. These are available to expressions parsed * with `ol.expression.parse`. diff --git a/src/ol/filter.exports b/src/ol/filter.exports deleted file mode 100644 index 696edee702..0000000000 --- a/src/ol/filter.exports +++ /dev/null @@ -1,7 +0,0 @@ -@exportSymbol ol.filter.Filter -@exportSymbol ol.filter.Geometry -@exportSymbol ol.filter.Logical - -@exportSymbol ol.filter.LogicalOperator -@exportProperty ol.filter.LogicalOperator.AND -@exportProperty ol.filter.LogicalOperator.OR diff --git a/src/ol/filter.jsdoc b/src/ol/filter.jsdoc deleted file mode 100644 index b9fad2900f..0000000000 --- a/src/ol/filter.jsdoc +++ /dev/null @@ -1,3 +0,0 @@ -/** - * @namespace ol.filter - */ diff --git a/src/ol/filter/extentfilter.js b/src/ol/filter/extentfilter.js deleted file mode 100644 index 2892246519..0000000000 --- a/src/ol/filter/extentfilter.js +++ /dev/null @@ -1,39 +0,0 @@ -goog.provide('ol.filter.Extent'); - -goog.require('ol.extent'); -goog.require('ol.filter.Filter'); - - - -/** - * @constructor - * @extends {ol.filter.Filter} - * @param {ol.Extent} extent The extent. - */ -ol.filter.Extent = function(extent) { - goog.base(this); - - /** - * @type {ol.Extent} - * @private - */ - this.extent_ = extent; - -}; -goog.inherits(ol.filter.Extent, ol.filter.Filter); - - -/** - * @return {ol.Extent} The filter extent. - */ -ol.filter.Extent.prototype.getExtent = function() { - return this.extent_; -}; - - -/** - * @inheritDoc - */ -ol.filter.Extent.prototype.applies = function(feature) { - return ol.extent.intersects(feature.getGeometry().getBounds(), this.extent_); -}; diff --git a/src/ol/filter/filter.js b/src/ol/filter/filter.js deleted file mode 100644 index 0d37a211dd..0000000000 --- a/src/ol/filter/filter.js +++ /dev/null @@ -1,36 +0,0 @@ -goog.provide('ol.filter.Filter'); - -goog.require('ol.Feature'); - - - -/** - * Create a new filter which can be used to filter features based on a - * function. - * - * Example: - * - * new ol.style.Rule({ - * filter: new ol.filter.Filter(function(feature) { - * return feature.get('where') == 'outer'; - * }), - * symbolizers: [ - * ... - * - * @constructor - * @param {function(this:ol.filter.Filter, ol.Feature)=} opt_filterFunction - * Filter function. Should return true if the passed feature passes the - * filter, false otherwise. - */ -ol.filter.Filter = function(opt_filterFunction) { - if (goog.isDef(opt_filterFunction)) { - this.applies = opt_filterFunction; - } -}; - - -/** - * @param {ol.Feature} feature Feature to evaluate the filter against. - * @return {boolean} The provided feature passes this filter. - */ -ol.filter.Filter.prototype.applies = goog.abstractMethod; diff --git a/src/ol/filter/geometryfilter.js b/src/ol/filter/geometryfilter.js deleted file mode 100644 index 1f6707f7e3..0000000000 --- a/src/ol/filter/geometryfilter.js +++ /dev/null @@ -1,42 +0,0 @@ -goog.provide('ol.filter.Geometry'); -goog.provide('ol.filter.GeometryType'); - -goog.require('ol.filter.Filter'); -goog.require('ol.geom.GeometryType'); - - - -/** - * Filter features by geometry type. - * @constructor - * @extends {ol.filter.Filter} - * @param {ol.geom.GeometryType} type The geometry type. - */ -ol.filter.Geometry = function(type) { - goog.base(this); - - /** - * @type {ol.geom.GeometryType} - * @private - */ - this.type_ = type; - -}; -goog.inherits(ol.filter.Geometry, ol.filter.Filter); - - -/** - * @inheritDoc - */ -ol.filter.Geometry.prototype.applies = function(feature) { - var geometry = feature.getGeometry(); - return goog.isNull(geometry) ? false : geometry.getType() === this.type_; -}; - - -/** - * @return {ol.geom.GeometryType} The geometry type. - */ -ol.filter.Geometry.prototype.getType = function() { - return this.type_; -}; diff --git a/src/ol/filter/logicalfilter.js b/src/ol/filter/logicalfilter.js deleted file mode 100644 index 1e97edad1e..0000000000 --- a/src/ol/filter/logicalfilter.js +++ /dev/null @@ -1,128 +0,0 @@ -goog.provide('ol.filter.Logical'); -goog.provide('ol.filter.LogicalOperator'); -goog.provide('ol.filter.and'); -goog.provide('ol.filter.not'); -goog.provide('ol.filter.or'); - -goog.require('goog.asserts'); -goog.require('ol.filter.Filter'); - - - -/** - * A filter to group (a) subfilter(s) by a logical operator (AND/OR/NOT). - * - * Example: - * - * var f1 = new ol.filter.Filter(myFunc1); - * var f2 = new ol.filter.Filter(myFunc2); - * // match one of the two filters above - * var logical = new ol.filter.Logical([f1, f2], - * ol.filter.LogicalOperator.OR); - * - * @constructor - * @extends {ol.filter.Filter} - * @param {Array.} filters Filters to combine. - * @param {ol.filter.LogicalOperator} operator Operator. - */ -ol.filter.Logical = function(filters, operator) { - goog.base(this); - - /** - * @type {Array.} - * @private - */ - this.filters_ = filters; - goog.asserts.assert(filters.length > 0, 'Must supply at least one filter'); - - /** - * @type {ol.filter.LogicalOperator} - */ - this.operator = operator; - -}; -goog.inherits(ol.filter.Logical, ol.filter.Filter); - - -/** - * @inheritDoc - */ -ol.filter.Logical.prototype.applies = function(feature) { - var filters = this.filters_, - i = 0, ii = filters.length, - result; - switch (this.operator) { - case ol.filter.LogicalOperator.AND: - result = true; - while (result && i < ii) { - result = result && filters[i].applies(feature); - ++i; - } - break; - case ol.filter.LogicalOperator.OR: - result = false; - while (!result && i < ii) { - result = result || filters[i].applies(feature); - ++i; - } - break; - case ol.filter.LogicalOperator.NOT: - result = !filters[i].applies(feature); - break; - default: - goog.asserts.assert(false, 'Unsupported operation: ' + this.operator); - } - return !!result; -}; - - -/** - * @return {Array.} The filter's filters. - */ -ol.filter.Logical.prototype.getFilters = function() { - return this.filters_; -}; - - -/** - * @enum {string} - */ -ol.filter.LogicalOperator = { - AND: '&&', - OR: '||', - NOT: '!' -}; - - -/** - * Create a filter that evaluates to true if all of the provided filters - * evaluate to true. - * @param {...ol.filter.Filter} var_args Filters. - * @return {ol.filter.Logical} A logical AND filter. - */ -ol.filter.and = function(var_args) { - var filters = Array.prototype.slice.call(arguments); - return new ol.filter.Logical(filters, ol.filter.LogicalOperator.AND); -}; - - -/** - * Create a new filter that is the logical compliment of another. - * @param {ol.filter.Filter} filter The filter to negate. - * @return {ol.filter.Logical} A logical NOT filter. - */ -ol.filter.not = function(filter) { - return new ol.filter.Logical([filter], ol.filter.LogicalOperator.NOT); -}; - - -/** - * Create a filter that evaluates to true if any of the provided filters - * evaluate to true. - * @param {...ol.filter.Filter} var_args Filters. - * @return {ol.filter.Logical} A logical OR filter. - */ -ol.filter.or = function(var_args) { - var filters = Array.prototype.slice.call(arguments); - return new ol.filter.Logical(filters, ol.filter.LogicalOperator.OR); -}; diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 12ffb250d5..261fd23f1d 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -5,6 +5,10 @@ goog.require('goog.asserts'); goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('ol.Feature'); +goog.require('ol.expression'); +goog.require('ol.expression.Literal'); +goog.require('ol.expression.Logical'); +goog.require('ol.expression.LogicalOp'); goog.require('ol.geom.GeometryType'); goog.require('ol.geom.SharedVertices'); goog.require('ol.layer.Layer'); @@ -79,35 +83,65 @@ ol.layer.FeatureCache.prototype.add = function(feature) { /** - * @param {ol.filter.Filter=} opt_filter Optional filter. + * @param {ol.expression.Expression=} opt_expr Expression for filtering. * @return {Object.} Object of features, keyed by id. */ -ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) { - var i, features; - if (!goog.isDef(opt_filter)) { +ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_expr) { + var features; + if (!goog.isDef(opt_expr)) { features = this.idLookup_; } else { - if (opt_filter instanceof ol.filter.Geometry) { - features = this.geometryTypeIndex_[opt_filter.getType()]; - } else if (opt_filter instanceof ol.filter.Extent) { - features = this.rTree_.searchReturningObject(opt_filter.getExtent()); - } else if (opt_filter instanceof ol.filter.Logical && - opt_filter.operator === ol.filter.LogicalOperator.AND) { - var filters = opt_filter.getFilters(); - if (filters.length === 2) { - var filter, geometryFilter, extentFilter; - for (i = 0; i <= 1; ++i) { - filter = filters[i]; - if (filter instanceof ol.filter.Geometry) { - geometryFilter = filter; - } else if (filter instanceof ol.filter.Extent) { - extentFilter = filter; + // check for geometryType or extent expression + var name = ol.expression.isLibCall(opt_expr); + if (name === 'geometryType') { + var args = /** @type {ol.expression.Call} */ (opt_expr).getArgs(); + goog.asserts.assert(args.length === 1); + goog.asserts.assert(args[0] instanceof ol.expression.Literal); + var type = /** @type {ol.expression.Literal } */ (args[0]).evaluate(); + goog.asserts.assertString(type); + features = this.geometryTypeIndex_[type]; + } else if (name === 'extent') { + var args = /** @type {ol.expression.Call} */ (opt_expr).getArgs(); + goog.asserts.assert(args.length === 4); + var extent = []; + for (var i = 0; i < 4; ++i) { + goog.asserts.assert(args[i] instanceof ol.expression.Literal); + extent[i] = /** @type {ol.expression.Literal} */ (args[i]).evaluate(); + goog.asserts.assertNumber(extent[i]); + } + features = this.rTree_.searchReturningObject(extent); + } else { + // not a call expression, check logical + if (opt_expr instanceof ol.expression.Logical) { + var op = /** @type {ol.expression.Logical} */ (opt_expr).getOperator(); + if (op === ol.expression.LogicalOp.AND) { + var expressions = [opt_expr.getLeft(), opt_expr.getRight()]; + var expr, args, type, extent; + for (var i = 0; i <= 1; ++i) { + expr = expressions[i]; + name = ol.expression.isLibCall(expr); + if (name === 'geometryType') { + args = /** @type {ol.expression.Call} */ (expr).getArgs(); + goog.asserts.assert(args.length === 1); + goog.asserts.assert(args[0] instanceof ol.expression.Literal); + type = /** @type {ol.expression.Literal } */ (args[0]).evaluate(); + goog.asserts.assertString(type); + } else if (name === 'extent') { + args = /** @type {ol.expression.Call} */ (expr).getArgs(); + goog.asserts.assert(args.length === 4); + extent = []; + for (var j = 0; j < 4; ++j) { + goog.asserts.assert(args[j] instanceof ol.expression.Literal); + extent[j] = + /** @type {ol.expression.Literal} */ (args[j]).evaluate(); + goog.asserts.assertNumber(extent[j]); + } + } + } + if (type && extent) { + features = this.getFeaturesObjectForExtent(extent, + /** @type {ol.geom.GeometryType} */ (type)); } - } - if (extentFilter && geometryFilter) { - var type = geometryFilter.getType(); - features = goog.object.isEmpty(this.geometryTypeIndex_[type]) ? {} : - this.rTree_.searchReturningObject(extentFilter.getExtent(), type); } } } @@ -118,7 +152,7 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) { features = {}; for (i in candidates) { feature = candidates[i]; - if (opt_filter.applies(feature) === true) { + if (ol.expression.evaluateFeature(opt_expr, feature)) { features[i] = feature; } } @@ -129,12 +163,22 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) { /** - * @param {ol.filter.Geometry} filter Geometry type filter. - * @return {Array.} Array of features. - * @private + * Get all features whose bounding box intersects the provided extent. + * + * @param {ol.Extent} extent Bounding extent. + * @param {ol.geom.GeometryType=} opt_type Optional geometry type. + * @return {Object.} Features. */ -ol.layer.FeatureCache.prototype.getFeaturesByGeometryType_ = function(filter) { - return goog.object.getValues(this.geometryTypeIndex_[filter.getType()]); +ol.layer.FeatureCache.prototype.getFeaturesObjectForExtent = function(extent, + opt_type) { + var features; + if (goog.isDef(opt_type) && + goog.object.isEmpty(this.geometryTypeIndex_[opt_type])) { + features = {}; + } else { + features = this.rTree_.searchReturningObject(extent, opt_type); + } + return features; }; @@ -234,21 +278,34 @@ ol.layer.Vector.prototype.getVectorSource = function() { /** - * @param {ol.filter.Filter=} opt_filter Optional filter. + * @param {ol.expression.Expression=} opt_expr Expression for filtering. * @return {Array.} Array of features. */ -ol.layer.Vector.prototype.getFeatures = function(opt_filter) { +ol.layer.Vector.prototype.getFeatures = function(opt_expr) { return goog.object.getValues( - this.featureCache_.getFeaturesObject(opt_filter)); + this.featureCache_.getFeaturesObject(opt_expr)); }; /** - * @param {ol.filter.Filter=} opt_filter Optional filter. + * @param {ol.expression.Expression=} opt_expr Expression for filtering. * @return {Object.} Features. */ -ol.layer.Vector.prototype.getFeaturesObject = function(opt_filter) { - return this.featureCache_.getFeaturesObject(opt_filter); +ol.layer.Vector.prototype.getFeaturesObject = function(opt_expr) { + return this.featureCache_.getFeaturesObject(opt_expr); +}; + + +/** + * Get all features whose bounding box intersects the provided extent. + * + * @param {ol.Extent} extent Bounding extent. + * @param {ol.geom.GeometryType=} opt_type Optional geometry type. + * @return {Object.} Features. + */ +ol.layer.Vector.prototype.getFeaturesObjectForExtent = function(extent, + opt_type) { + return this.featureCache_.getFeaturesObjectForExtent(extent, opt_type); }; @@ -415,10 +472,3 @@ ol.layer.Vector.uidTransformFeatureInfo = function(features) { function(feature) { return goog.getUid(feature); }); return featureIds.join(', '); }; - - -goog.require('ol.filter.Extent'); -goog.require('ol.filter.Geometry'); -goog.require('ol.filter.Logical'); -goog.require('ol.filter.LogicalOperator'); -goog.require('ol.geom.GeometryType'); diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index f690fbc589..343d783f72 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -12,10 +12,6 @@ goog.require('ol.TileCoord'); goog.require('ol.TileRange'); goog.require('ol.ViewHint'); goog.require('ol.extent'); -goog.require('ol.filter.Extent'); -goog.require('ol.filter.Geometry'); -goog.require('ol.filter.Logical'); -goog.require('ol.filter.LogicalOperator'); goog.require('ol.geom.GeometryType'); goog.require('ol.layer.Vector'); goog.require('ol.renderer.canvas.Layer'); @@ -101,18 +97,18 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) { this.tileArchetype_ = null; /** - * Geometry filters in rendering order. + * Geometry types in rendering order. * TODO: these will go away shortly (in favor of one call per symbolizer type) * @private - * @type {Array.} + * @type {Array.} */ - this.geometryFilters_ = [ - new ol.filter.Geometry(ol.geom.GeometryType.POINT), - new ol.filter.Geometry(ol.geom.GeometryType.MULTIPOINT), - new ol.filter.Geometry(ol.geom.GeometryType.LINESTRING), - new ol.filter.Geometry(ol.geom.GeometryType.MULTILINESTRING), - new ol.filter.Geometry(ol.geom.GeometryType.POLYGON), - new ol.filter.Geometry(ol.geom.GeometryType.MULTIPOLYGON) + this.geometryTypes_ = [ + ol.geom.GeometryType.POINT, + ol.geom.GeometryType.MULTIPOINT, + ol.geom.GeometryType.LINESTRING, + ol.geom.GeometryType.MULTILINESTRING, + ol.geom.GeometryType.POLYGON, + ol.geom.GeometryType.MULTIPOLYGON ]; /** @@ -251,13 +247,12 @@ ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel = var locationMin = [location[0] - halfMaxWidth, location[1] - halfMaxHeight]; var locationMax = [location[0] + halfMaxWidth, location[1] + halfMaxHeight]; var locationBbox = ol.extent.boundingExtent([locationMin, locationMax]); - var filter = new ol.filter.Extent(locationBbox); - var candidates = layer.getFeatures(filter); + var candidates = layer.getFeaturesObjectForExtent(locationBbox); var candidate, geom, type, symbolBounds, symbolSize, halfWidth, halfHeight, coordinates, j; - for (var i = 0, ii = candidates.length; i < ii; ++i) { - candidate = candidates[i]; + for (var id in candidates) { + candidate = candidates[id]; geom = candidate.getGeometry(); type = geom.getType(); if (type === ol.geom.GeometryType.POINT || @@ -445,11 +440,11 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = var tileGutter = 15 * tileResolution; var tile, tileCoord, key, x, y; // render features by geometry type - var filters = this.geometryFilters_, - numFilters = filters.length, + var types = this.geometryTypes_, + numTypes = types.length, deferred = false, dirty = false, - i, geomFilter, tileExtent, extentFilter, type, + i, type, tileExtent, groups, group, j, numGroups, featuresObject, tileHasFeatures; for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) { @@ -463,16 +458,13 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = tileExtent[1] += tileGutter; tileExtent[2] -= tileGutter; tileExtent[3] += tileGutter; - extentFilter = new ol.filter.Extent(tileExtent); tileHasFeatures = false; - for (i = 0; i < numFilters; ++i) { - geomFilter = filters[i]; - type = geomFilter.getType(); + for (i = 0; i < numTypes; ++i) { + type = types[i]; if (!goog.isDef(featuresToRender[type])) { featuresToRender[type] = {}; } - featuresObject = layer.getFeaturesObject(new ol.filter.Logical( - [geomFilter, extentFilter], ol.filter.LogicalOperator.AND)); + featuresObject = layer.getFeaturesObjectForExtent(tileExtent, type); tileHasFeatures = tileHasFeatures || !goog.object.isEmpty(featuresObject); goog.object.extend(featuresToRender[type], featuresObject); diff --git a/test/spec/ol/filter/extentfilter.test.js b/test/spec/ol/filter/extentfilter.test.js deleted file mode 100644 index 835d4411d8..0000000000 --- a/test/spec/ol/filter/extentfilter.test.js +++ /dev/null @@ -1,36 +0,0 @@ -goog.provide('ol.test.filter.Extent'); - - -describe('ol.filter.Extent', function() { - - var extent, filter; - - beforeEach(function() { - extent = [0, 45, 0, 90]; - filter = new ol.filter.Extent(extent); - }); - - describe('#getExtent()', function() { - - it('returns the configured extent', function() { - expect(filter.getExtent()).to.be(extent); - }); - - }); - - describe('#evaluate()', function() { - - it('returns true if a feature intersects, false if not', function() { - expect(filter.applies(new ol.Feature({g: new ol.geom.Point([44, 89])}))) - .to.be(true); - expect(filter.applies(new ol.Feature({g: new ol.geom.Point([46, 91])}))) - .to.be(false); - }); - - }); - -}); - -goog.require('ol.Feature'); -goog.require('ol.filter.Extent'); -goog.require('ol.geom.Point'); diff --git a/test/spec/ol/filter/geometryfilter.test.js b/test/spec/ol/filter/geometryfilter.test.js deleted file mode 100644 index ab64abc862..0000000000 --- a/test/spec/ol/filter/geometryfilter.test.js +++ /dev/null @@ -1,51 +0,0 @@ -goog.provide('ol.test.filter.Geometry'); - - -describe('ol.filter.Geometry', function() { - - describe('constructor', function() { - it('creates a new filter', function() { - var filter = new ol.filter.Geometry(ol.filter.GeometryType.POINT); - expect(filter).to.be.a(ol.filter.Geometry); - }); - }); - - describe('#getType()', function() { - - it('works for point', function() { - var filter = new ol.filter.Geometry(ol.filter.GeometryType.POINT); - expect(filter.getType()).to.be(ol.filter.GeometryType.POINT); - }); - - it('works for linestring', function() { - var filter = new ol.filter.Geometry(ol.filter.GeometryType.LINESTRING); - expect(filter.getType()).to.be(ol.filter.GeometryType.LINESTRING); - }); - - it('works for polygon', function() { - var filter = new ol.filter.Geometry(ol.filter.GeometryType.POLYGON); - expect(filter.getType()).to.be(ol.filter.GeometryType.POLYGON); - }); - - it('works for multi-point', function() { - var filter = new ol.filter.Geometry(ol.filter.GeometryType.MULTIPOINT); - expect(filter.getType()).to.be(ol.filter.GeometryType.MULTIPOINT); - }); - - it('works for multi-linestring', function() { - var filter = new ol.filter.Geometry( - ol.filter.GeometryType.MULTILINESTRING); - expect(filter.getType()).to.be(ol.filter.GeometryType.MULTILINESTRING); - }); - - it('works for multi-polygon', function() { - var filter = new ol.filter.Geometry(ol.filter.GeometryType.MULTIPOLYGON); - expect(filter.getType()).to.be(ol.filter.GeometryType.MULTIPOLYGON); - }); - - }); - -}); - -goog.require('ol.filter.Geometry'); -goog.require('ol.filter.GeometryType'); diff --git a/test/spec/ol/filter/logicalfilter.test.js b/test/spec/ol/filter/logicalfilter.test.js deleted file mode 100644 index 847d43af3f..0000000000 --- a/test/spec/ol/filter/logicalfilter.test.js +++ /dev/null @@ -1,144 +0,0 @@ -goog.provide('ol.test.filter.Logical'); - - -describe('ol.filter.Logical', function() { - - var OR = ol.filter.LogicalOperator.OR; - var AND = ol.filter.LogicalOperator.AND; - var NOT = ol.filter.LogicalOperator.NOT; - var include = new ol.filter.Filter(function() {return true;}); - var exclude = new ol.filter.Filter(function() {return false;}); - - var apple = new ol.Feature({}); - var orange = new ol.Feature({}); - var duck = new ol.Feature({}); - - var isApple = new ol.filter.Filter(function(feature) { - return feature === apple; - }); - var isOrange = new ol.filter.Filter(function(feature) { - return feature === orange; - }); - var isDuck = new ol.filter.Filter(function(feature) { - return feature === duck; - }); - - describe('constructor', function() { - it('creates a new filter', function() { - var filter = new ol.filter.Logical([include, exclude], OR); - expect(filter).to.be.a(ol.filter.Logical); - }); - }); - - describe('#operator', function() { - it('can be OR', function() { - var filter = new ol.filter.Logical([include, exclude], OR); - expect(filter.operator).to.be(OR); - }); - - it('can be AND', function() { - var filter = new ol.filter.Logical([include, exclude], AND); - expect(filter.operator).to.be(AND); - }); - - it('can be NOT', function() { - var filter = new ol.filter.Logical([include], NOT); - expect(filter.operator).to.be(NOT); - }); - }); - - describe('#applies', function() { - - it('works for OR', function() { - var isFruit = new ol.filter.Logical([isApple, isOrange], OR); - - expect(isApple.applies(apple)).to.be(true); - expect(isOrange.applies(apple)).to.be(false); - expect(isFruit.applies(apple)).to.be(true); - - expect(isApple.applies(duck)).to.be(false); - expect(isOrange.applies(duck)).to.be(false); - expect(isFruit.applies(duck)).to.be(false); - }); - - it('works for AND', function() { - expect(include.applies(apple)).to.be(true); - expect(isApple.applies(apple)).to.be(true); - expect(isDuck.applies(apple)).to.be(false); - - var pass = new ol.filter.Logical([include, isApple], AND); - expect(pass.applies(apple)).to.be(true); - - var fail = new ol.filter.Logical([isApple, isDuck], AND); - expect(fail.applies(apple)).to.be(false); - }); - - it('works for NOT', function() { - expect(isApple.applies(apple)).to.be(true); - expect(isDuck.applies(apple)).to.be(false); - expect(isDuck.applies(duck)).to.be(true); - expect(isDuck.applies(apple)).to.be(false); - - var notApple = new ol.filter.Logical([isApple], NOT); - expect(notApple.applies(apple)).to.be(false); - expect(notApple.applies(duck)).to.be(true); - - var notDuck = new ol.filter.Logical([isDuck], NOT); - expect(notDuck.applies(apple)).to.be(true); - expect(notDuck.applies(duck)).to.be(false); - }); - }); - -}); - -describe('ol.filter.and', function() { - it('creates the a logical AND filter', function() { - var a = new ol.filter.Filter(); - var b = new ol.filter.Filter(); - var c = new ol.filter.Filter(); - var and = ol.filter.and(a, b, c); - expect(and).to.be.a(ol.filter.Logical); - expect(and.operator).to.be(ol.filter.LogicalOperator.AND); - var filters = and.getFilters(); - expect(filters[0]).to.be(a); - expect(filters[1]).to.be(b); - expect(filters[2]).to.be(c); - }); -}); - -describe('ol.filter.not', function() { - it('creates the logical compliment of another filter', function() { - var include = new ol.filter.Filter(function() {return true;}); - var notInclude = ol.filter.not(include); - expect(notInclude).to.be.a(ol.filter.Logical); - expect(notInclude.applies()).to.be(false); - - var exclude = new ol.filter.Filter(function() {return false;}); - var notExclude = ol.filter.not(exclude); - expect(notExclude).to.be.a(ol.filter.Logical); - expect(notExclude.applies()).to.be(true); - }); -}); - -describe('ol.filter.or', function() { - it('creates the a logical OR filter', function() { - var a = new ol.filter.Filter(); - var b = new ol.filter.Filter(); - var c = new ol.filter.Filter(); - var and = ol.filter.or(a, b, c); - expect(and).to.be.a(ol.filter.Logical); - expect(and.operator).to.be(ol.filter.LogicalOperator.OR); - var filters = and.getFilters(); - expect(filters[0]).to.be(a); - expect(filters[1]).to.be(b); - expect(filters[2]).to.be(c); - }); -}); - -goog.require('ol.Feature'); -goog.require('ol.filter.Filter'); -goog.require('ol.filter.Logical'); -goog.require('ol.filter.LogicalOperator'); -goog.require('ol.filter.and'); -goog.require('ol.filter.not'); -goog.require('ol.filter.or'); diff --git a/test/spec/ol/layer/vectorlayer.test.js b/test/spec/ol/layer/vectorlayer.test.js index 5deba1c925..d1773d65b9 100644 --- a/test/spec/ol/layer/vectorlayer.test.js +++ b/test/spec/ol/layer/vectorlayer.test.js @@ -50,46 +50,46 @@ describe('ol.layer.Vector', function() { layer.addFeatures(features); }); - var geomFilter = new ol.filter.Geometry(ol.geom.GeometryType.LINESTRING); - var extentFilter = new ol.filter.Extent([16, 16.3, 48, 48.3]); + var geomFilter = ol.expression.parse('geometryType("linestring")'); + var extentFilter = ol.expression.parse('extent(16, 16.3, 48, 48.3)'); it('can filter by geometry type using its GeometryType index', function() { - sinon.spy(geomFilter, 'applies'); + sinon.spy(geomFilter, 'evaluate'); var lineStrings = layer.getFeatures(geomFilter); - expect(geomFilter.applies).to.not.be.called(); + expect(geomFilter.evaluate).to.not.be.called(); expect(lineStrings.length).to.eql(4); expect(lineStrings).to.contain(features[4]); }); it('can filter by extent using its RTree', function() { - sinon.spy(extentFilter, 'applies'); + sinon.spy(extentFilter, 'evaluate'); var subset = layer.getFeatures(extentFilter); - expect(extentFilter.applies).to.not.be.called(); + expect(extentFilter.evaluate).to.not.be.called(); expect(subset.length).to.eql(4); expect(subset).not.to.contain(features[7]); }); it('can filter by extent and geometry type using its index', function() { - var filter1 = new ol.filter.Logical([geomFilter, extentFilter], - ol.filter.LogicalOperator.AND); - var filter2 = new ol.filter.Logical([extentFilter, geomFilter], - ol.filter.LogicalOperator.AND); - sinon.spy(filter1, 'applies'); - sinon.spy(filter2, 'applies'); + var filter1 = new ol.expression.Logical( + ol.expression.LogicalOp.AND, geomFilter, extentFilter); + var filter2 = new ol.expression.Logical( + ol.expression.LogicalOp.AND, extentFilter, geomFilter); + sinon.spy(filter1, 'evaluate'); + sinon.spy(filter2, 'evaluate'); var subset1 = layer.getFeatures(filter1); var subset2 = layer.getFeatures(filter2); - expect(filter1.applies).to.not.be.called(); - expect(filter2.applies).to.not.be.called(); + expect(filter1.evaluate).to.not.be.called(); + expect(filter2.evaluate).to.not.be.called(); expect(subset1.length).to.eql(0); expect(subset2.length).to.eql(0); }); - it('can handle query using the filter\'s applies function', function() { - var filter = new ol.filter.Logical([geomFilter, extentFilter], - ol.filter.LogicalOperator.OR); - sinon.spy(filter, 'applies'); + it('can handle query using the filter\'s evaluate function', function() { + var filter = new ol.expression.Logical( + ol.expression.LogicalOp.OR, geomFilter, extentFilter); + sinon.spy(filter, 'evaluate'); var subset = layer.getFeatures(filter); - expect(filter.applies).to.be.called(); + expect(filter.evaluate).to.be.called(); expect(subset.length).to.eql(8); }); @@ -179,11 +179,8 @@ describe('ol.layer.Vector', function() { goog.require('goog.dispose'); goog.require('ol.Feature'); goog.require('ol.expression'); -goog.require('ol.filter.Extent'); -goog.require('ol.filter.Geometry'); -goog.require('ol.filter.Logical'); -goog.require('ol.filter.LogicalOperator'); -goog.require('ol.geom.GeometryType'); +goog.require('ol.expression.Logical'); +goog.require('ol.expression.LogicalOp'); goog.require('ol.geom.LineString'); goog.require('ol.geom.Point'); goog.require('ol.proj'); From c7da7e4c2e4139c44ec85b0d0a53c84aea049c93 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 21 Jun 2013 09:50:55 -0600 Subject: [PATCH 73/84] Using new expressions in text symbolizer --- src/objectliterals.jsdoc | 10 ++++---- src/ol/style/text.js | 52 ++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 0f5f852fee..6e246e5180 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -576,11 +576,11 @@ /** * @typedef {Object} ol.style.TextOptions - * @property {string|ol.Expression|undefined} color Color. - * @property {string|ol.Expression|undefined} fontFamily Font family. - * @property {number|ol.Expression|undefined} fontSize Font size in pixels. - * @property {string|ol.Expression} text Text for the label. - * @property {number|ol.Expression|undefined} opacity Opacity (0-1). + * @property {string|ol.expression.Expression|undefined} color Color. + * @property {string|ol.expression.Expression|undefined} fontFamily Font family. + * @property {number|ol.expression.Expression|undefined} fontSize Font size in pixels. + * @property {string|ol.expression.Expression} text Text for the label. + * @property {number|ol.expression.Expression|undefined} opacity Opacity (0-1). */ /** diff --git a/src/ol/style/text.js b/src/ol/style/text.js index 7528c158dc..0ffa64d391 100644 --- a/src/ol/style/text.js +++ b/src/ol/style/text.js @@ -2,8 +2,8 @@ goog.provide('ol.style.Text'); goog.provide('ol.style.TextLiteral'); goog.require('goog.asserts'); -goog.require('ol.Expression'); -goog.require('ol.ExpressionLiteral'); +goog.require('ol.expression.Expression'); +goog.require('ol.expression.Literal'); goog.require('ol.style.Symbolizer'); goog.require('ol.style.SymbolizerLiteral'); @@ -75,47 +75,47 @@ ol.style.TextLiteral.prototype.equals = function(textLiteral) { ol.style.Text = function(options) { /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.color_ = !goog.isDef(options.color) ? - new ol.ExpressionLiteral(ol.style.TextDefaults.color) : - (options.color instanceof ol.Expression) ? - options.color : new ol.ExpressionLiteral(options.color); + new ol.expression.Literal(ol.style.TextDefaults.color) : + (options.color instanceof ol.expression.Expression) ? + options.color : new ol.expression.Literal(options.color); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.fontFamily_ = !goog.isDef(options.fontFamily) ? - new ol.ExpressionLiteral(ol.style.TextDefaults.fontFamily) : - (options.fontFamily instanceof ol.Expression) ? - options.fontFamily : new ol.ExpressionLiteral(options.fontFamily); + new ol.expression.Literal(ol.style.TextDefaults.fontFamily) : + (options.fontFamily instanceof ol.expression.Expression) ? + options.fontFamily : new ol.expression.Literal(options.fontFamily); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.fontSize_ = !goog.isDef(options.fontSize) ? - new ol.ExpressionLiteral(ol.style.TextDefaults.fontSize) : - (options.fontSize instanceof ol.Expression) ? - options.fontSize : new ol.ExpressionLiteral(options.fontSize); + new ol.expression.Literal(ol.style.TextDefaults.fontSize) : + (options.fontSize instanceof ol.expression.Expression) ? + options.fontSize : new ol.expression.Literal(options.fontSize); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ - this.text_ = (options.text instanceof ol.Expression) ? - options.text : new ol.ExpressionLiteral(options.text); + this.text_ = (options.text instanceof ol.expression.Expression) ? + options.text : new ol.expression.Literal(options.text); /** - * @type {ol.Expression} + * @type {ol.expression.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.ExpressionLiteral(ol.style.TextDefaults.opacity) : - (options.opacity instanceof ol.Expression) ? - options.opacity : new ol.ExpressionLiteral(options.opacity); + new ol.expression.Literal(ol.style.TextDefaults.opacity) : + (options.opacity instanceof ol.expression.Expression) ? + options.opacity : new ol.expression.Literal(options.opacity); }; goog.inherits(ol.style.Text, ol.style.Symbolizer); @@ -132,19 +132,19 @@ ol.style.Text.prototype.createLiteral = function(opt_feature) { attrs = feature.getAttributes(); } - var color = this.color_.evaluate(feature, attrs); + var color = this.color_.evaluate(attrs, null, feature); goog.asserts.assertString(color, 'color must be a string'); - var fontFamily = this.fontFamily_.evaluate(feature, attrs); + var fontFamily = this.fontFamily_.evaluate(attrs, null, feature); goog.asserts.assertString(fontFamily, 'fontFamily must be a string'); - var fontSize = this.fontSize_.evaluate(feature, attrs); + var fontSize = this.fontSize_.evaluate(attrs, null, feature); goog.asserts.assertNumber(fontSize, 'fontSize must be a number'); - var text = this.text_.evaluate(feature, attrs); + var text = this.text_.evaluate(attrs, null, feature); goog.asserts.assertString(text, 'text must be a string'); - var opacity = this.opacity_.evaluate(feature, attrs); + var opacity = this.opacity_.evaluate(attrs, null, feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.TextLiteral({ From cab983f826bd1df59d62552179e5e45ede611b0d Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 21 Jun 2013 09:53:28 -0600 Subject: [PATCH 74/84] We are guarnteed that all TextLiteral properties are defined (see #770) Instead of using the browser defaults, we use `ol.style.TextDefaults`. --- src/ol/renderer/canvas/canvasvectorrenderer.js | 16 +++------------- src/ol/style/text.js | 9 ++------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/ol/renderer/canvas/canvasvectorrenderer.js b/src/ol/renderer/canvas/canvasvectorrenderer.js index 02d4924b3c..183412b679 100644 --- a/src/ol/renderer/canvas/canvasvectorrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorrenderer.js @@ -272,22 +272,12 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ = ol.renderer.canvas.VectorRenderer.prototype.renderText_ = function(features, text, texts) { var context = this.context_, - fontArray = [], - color = text.color, vecs, vec; - if (color) { - context.fillStyle = color; - } - if (goog.isDef(text.fontSize)) { - fontArray.push(text.fontSize + 'px'); - } - if (goog.isDef(text.fontFamily)) { - fontArray.push(text.fontFamily); - } - if (fontArray.length) { - context.font = fontArray.join(' '); + if (context.fillStyle !== text.color) { + context.fillStyle = text.color; } + context.font = text.fontSize + 'px ' + text.fontFamily; context.globalAlpha = text.opacity; // TODO: make alignments configurable diff --git a/src/ol/style/text.js b/src/ol/style/text.js index 0ffa64d391..200ce377b3 100644 --- a/src/ol/style/text.js +++ b/src/ol/style/text.js @@ -26,18 +26,13 @@ ol.style.TextLiteralOptions; */ ol.style.TextLiteral = function(options) { + goog.asserts.assertString(options.color, 'color must be a string'); /** @type {string} */ this.color = options.color; - if (goog.isDef(options.color)) { - goog.asserts.assertString(options.color, 'color must be a string'); - } + goog.asserts.assertString(options.fontFamily, 'fontFamily must be a string'); /** @type {string} */ this.fontFamily = options.fontFamily; - if (goog.isDef(options.fontFamily)) { - goog.asserts.assertString(options.fontFamily, - 'fontFamily must be a string'); - } goog.asserts.assertNumber(options.fontSize, 'fontSize must be a number'); /** @type {number} */ From 8e8b26805f78cf8ca0a9d60c0eb3902f350ac9f7 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 21 Jun 2013 10:42:11 -0600 Subject: [PATCH 75/84] Uniformly support evaluating symbolizers without features In combination with a (not yet implemented) `Symbolizer#isLiteral` method, calling `Symbolizer#evaluate` without a feature is the fast track for rendering batches of like-styled features. This change also simplifies the handling of undefined symbolizer literal properties (where stroke or fill properties are optional). --- src/ol/expression/expression.js | 14 ++++++++---- src/ol/style/icon.js | 33 ++++++++++++++-------------- src/ol/style/line.js | 14 +++++------- src/ol/style/polygon.js | 37 +++++++++++++++---------------- src/ol/style/shape.js | 39 ++++++++++++++++----------------- src/ol/style/text.js | 16 +++++--------- 6 files changed, 76 insertions(+), 77 deletions(-) diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index 1c3300e708..a5b444336d 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -16,12 +16,18 @@ goog.require('ol.geom.GeometryType'); * function scope. The feature itself will be used as the `this` argument. * * @param {ol.expression.Expression} expr The expression. - * @param {ol.Feature} feature The feature. + * @param {ol.Feature=} opt_feature The feature. * @return {*} The result of the expression. */ -ol.expression.evaluateFeature = function(expr, feature) { - return expr.evaluate( - feature.getAttributes(), ol.expression.lib, feature); +ol.expression.evaluateFeature = function(expr, opt_feature) { + var result; + if (goog.isDef(opt_feature)) { + result = expr.evaluate( + opt_feature.getAttributes(), ol.expression.lib, opt_feature); + } else { + result = expr.evaluate(); + } + return result; }; diff --git a/src/ol/style/icon.js b/src/ol/style/icon.js index 4a3b148a10..f4116b6c79 100644 --- a/src/ol/style/icon.js +++ b/src/ol/style/icon.js @@ -3,6 +3,7 @@ goog.provide('ol.style.IconLiteral'); goog.provide('ol.style.IconType'); goog.require('goog.asserts'); +goog.require('ol.expression'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Literal'); goog.require('ol.style.Point'); @@ -118,28 +119,28 @@ ol.style.Icon = function(options) { * @inheritDoc * @return {ol.style.IconLiteral} Literal shape symbolizer. */ -ol.style.Icon.prototype.createLiteral = function(feature) { - var attrs = feature && feature.getAttributes(); +ol.style.Icon.prototype.createLiteral = function(opt_feature) { - var url = /** @type {string} */ (this.url_.evaluate(attrs, null, feature)); - goog.asserts.assert(goog.isString(url) && url != '#', 'url must be a string'); + var url = ol.expression.evaluateFeature(this.url_, opt_feature); + goog.asserts.assertString(url, 'url must be a string'); + goog.asserts.assert(url != '#', 'url must not be "#"'); - var width = /** @type {number|undefined} */ (goog.isNull(this.width_) ? - undefined : this.width_.evaluate(attrs, null, feature)); - goog.asserts.assert(!goog.isDef(width) || goog.isNumber(width), - 'width must be undefined or a number'); + var width; + if (!goog.isNull(this.width_)) { + width = ol.expression.evaluateFeature(this.width_, opt_feature); + goog.asserts.assertNumber(width, 'width must be a number'); + } - var height = /** @type {number|undefined} */ (goog.isNull(this.height_) ? - undefined : this.height_.evaluate(attrs, null, feature)); - goog.asserts.assert(!goog.isDef(height) || goog.isNumber(height), - 'height must be undefined or a number'); + var height; + if (!goog.isNull(this.height_)) { + height = ol.expression.evaluateFeature(this.height_, opt_feature); + goog.asserts.assertNumber(height, 'height must be a number'); + } - var opacity = /** {@type {number} */ (this.opacity_.evaluate(attrs, null, - feature)); + var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); - var rotation = - /** {@type {number} */ (this.rotation_.evaluate(attrs, null, feature)); + var rotation = ol.expression.evaluateFeature(this.rotation_, opt_feature); goog.asserts.assertNumber(rotation, 'rotation must be a number'); return new ol.style.IconLiteral({ diff --git a/src/ol/style/line.js b/src/ol/style/line.js index e226ea085c..5c9e249580 100644 --- a/src/ol/style/line.js +++ b/src/ol/style/line.js @@ -2,6 +2,7 @@ goog.provide('ol.style.Line'); goog.provide('ol.style.LineLiteral'); goog.require('goog.asserts'); +goog.require('ol.expression'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Literal'); goog.require('ol.style.Symbolizer'); @@ -98,19 +99,16 @@ goog.inherits(ol.style.Line, ol.style.Symbolizer); * @return {ol.style.LineLiteral} Literal line symbolizer. */ ol.style.Line.prototype.createLiteral = function(opt_feature) { - var attrs, - feature = opt_feature; - if (goog.isDef(feature)) { - attrs = feature.getAttributes(); - } - var strokeColor = this.strokeColor_.evaluate(attrs, null, feature); + var strokeColor = ol.expression.evaluateFeature( + this.strokeColor_, opt_feature); goog.asserts.assertString(strokeColor, 'strokeColor must be a string'); - var strokeWidth = this.strokeWidth_.evaluate(attrs, null, feature); + var strokeWidth = ol.expression.evaluateFeature( + this.strokeWidth_, opt_feature); goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number'); - var opacity = this.opacity_.evaluate(attrs, null, feature); + var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.LineLiteral({ diff --git a/src/ol/style/polygon.js b/src/ol/style/polygon.js index 6819fff56f..df683438a6 100644 --- a/src/ol/style/polygon.js +++ b/src/ol/style/polygon.js @@ -2,6 +2,7 @@ goog.provide('ol.style.Polygon'); goog.provide('ol.style.PolygonLiteral'); goog.require('goog.asserts'); +goog.require('ol.expression'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Literal'); goog.require('ol.style.Symbolizer'); @@ -149,33 +150,31 @@ goog.inherits(ol.style.Polygon, ol.style.Symbolizer); * @return {ol.style.PolygonLiteral} Literal shape symbolizer. */ ol.style.Polygon.prototype.createLiteral = function(opt_feature) { - var attrs, - feature = opt_feature; - if (goog.isDef(feature)) { - attrs = feature.getAttributes(); + + var fillColor; + if (!goog.isNull(this.fillColor_)) { + fillColor = ol.expression.evaluateFeature(this.fillColor_, opt_feature); + goog.asserts.assertString(fillColor, 'fillColor must be a string'); } - var fillColor = goog.isNull(this.fillColor_) ? - undefined : - /** @type {string} */ (this.fillColor_.evaluate(attrs, null, feature)); - goog.asserts.assert(!goog.isDef(fillColor) || goog.isString(fillColor)); + var strokeColor; + if (!goog.isNull(this.strokeColor_)) { + strokeColor = ol.expression.evaluateFeature(this.strokeColor_, opt_feature); + goog.asserts.assertString(strokeColor, 'strokeColor must be a string'); + } - var strokeColor = goog.isNull(this.strokeColor_) ? - undefined : - /** @type {string} */ (this.strokeColor_.evaluate(attrs, null, feature)); - goog.asserts.assert(!goog.isDef(strokeColor) || goog.isString(strokeColor)); - - var strokeWidth = goog.isNull(this.strokeWidth_) ? - undefined : - /** @type {number} */ (this.strokeWidth_.evaluate(attrs, null, feature)); - goog.asserts.assert(!goog.isDef(strokeWidth) || goog.isNumber(strokeWidth)); + var strokeWidth; + if (!goog.isNull(this.strokeWidth_)) { + strokeWidth = ol.expression.evaluateFeature(this.strokeWidth_, opt_feature); + goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number'); + } goog.asserts.assert( goog.isDef(fillColor) || (goog.isDef(strokeColor) && goog.isDef(strokeWidth)), - 'either fill style or strokeColor and strokeWidth must be defined'); + 'either fillColor or strokeColor and strokeWidth must be defined'); - var opacity = this.opacity_.evaluate(attrs, null, feature); + var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.PolygonLiteral({ diff --git a/src/ol/style/shape.js b/src/ol/style/shape.js index b1ff0e1bf3..5c8802b47f 100644 --- a/src/ol/style/shape.js +++ b/src/ol/style/shape.js @@ -3,6 +3,7 @@ goog.provide('ol.style.ShapeLiteral'); goog.provide('ol.style.ShapeType'); goog.require('goog.asserts'); +goog.require('ol.expression'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Literal'); goog.require('ol.style.Point'); @@ -184,36 +185,34 @@ ol.style.Shape = function(options) { * @return {ol.style.ShapeLiteral} Literal shape symbolizer. */ ol.style.Shape.prototype.createLiteral = function(opt_feature) { - var attrs, - feature = opt_feature; - if (goog.isDef(feature)) { - attrs = feature.getAttributes(); - } - var size = this.size_.evaluate(attrs, null, feature); + var size = ol.expression.evaluateFeature(this.size_, opt_feature); goog.asserts.assertNumber(size, 'size must be a number'); - var fillColor = goog.isNull(this.fillColor_) ? - undefined : - /** @type {string} */ (this.fillColor_.evaluate(attrs, null, feature)); - goog.asserts.assert(!goog.isDef(fillColor) || goog.isString(fillColor)); + var fillColor; + if (!goog.isNull(this.fillColor_)) { + fillColor = ol.expression.evaluateFeature(this.fillColor_, opt_feature); + goog.asserts.assertString(fillColor, 'fillColor must be a string'); + } - var strokeColor = goog.isNull(this.strokeColor_) ? - undefined : - /** @type {string} */ (this.strokeColor_.evaluate(attrs, null, feature)); - goog.asserts.assert(!goog.isDef(strokeColor) || goog.isString(strokeColor)); + var strokeColor; + if (!goog.isNull(this.strokeColor_)) { + strokeColor = ol.expression.evaluateFeature(this.strokeColor_, opt_feature); + goog.asserts.assertString(strokeColor, 'strokeColor must be a string'); + } - var strokeWidth = goog.isNull(this.strokeWidth_) ? - undefined : - /** @type {number} */ (this.strokeWidth_.evaluate(attrs, null, feature)); - goog.asserts.assert(!goog.isDef(strokeWidth) || goog.isNumber(strokeWidth)); + var strokeWidth; + if (!goog.isNull(this.strokeWidth_)) { + strokeWidth = ol.expression.evaluateFeature(this.strokeWidth_, opt_feature); + goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number'); + } goog.asserts.assert( goog.isDef(fillColor) || (goog.isDef(strokeColor) && goog.isDef(strokeWidth)), - 'either fill style or strokeColor and strokeWidth must be defined'); + 'either fillColor or strokeColor and strokeWidth must be defined'); - var opacity = this.opacity_.evaluate(attrs, null, feature); + var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.ShapeLiteral({ diff --git a/src/ol/style/text.js b/src/ol/style/text.js index 200ce377b3..1c8b4005f9 100644 --- a/src/ol/style/text.js +++ b/src/ol/style/text.js @@ -2,6 +2,7 @@ goog.provide('ol.style.Text'); goog.provide('ol.style.TextLiteral'); goog.require('goog.asserts'); +goog.require('ol.expression'); goog.require('ol.expression.Expression'); goog.require('ol.expression.Literal'); goog.require('ol.style.Symbolizer'); @@ -121,25 +122,20 @@ goog.inherits(ol.style.Text, ol.style.Symbolizer); * @return {ol.style.TextLiteral} Literal text symbolizer. */ ol.style.Text.prototype.createLiteral = function(opt_feature) { - var attrs, - feature = opt_feature; - if (goog.isDef(feature)) { - attrs = feature.getAttributes(); - } - var color = this.color_.evaluate(attrs, null, feature); + var color = ol.expression.evaluateFeature(this.color_, opt_feature); goog.asserts.assertString(color, 'color must be a string'); - var fontFamily = this.fontFamily_.evaluate(attrs, null, feature); + var fontFamily = ol.expression.evaluateFeature(this.fontFamily_, opt_feature); goog.asserts.assertString(fontFamily, 'fontFamily must be a string'); - var fontSize = this.fontSize_.evaluate(attrs, null, feature); + var fontSize = ol.expression.evaluateFeature(this.fontSize_, opt_feature); goog.asserts.assertNumber(fontSize, 'fontSize must be a number'); - var text = this.text_.evaluate(attrs, null, feature); + var text = ol.expression.evaluateFeature(this.text_, opt_feature); goog.asserts.assertString(text, 'text must be a string'); - var opacity = this.opacity_.evaluate(attrs, null, feature); + var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.TextLiteral({ From 233595ac758429b178be4b7d67900efa4e0e8cb0 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 21 Jun 2013 11:13:20 -0600 Subject: [PATCH 76/84] Allow registration of custom functions for expressions --- examples/style-rules.js | 2 +- examples/vector-layer.js | 14 ++++++++------ src/ol/expression/expression.exports | 1 + src/ol/expression/expression.js | 14 +++++++++++++- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/examples/style-rules.js b/examples/style-rules.js index 07a3a35a5c..143a10caf9 100644 --- a/examples/style-rules.js +++ b/examples/style-rules.js @@ -49,7 +49,7 @@ var style = new ol.style.Style({rules: [ }), new ol.style.Text({ color: '#bada55', - text: new ol.Expression('label'), + text: ol.expression.parse('label'), fontFamily: 'Calibri,sans-serif', fontSize: 14 }) diff --git a/examples/vector-layer.js b/examples/vector-layer.js index afd596c286..62ee037394 100644 --- a/examples/vector-layer.js +++ b/examples/vector-layer.js @@ -1,8 +1,7 @@ -goog.require('ol.Expression'); goog.require('ol.Map'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); -goog.require('ol.filter.Filter'); +goog.require('ol.expression'); goog.require('ol.layer.TileLayer'); goog.require('ol.layer.Vector'); goog.require('ol.parser.GeoJSON'); @@ -19,6 +18,11 @@ var raster = new ol.layer.TileLayer({ source: new ol.source.MapQuestOpenAerial() }); +// TODO: discuss scale dependent rules +ol.expression.register('resolution', function() { + return map.getView().getView2D().getResolution(); +}); + var vector = new ol.layer.Vector({ source: new ol.source.Vector({ projection: ol.proj.get('EPSG:4326') @@ -32,13 +36,11 @@ var vector = new ol.layer.Vector({ ] }), new ol.style.Rule({ - filter: new ol.filter.Filter(function() { - return map.getView().getResolution() < 5000; - }), + filter: 'resolution() < 5000', symbolizers: [ new ol.style.Text({ color: '#bada55', - text: new ol.Expression('name'), + text: ol.expression.parse('name'), fontFamily: 'Calibri,sans-serif', fontSize: 12 }) diff --git a/src/ol/expression/expression.exports b/src/ol/expression/expression.exports index 8f9155e23f..e3702d1e8b 100644 --- a/src/ol/expression/expression.exports +++ b/src/ol/expression/expression.exports @@ -1 +1,2 @@ @exportSymbol ol.expression.parse +@exportSymbol ol.expression.register diff --git a/src/ol/expression/expression.js b/src/ol/expression/expression.js index a5b444336d..388317e9d3 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expression/expression.js @@ -32,7 +32,7 @@ ol.expression.evaluateFeature = function(expr, opt_feature) { /** - * Parse an expression + * Parse an expression. * @param {string} source The expression source (e.g. `'foo + 2'`). * @return {ol.expression.Expression} An expression instance that can be * evaluated within some scope to provide a value. @@ -43,6 +43,18 @@ ol.expression.parse = function(source) { }; +/** + * Register a library function to be used in expressions. + * @param {string} name The function name (e.g. 'myFunc'). + * @param {function(this:ol.Feature)} func The function to be called in an + * expression. This function will be called with a feature as the `this` + * argument when the expression is evaluated in the context of a features. + */ +ol.expression.register = function(name, func) { + ol.expression.lib[name] = func; +}; + + /** * Determines whether an expression is a call expression that calls one of the * `ol.expression.lib` functions. From 9928730bd3854375616608ff8637b35e9cf8633e Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 21 Jun 2013 16:36:07 -0600 Subject: [PATCH 77/84] Tests for ol.expression.register() --- test/spec/ol/expression/expression.test.js | 49 +++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expression/expression.test.js index ab184ef344..7b330b4d1c 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expression/expression.test.js @@ -1,7 +1,7 @@ goog.provide('ol.test.expression'); -describe('ol.expression.parse', function() { +describe('ol.expression.parse()', function() { it('parses a subset of ECMAScript 5.1 expressions', function() { var expr = ol.expression.parse('foo'); @@ -611,6 +611,53 @@ describe('ol.expression.lib', function() { }); +describe('ol.expression.register()', function() { + + var spy; + beforeEach(function() { + spy = sinon.spy(); + }); + + it('registers custom functions in ol.expression.lib', function() { + ol.expression.register('someFunc', spy); + expect(ol.expression.lib.someFunc).to.be(spy); + }); + + it('allows custom functions to be called', function() { + ol.expression.register('myFunc', spy); + var expr = ol.expression.parse('myFunc(42)'); + expr.evaluate(null, ol.expression.lib); + expect(spy.calledOnce); + expect(spy.calledWithExactly(42)); + }); + + it('allows custom functions to be called with identifiers', function() { + ol.expression.register('myFunc', spy); + var expr = ol.expression.parse('myFunc(foo, 42)'); + expr.evaluate({foo: 'bar'}, ol.expression.lib); + expect(spy.calledOnce); + expect(spy.calledWithExactly('bar', 42)); + }); + + it('allows custom functions to be called with custom this obj', function() { + ol.expression.register('myFunc', spy); + var expr = ol.expression.parse('myFunc(foo, 42)'); + var that = {}; + expr.evaluate({foo: 'bar'}, ol.expression.lib, that); + expect(spy.calledOnce); + expect(spy.calledWithExactly('bar', 42)); + expect(spy.calledOn(that)); + }); + + it('allows overriding existing ol.expression.lib functions', function() { + expect(ol.expression.lib.extent).not.to.be(spy); + ol.expression.register('extent', spy); + expect(ol.expression.lib.extent).to.be(spy); + }); + +}); + + goog.require('ol.Feature'); goog.require('ol.expression'); From 2577d3f7d694677b9e0ca04d905f81ea76a7cd3d Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 21 Jun 2013 17:29:16 -0600 Subject: [PATCH 78/84] Rename ol.expression to ol.expr --- examples/style-rules.js | 8 +- examples/vector-layer.js | 6 +- src/objectliterals.jsdoc | 46 +- src/ol/expr.jsdoc | 3 + src/ol/expr/expression.exports | 2 + src/ol/{expression => expr}/expression.js | 44 +- src/ol/{expression => expr}/expressions.js | 258 +++---- src/ol/{expression => expr}/lexer.js | 292 ++++---- src/ol/{expression => expr}/parser.js | 234 +++---- src/ol/expression.jsdoc | 3 - src/ol/expression/expression.exports | 2 - src/ol/layer/vectorlayer.js | 50 +- src/ol/style/icon.js | 50 +- src/ol/style/line.js | 36 +- src/ol/style/polygon.js | 44 +- src/ol/style/rule.js | 12 +- src/ol/style/shape.js | 54 +- src/ol/style/text.js | 54 +- .../{expression => expr}/expression.test.js | 290 ++++---- test/spec/ol/expr/expressions.test.js | 638 ++++++++++++++++++ .../ol/{expression => expr}/lexer.test.js | 166 ++--- .../ol/{expression => expr}/parser.test.js | 102 +-- test/spec/ol/expression/expressions.test.js | 638 ------------------ test/spec/ol/layer/vectorlayer.test.js | 26 +- test/spec/ol/style/line.test.js | 10 +- test/spec/ol/style/polygon.test.js | 10 +- test/spec/ol/style/rule.test.js | 6 +- test/spec/ol/style/shape.test.js | 14 +- 28 files changed, 1550 insertions(+), 1548 deletions(-) create mode 100644 src/ol/expr.jsdoc create mode 100644 src/ol/expr/expression.exports rename src/ol/{expression => expr}/expression.js (71%) rename src/ol/{expression => expr}/expressions.js (54%) rename src/ol/{expression => expr}/lexer.js (67%) rename src/ol/{expression => expr}/parser.js (57%) delete mode 100644 src/ol/expression.jsdoc delete mode 100644 src/ol/expression/expression.exports rename test/spec/ol/{expression => expr}/expression.test.js (68%) create mode 100644 test/spec/ol/expr/expressions.test.js rename test/spec/ol/{expression => expr}/lexer.test.js (67%) rename test/spec/ol/{expression => expr}/parser.test.js (68%) delete mode 100644 test/spec/ol/expression/expressions.test.js diff --git a/examples/style-rules.js b/examples/style-rules.js index 143a10caf9..eafea47bd7 100644 --- a/examples/style-rules.js +++ b/examples/style-rules.js @@ -2,7 +2,7 @@ goog.require('ol.Map'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); goog.require('ol.control.defaults'); -goog.require('ol.expression'); +goog.require('ol.expr'); goog.require('ol.layer.Vector'); goog.require('ol.parser.GeoJSON'); goog.require('ol.proj'); @@ -19,7 +19,7 @@ var style = new ol.style.Style({rules: [ filter: 'where == "outer"', symbolizers: [ new ol.style.Line({ - strokeColor: ol.expression.parse('color'), + strokeColor: ol.expr.parse('color'), strokeWidth: 4, opacity: 1 }) @@ -34,7 +34,7 @@ var style = new ol.style.Style({rules: [ opacity: 1 }), new ol.style.Line({ - strokeColor: ol.expression.parse('color'), + strokeColor: ol.expr.parse('color'), strokeWidth: 2, opacity: 1 }) @@ -49,7 +49,7 @@ var style = new ol.style.Style({rules: [ }), new ol.style.Text({ color: '#bada55', - text: ol.expression.parse('label'), + text: ol.expr.parse('label'), fontFamily: 'Calibri,sans-serif', fontSize: 14 }) diff --git a/examples/vector-layer.js b/examples/vector-layer.js index 62ee037394..05774f56f5 100644 --- a/examples/vector-layer.js +++ b/examples/vector-layer.js @@ -1,7 +1,7 @@ goog.require('ol.Map'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); -goog.require('ol.expression'); +goog.require('ol.expr'); goog.require('ol.layer.TileLayer'); goog.require('ol.layer.Vector'); goog.require('ol.parser.GeoJSON'); @@ -19,7 +19,7 @@ var raster = new ol.layer.TileLayer({ }); // TODO: discuss scale dependent rules -ol.expression.register('resolution', function() { +ol.expr.register('resolution', function() { return map.getView().getView2D().getResolution(); }); @@ -40,7 +40,7 @@ var vector = new ol.layer.Vector({ symbolizers: [ new ol.style.Text({ color: '#bada55', - text: ol.expression.parse('name'), + text: ol.expr.parse('name'), fontFamily: 'Calibri,sans-serif', fontSize: 12 }) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 6e246e5180..5ef74a191b 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -519,54 +519,54 @@ /** * @typedef {Object} ol.style.IconOptions - * @property {string|ol.expression.Expression} url Icon image url. - * @property {number|ol.expression.Expression|undefined} width Width of the icon + * @property {string|ol.expr.Expression} url Icon image url. + * @property {number|ol.expr.Expression|undefined} width Width of the icon * in pixels. Default is the width of the icon image. - * @property {number|ol.expression.Expression|undefined} height Height of the + * @property {number|ol.expr.Expression|undefined} height Height of the * icon in pixels. Default is the height of the icon image. - * @property {number|ol.expression.Expression|undefined} opacity Icon opacity + * @property {number|ol.expr.Expression|undefined} opacity Icon opacity * (0-1). - * @property {number|ol.expression.Expression|undefined} rotation Rotation in + * @property {number|ol.expr.Expression|undefined} rotation Rotation in * degrees (0-360). */ /** * @typedef {Object} ol.style.LineOptions - * @property {string|ol.expression.Expression|undefined} strokeColor Stroke + * @property {string|ol.expr.Expression|undefined} strokeColor Stroke * color as hex color code. - * @property {number|ol.expression.Expression|undefined} strokeWidth Stroke + * @property {number|ol.expr.Expression|undefined} strokeWidth Stroke * width in pixels. - * @property {number|ol.expression.Expression|undefined} opacity Opacity (0-1). + * @property {number|ol.expr.Expression|undefined} opacity Opacity (0-1). */ /** * @typedef {Object} ol.style.PolygonOptions - * @property {string|ol.expression.Expression|undefined} fillColor Fill color as + * @property {string|ol.expr.Expression|undefined} fillColor Fill color as * hex color code. - * @property {string|ol.expression.Expression|undefined} strokeColor Stroke + * @property {string|ol.expr.Expression|undefined} strokeColor Stroke * color as hex color code. - * @property {number|ol.expression.Expression|undefined} strokeWidth Stroke + * @property {number|ol.expr.Expression|undefined} strokeWidth Stroke * width in pixels. - * @property {number|ol.expression.Expression|undefined} opacity Opacity (0-1). + * @property {number|ol.expr.Expression|undefined} opacity Opacity (0-1). */ /** * @typedef {Object} ol.style.RuleOptions - * @property {ol.expression.Expression|string|undefined} filter Filter. + * @property {ol.expr.Expression|string|undefined} filter Filter. * @property {Array.|undefined} symbolizers Symbolizers. */ /** * @typedef {Object} ol.style.ShapeOptions * @property {ol.style.ShapeType|undefined} type Type. - * @property {number|ol.expression.Expression|undefined} size Size in pixels. - * @property {string|ol.expression.Expression|undefined} fillColor Fill color as + * @property {number|ol.expr.Expression|undefined} size Size in pixels. + * @property {string|ol.expr.Expression|undefined} fillColor Fill color as * hex color code. - * @property {string|ol.expression.Expression|undefined} strokeColor Stroke + * @property {string|ol.expr.Expression|undefined} strokeColor Stroke * color as hex color code. - * @property {number|ol.expression.Expression|undefined} strokeWidth Stroke + * @property {number|ol.expr.Expression|undefined} strokeWidth Stroke * width in pixels. - * @property {number|ol.expression.Expression|undefined} opacity Opacity (0-1). + * @property {number|ol.expr.Expression|undefined} opacity Opacity (0-1). */ /** @@ -576,11 +576,11 @@ /** * @typedef {Object} ol.style.TextOptions - * @property {string|ol.expression.Expression|undefined} color Color. - * @property {string|ol.expression.Expression|undefined} fontFamily Font family. - * @property {number|ol.expression.Expression|undefined} fontSize Font size in pixels. - * @property {string|ol.expression.Expression} text Text for the label. - * @property {number|ol.expression.Expression|undefined} opacity Opacity (0-1). + * @property {string|ol.expr.Expression|undefined} color Color. + * @property {string|ol.expr.Expression|undefined} fontFamily Font family. + * @property {number|ol.expr.Expression|undefined} fontSize Font size in pixels. + * @property {string|ol.expr.Expression} text Text for the label. + * @property {number|ol.expr.Expression|undefined} opacity Opacity (0-1). */ /** diff --git a/src/ol/expr.jsdoc b/src/ol/expr.jsdoc new file mode 100644 index 0000000000..3147c2784e --- /dev/null +++ b/src/ol/expr.jsdoc @@ -0,0 +1,3 @@ +/** + * @namespace ol.expr + */ diff --git a/src/ol/expr/expression.exports b/src/ol/expr/expression.exports new file mode 100644 index 0000000000..14b6f57d46 --- /dev/null +++ b/src/ol/expr/expression.exports @@ -0,0 +1,2 @@ +@exportSymbol ol.expr.parse +@exportSymbol ol.expr.register diff --git a/src/ol/expression/expression.js b/src/ol/expr/expression.js similarity index 71% rename from src/ol/expression/expression.js rename to src/ol/expr/expression.js index 388317e9d3..8a60e5b2ad 100644 --- a/src/ol/expression/expression.js +++ b/src/ol/expr/expression.js @@ -1,29 +1,29 @@ -goog.provide('ol.expression'); +goog.provide('ol.expr'); goog.require('ol.Extent'); goog.require('ol.Feature'); -goog.require('ol.expression.Call'); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Identifier'); -goog.require('ol.expression.Parser'); +goog.require('ol.expr.Call'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Identifier'); +goog.require('ol.expr.Parser'); goog.require('ol.extent'); goog.require('ol.geom.GeometryType'); /** * Evaluate an expression with a feature. The feature attributes will be used - * as the evaluation scope. The `ol.expression.lib` functions will be used as + * as the evaluation scope. The `ol.expr.lib` functions will be used as * function scope. The feature itself will be used as the `this` argument. * - * @param {ol.expression.Expression} expr The expression. + * @param {ol.expr.Expression} expr The expression. * @param {ol.Feature=} opt_feature The feature. * @return {*} The result of the expression. */ -ol.expression.evaluateFeature = function(expr, opt_feature) { +ol.expr.evaluateFeature = function(expr, opt_feature) { var result; if (goog.isDef(opt_feature)) { result = expr.evaluate( - opt_feature.getAttributes(), ol.expression.lib, opt_feature); + opt_feature.getAttributes(), ol.expr.lib, opt_feature); } else { result = expr.evaluate(); } @@ -34,11 +34,11 @@ ol.expression.evaluateFeature = function(expr, opt_feature) { /** * Parse an expression. * @param {string} source The expression source (e.g. `'foo + 2'`). - * @return {ol.expression.Expression} An expression instance that can be + * @return {ol.expr.Expression} An expression instance that can be * evaluated within some scope to provide a value. */ -ol.expression.parse = function(source) { - var parser = new ol.expression.Parser(); +ol.expr.parse = function(source) { + var parser = new ol.expr.Parser(); return parser.parse(source); }; @@ -50,27 +50,27 @@ ol.expression.parse = function(source) { * expression. This function will be called with a feature as the `this` * argument when the expression is evaluated in the context of a features. */ -ol.expression.register = function(name, func) { - ol.expression.lib[name] = func; +ol.expr.register = function(name, func) { + ol.expr.lib[name] = func; }; /** * Determines whether an expression is a call expression that calls one of the - * `ol.expression.lib` functions. + * `ol.expr.lib` functions. * - * @param {ol.expression.Expression} expr The candidate expression. + * @param {ol.expr.Expression} expr The candidate expression. * @return {string|undefined} If the candidate expression is a call to a lib * function, the return will be the function name. If not, the return will be * `undefined`. */ -ol.expression.isLibCall = function(expr) { +ol.expr.isLibCall = function(expr) { var name; - if (expr instanceof ol.expression.Call) { + if (expr instanceof ol.expr.Call) { var callee = expr.getCallee(); - if (callee instanceof ol.expression.Identifier) { + if (callee instanceof ol.expr.Identifier) { name = callee.getName(); - if (!ol.expression.lib.hasOwnProperty(name)) { + if (!ol.expr.lib.hasOwnProperty(name)) { name = undefined; } } @@ -81,11 +81,11 @@ ol.expression.isLibCall = function(expr) { /** * Library of well-known functions. These are available to expressions parsed - * with `ol.expression.parse`. + * with `ol.expr.parse`. * * @type {Object} */ -ol.expression.lib = { +ol.expr.lib = { /** * Determine if a feature's extent intersects the provided extent. diff --git a/src/ol/expression/expressions.js b/src/ol/expr/expressions.js similarity index 54% rename from src/ol/expression/expressions.js rename to src/ol/expr/expressions.js index a37ade846d..0b1d716f3c 100644 --- a/src/ol/expression/expressions.js +++ b/src/ol/expr/expressions.js @@ -1,20 +1,20 @@ -goog.provide('ol.expression.Call'); -goog.provide('ol.expression.Comparison'); -goog.provide('ol.expression.ComparisonOp'); -goog.provide('ol.expression.Expression'); -goog.provide('ol.expression.Identifier'); -goog.provide('ol.expression.Literal'); -goog.provide('ol.expression.Logical'); -goog.provide('ol.expression.LogicalOp'); -goog.provide('ol.expression.Math'); -goog.provide('ol.expression.MathOp'); -goog.provide('ol.expression.Member'); -goog.provide('ol.expression.Not'); +goog.provide('ol.expr.Call'); +goog.provide('ol.expr.Comparison'); +goog.provide('ol.expr.ComparisonOp'); +goog.provide('ol.expr.Expression'); +goog.provide('ol.expr.Identifier'); +goog.provide('ol.expr.Literal'); +goog.provide('ol.expr.Logical'); +goog.provide('ol.expr.LogicalOp'); +goog.provide('ol.expr.Math'); +goog.provide('ol.expr.MathOp'); +goog.provide('ol.expr.Member'); +goog.provide('ol.expr.Not'); /** - * Base class for all expressions. Instances of ol.expression.Expression + * Base class for all expressions. Instances of ol.expr.Expression * correspond to a limited set of ECMAScript 5.1 expressions. * http://www.ecma-international.org/ecma-262/5.1/#sec-11 * @@ -23,7 +23,7 @@ goog.provide('ol.expression.Not'); * * @constructor */ -ol.expression.Expression = function() {}; +ol.expr.Expression = function() {}; /** @@ -38,7 +38,7 @@ ol.expression.Expression = function() {}; * expressions. If not provided, `this` will resolve to a new object. * @return {*} Result of the expression. */ -ol.expression.Expression.prototype.evaluate = goog.abstractMethod; +ol.expr.Expression.prototype.evaluate = goog.abstractMethod; @@ -46,33 +46,33 @@ ol.expression.Expression.prototype.evaluate = goog.abstractMethod; * A call expression (e.g. `foo(bar)`). * * @constructor - * @extends {ol.expression.Expression} - * @param {ol.expression.Expression} callee An expression that resolves to a + * @extends {ol.expr.Expression} + * @param {ol.expr.Expression} callee An expression that resolves to a * function. - * @param {Array.} args Arguments. + * @param {Array.} args Arguments. */ -ol.expression.Call = function(callee, args) { +ol.expr.Call = function(callee, args) { /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.callee_ = callee; /** - * @type {Array.} + * @type {Array.} * @private */ this.args_ = args; }; -goog.inherits(ol.expression.Call, ol.expression.Expression); +goog.inherits(ol.expr.Call, ol.expr.Expression); /** * @inheritDoc */ -ol.expression.Call.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { +ol.expr.Call.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { var fnScope = goog.isDefAndNotNull(opt_fns) ? opt_fns : opt_scope; var fn = this.callee_.evaluate(fnScope); if (!fn || !goog.isFunction(fn)) { @@ -91,18 +91,18 @@ ol.expression.Call.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { /** * Get the argument list. - * @return {Array.} The argument. + * @return {Array.} The argument. */ -ol.expression.Call.prototype.getArgs = function() { +ol.expr.Call.prototype.getArgs = function() { return this.args_; }; /** * Get the callee expression. - * @return {ol.expression.Expression} The callee expression. + * @return {ol.expr.Expression} The callee expression. */ -ol.expression.Call.prototype.getCallee = function() { +ol.expr.Call.prototype.getCallee = function() { return this.callee_; }; @@ -110,7 +110,7 @@ ol.expression.Call.prototype.getCallee = function() { /** * @enum {string} */ -ol.expression.ComparisonOp = { +ol.expr.ComparisonOp = { EQ: '==', NEQ: '!=', STRICT_EQ: '===', @@ -127,33 +127,33 @@ ol.expression.ComparisonOp = { * A comparison expression (e.g. `foo >= 42`, `bar != "chicken"`). * * @constructor - * @extends {ol.expression.Expression} - * @param {ol.expression.ComparisonOp} operator Comparison operator. - * @param {ol.expression.Expression} left Left expression. - * @param {ol.expression.Expression} right Right expression. + * @extends {ol.expr.Expression} + * @param {ol.expr.ComparisonOp} operator Comparison operator. + * @param {ol.expr.Expression} left Left expression. + * @param {ol.expr.Expression} right Right expression. */ -ol.expression.Comparison = function(operator, left, right) { +ol.expr.Comparison = function(operator, left, right) { /** - * @type {ol.expression.ComparisonOp} + * @type {ol.expr.ComparisonOp} * @private */ this.operator_ = operator; /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.left_ = left; /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.right_ = right; }; -goog.inherits(ol.expression.Comparison, ol.expression.Expression); +goog.inherits(ol.expr.Comparison, ol.expr.Expression); /** @@ -161,10 +161,10 @@ goog.inherits(ol.expression.Comparison, ol.expression.Expression); * @param {string} candidate Operator to test. * @return {boolean} The operator is valid. */ -ol.expression.Comparison.isValidOp = (function() { +ol.expr.Comparison.isValidOp = (function() { var valid = {}; - for (var key in ol.expression.ComparisonOp) { - valid[ol.expression.ComparisonOp[key]] = true; + for (var key in ol.expr.ComparisonOp) { + valid[ol.expr.ComparisonOp[key]] = true; } return function isValidOp(candidate) { return !!valid[candidate]; @@ -175,35 +175,35 @@ ol.expression.Comparison.isValidOp = (function() { /** * @inheritDoc */ -ol.expression.Comparison.prototype.evaluate = function(opt_scope, opt_fns, +ol.expr.Comparison.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { var result; var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this); var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this); switch (this.operator_) { - case ol.expression.ComparisonOp.EQ: + case ol.expr.ComparisonOp.EQ: result = leftVal == rightVal; break; - case ol.expression.ComparisonOp.NEQ: + case ol.expr.ComparisonOp.NEQ: result = leftVal != rightVal; break; - case ol.expression.ComparisonOp.STRICT_EQ: + case ol.expr.ComparisonOp.STRICT_EQ: result = leftVal === rightVal; break; - case ol.expression.ComparisonOp.STRICT_NEQ: + case ol.expr.ComparisonOp.STRICT_NEQ: result = leftVal !== rightVal; break; - case ol.expression.ComparisonOp.GT: + case ol.expr.ComparisonOp.GT: result = leftVal > rightVal; break; - case ol.expression.ComparisonOp.LT: + case ol.expr.ComparisonOp.LT: result = leftVal < rightVal; break; - case ol.expression.ComparisonOp.GTE: + case ol.expr.ComparisonOp.GTE: result = leftVal >= rightVal; break; - case ol.expression.ComparisonOp.LTE: + case ol.expr.ComparisonOp.LTE: result = leftVal <= rightVal; break; default: @@ -217,25 +217,25 @@ ol.expression.Comparison.prototype.evaluate = function(opt_scope, opt_fns, * Get the comparison operator. * @return {string} The comparison operator. */ -ol.expression.Comparison.prototype.getOperator = function() { +ol.expr.Comparison.prototype.getOperator = function() { return this.operator_; }; /** * Get the left expression. - * @return {ol.expression.Expression} The left expression. + * @return {ol.expr.Expression} The left expression. */ -ol.expression.Comparison.prototype.getLeft = function() { +ol.expr.Comparison.prototype.getLeft = function() { return this.left_; }; /** * Get the right expression. - * @return {ol.expression.Expression} The right expression. + * @return {ol.expr.Expression} The right expression. */ -ol.expression.Comparison.prototype.getRight = function() { +ol.expr.Comparison.prototype.getRight = function() { return this.right_; }; @@ -245,10 +245,10 @@ ol.expression.Comparison.prototype.getRight = function() { * An identifier expression (e.g. `foo`). * * @constructor - * @extends {ol.expression.Expression} + * @extends {ol.expr.Expression} * @param {string} name An identifier name. */ -ol.expression.Identifier = function(name) { +ol.expr.Identifier = function(name) { /** * @type {string} @@ -257,13 +257,13 @@ ol.expression.Identifier = function(name) { this.name_ = name; }; -goog.inherits(ol.expression.Identifier, ol.expression.Expression); +goog.inherits(ol.expr.Identifier, ol.expr.Expression); /** * @inheritDoc */ -ol.expression.Identifier.prototype.evaluate = function(opt_scope) { +ol.expr.Identifier.prototype.evaluate = function(opt_scope) { if (!goog.isDefAndNotNull(opt_scope)) { throw new Error('Attempt to evaluate identifier with no scope'); } @@ -275,7 +275,7 @@ ol.expression.Identifier.prototype.evaluate = function(opt_scope) { * Get the identifier name. * @return {string} The identifier name. */ -ol.expression.Identifier.prototype.getName = function() { +ol.expr.Identifier.prototype.getName = function() { return this.name_; }; @@ -285,10 +285,10 @@ ol.expression.Identifier.prototype.getName = function() { * A literal expression (e.g. `"chicken"`, `42`, `true`, `null`). * * @constructor - * @extends {ol.expression.Expression} + * @extends {ol.expr.Expression} * @param {string|number|boolean|null} value A literal value. */ -ol.expression.Literal = function(value) { +ol.expr.Literal = function(value) { /** * @type {string|number|boolean|null} @@ -297,13 +297,13 @@ ol.expression.Literal = function(value) { this.value_ = value; }; -goog.inherits(ol.expression.Literal, ol.expression.Expression); +goog.inherits(ol.expr.Literal, ol.expr.Expression); /** * @inheritDoc */ -ol.expression.Literal.prototype.evaluate = function() { +ol.expr.Literal.prototype.evaluate = function() { return this.value_; }; @@ -312,7 +312,7 @@ ol.expression.Literal.prototype.evaluate = function() { * Get the literal value. * @return {string|number|boolean|null} The literal value. */ -ol.expression.Literal.prototype.getValue = function() { +ol.expr.Literal.prototype.getValue = function() { return this.value_; }; @@ -320,7 +320,7 @@ ol.expression.Literal.prototype.getValue = function() { /** * @enum {string} */ -ol.expression.LogicalOp = { +ol.expr.LogicalOp = { AND: '&&', OR: '||' }; @@ -331,33 +331,33 @@ ol.expression.LogicalOp = { * A binary logical expression (e.g. `foo && bar`, `bar || "chicken"`). * * @constructor - * @extends {ol.expression.Expression} - * @param {ol.expression.LogicalOp} operator Logical operator. - * @param {ol.expression.Expression} left Left expression. - * @param {ol.expression.Expression} right Right expression. + * @extends {ol.expr.Expression} + * @param {ol.expr.LogicalOp} operator Logical operator. + * @param {ol.expr.Expression} left Left expression. + * @param {ol.expr.Expression} right Right expression. */ -ol.expression.Logical = function(operator, left, right) { +ol.expr.Logical = function(operator, left, right) { /** - * @type {ol.expression.LogicalOp} + * @type {ol.expr.LogicalOp} * @private */ this.operator_ = operator; /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.left_ = left; /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.right_ = right; }; -goog.inherits(ol.expression.Logical, ol.expression.Expression); +goog.inherits(ol.expr.Logical, ol.expr.Expression); /** @@ -365,10 +365,10 @@ goog.inherits(ol.expression.Logical, ol.expression.Expression); * @param {string} candidate Operator to test. * @return {boolean} The operator is valid. */ -ol.expression.Logical.isValidOp = (function() { +ol.expr.Logical.isValidOp = (function() { var valid = {}; - for (var key in ol.expression.LogicalOp) { - valid[ol.expression.LogicalOp[key]] = true; + for (var key in ol.expr.LogicalOp) { + valid[ol.expr.LogicalOp[key]] = true; } return function isValidOp(candidate) { return !!valid[candidate]; @@ -379,15 +379,15 @@ ol.expression.Logical.isValidOp = (function() { /** * @inheritDoc */ -ol.expression.Logical.prototype.evaluate = function(opt_scope, opt_fns, +ol.expr.Logical.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { var result; var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this); var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this); - if (this.operator_ === ol.expression.LogicalOp.AND) { + if (this.operator_ === ol.expr.LogicalOp.AND) { result = leftVal && rightVal; - } else if (this.operator_ === ol.expression.LogicalOp.OR) { + } else if (this.operator_ === ol.expr.LogicalOp.OR) { result = leftVal || rightVal; } else { throw new Error('Unsupported logical operator: ' + this.operator_); @@ -400,25 +400,25 @@ ol.expression.Logical.prototype.evaluate = function(opt_scope, opt_fns, * Get the logical operator. * @return {string} The logical operator. */ -ol.expression.Logical.prototype.getOperator = function() { +ol.expr.Logical.prototype.getOperator = function() { return this.operator_; }; /** * Get the left expression. - * @return {ol.expression.Expression} The left expression. + * @return {ol.expr.Expression} The left expression. */ -ol.expression.Logical.prototype.getLeft = function() { +ol.expr.Logical.prototype.getLeft = function() { return this.left_; }; /** * Get the right expression. - * @return {ol.expression.Expression} The right expression. + * @return {ol.expr.Expression} The right expression. */ -ol.expression.Logical.prototype.getRight = function() { +ol.expr.Logical.prototype.getRight = function() { return this.right_; }; @@ -426,7 +426,7 @@ ol.expression.Logical.prototype.getRight = function() { /** * @enum {string} */ -ol.expression.MathOp = { +ol.expr.MathOp = { ADD: '+', SUBTRACT: '-', MULTIPLY: '*', @@ -440,33 +440,33 @@ ol.expression.MathOp = { * 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. + * @extends {ol.expr.Expression} + * @param {ol.expr.MathOp} operator Math operator. + * @param {ol.expr.Expression} left Left expression. + * @param {ol.expr.Expression} right Right expression. */ -ol.expression.Math = function(operator, left, right) { +ol.expr.Math = function(operator, left, right) { /** - * @type {ol.expression.MathOp} + * @type {ol.expr.MathOp} * @private */ this.operator_ = operator; /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.left_ = left; /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.right_ = right; }; -goog.inherits(ol.expression.Math, ol.expression.Expression); +goog.inherits(ol.expr.Math, ol.expr.Expression); /** @@ -474,10 +474,10 @@ goog.inherits(ol.expression.Math, ol.expression.Expression); * @param {string} candidate Operator to test. * @return {boolean} The operator is valid. */ -ol.expression.Math.isValidOp = (function() { +ol.expr.Math.isValidOp = (function() { var valid = {}; - for (var key in ol.expression.MathOp) { - valid[ol.expression.MathOp[key]] = true; + for (var key in ol.expr.MathOp) { + valid[ol.expr.MathOp[key]] = true; } return function isValidOp(candidate) { return !!valid[candidate]; @@ -488,7 +488,7 @@ ol.expression.Math.isValidOp = (function() { /** * @inheritDoc */ -ol.expression.Math.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { +ol.expr.Math.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { var result; var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this); var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this); @@ -499,19 +499,19 @@ ol.expression.Math.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { */ switch (this.operator_) { - case ol.expression.MathOp.ADD: + case ol.expr.MathOp.ADD: result = leftVal + rightVal; break; - case ol.expression.MathOp.SUBTRACT: + case ol.expr.MathOp.SUBTRACT: result = Number(leftVal) - Number(rightVal); break; - case ol.expression.MathOp.MULTIPLY: + case ol.expr.MathOp.MULTIPLY: result = Number(leftVal) * Number(rightVal); break; - case ol.expression.MathOp.DIVIDE: + case ol.expr.MathOp.DIVIDE: result = Number(leftVal) / Number(rightVal); break; - case ol.expression.MathOp.MOD: + case ol.expr.MathOp.MOD: result = Number(leftVal) % Number(rightVal); break; default: @@ -525,25 +525,25 @@ ol.expression.Math.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { * Get the math operator. * @return {string} The math operator. */ -ol.expression.Math.prototype.getOperator = function() { +ol.expr.Math.prototype.getOperator = function() { return this.operator_; }; /** * Get the left expression. - * @return {ol.expression.Expression} The left expression. + * @return {ol.expr.Expression} The left expression. */ -ol.expression.Math.prototype.getLeft = function() { +ol.expr.Math.prototype.getLeft = function() { return this.left_; }; /** * Get the right expression. - * @return {ol.expression.Expression} The right expression. + * @return {ol.expr.Expression} The right expression. */ -ol.expression.Math.prototype.getRight = function() { +ol.expr.Math.prototype.getRight = function() { return this.right_; }; @@ -553,33 +553,33 @@ ol.expression.Math.prototype.getRight = function() { * A member expression (e.g. `foo.bar`). * * @constructor - * @extends {ol.expression.Expression} - * @param {ol.expression.Expression} object An expression that resolves to an + * @extends {ol.expr.Expression} + * @param {ol.expr.Expression} object An expression that resolves to an * object. - * @param {ol.expression.Identifier} property Identifier with name of property. + * @param {ol.expr.Identifier} property Identifier with name of property. */ -ol.expression.Member = function(object, property) { +ol.expr.Member = function(object, property) { /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.object_ = object; /** - * @type {ol.expression.Identifier} + * @type {ol.expr.Identifier} * @private */ this.property_ = property; }; -goog.inherits(ol.expression.Member, ol.expression.Expression); +goog.inherits(ol.expr.Member, ol.expr.Expression); /** * @inheritDoc */ -ol.expression.Member.prototype.evaluate = function(opt_scope, opt_fns, +ol.expr.Member.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { var obj = this.object_.evaluate(opt_scope, opt_fns, opt_this); if (!goog.isObject(obj)) { @@ -592,18 +592,18 @@ ol.expression.Member.prototype.evaluate = function(opt_scope, opt_fns, /** * Get the object expression. - * @return {ol.expression.Expression} The object. + * @return {ol.expr.Expression} The object. */ -ol.expression.Member.prototype.getObject = function() { +ol.expr.Member.prototype.getObject = function() { return this.object_; }; /** * Get the property expression. - * @return {ol.expression.Identifier} The property. + * @return {ol.expr.Identifier} The property. */ -ol.expression.Member.prototype.getProperty = function() { +ol.expr.Member.prototype.getProperty = function() { return this.property_; }; @@ -613,33 +613,33 @@ ol.expression.Member.prototype.getProperty = function() { * A logical not expression (e.g. `!foo`). * * @constructor - * @extends {ol.expression.Expression} - * @param {ol.expression.Expression} argument Expression to negate. + * @extends {ol.expr.Expression} + * @param {ol.expr.Expression} argument Expression to negate. */ -ol.expression.Not = function(argument) { +ol.expr.Not = function(argument) { /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.argument_ = argument; }; -goog.inherits(ol.expression.Not, ol.expression.Expression); +goog.inherits(ol.expr.Not, ol.expr.Expression); /** * @inheritDoc */ -ol.expression.Not.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { +ol.expr.Not.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { return !this.argument_.evaluate(opt_scope, opt_fns, opt_this); }; /** * Get the argument (the negated expression). - * @return {ol.expression.Expression} The argument. + * @return {ol.expr.Expression} The argument. */ -ol.expression.Not.prototype.getArgument = function() { +ol.expr.Not.prototype.getArgument = function() { return this.argument_; }; diff --git a/src/ol/expression/lexer.js b/src/ol/expr/lexer.js similarity index 67% rename from src/ol/expression/lexer.js rename to src/ol/expr/lexer.js index 4389997290..c1af520c34 100644 --- a/src/ol/expression/lexer.js +++ b/src/ol/expr/lexer.js @@ -13,11 +13,11 @@ * Copyright (C) 2011 Ariya Hidayat */ -goog.provide('ol.expression.Char'); // TODO: remove this - see #785 -goog.provide('ol.expression.Lexer'); -goog.provide('ol.expression.Token'); -goog.provide('ol.expression.TokenType'); -goog.provide('ol.expression.UnexpectedToken'); +goog.provide('ol.expr.Char'); // TODO: remove this - see #785 +goog.provide('ol.expr.Lexer'); +goog.provide('ol.expr.Token'); +goog.provide('ol.expr.TokenType'); +goog.provide('ol.expr.UnexpectedToken'); goog.require('goog.asserts'); goog.require('goog.debug.Error'); @@ -26,7 +26,7 @@ goog.require('goog.debug.Error'); /** * @enum {number} */ -ol.expression.Char = { +ol.expr.Char = { AMPERSAND: 38, BACKSLASH: 92, BANG: 33, // ! @@ -76,7 +76,7 @@ ol.expression.Char = { /** * @enum {string} */ -ol.expression.TokenType = { +ol.expr.TokenType = { BOOLEAN_LITERAL: 'Boolean', EOF: '', IDENTIFIER: 'Identifier', @@ -90,11 +90,11 @@ ol.expression.TokenType = { /** - * @typedef {{type: (ol.expression.TokenType), + * @typedef {{type: (ol.expr.TokenType), * value: (string|number|boolean|null), * index: (number)}} */ -ol.expression.Token; +ol.expr.Token; @@ -105,7 +105,7 @@ ol.expression.Token; * @constructor * @param {string} source Source code. */ -ol.expression.Lexer = function(source) { +ol.expr.Lexer = function(source) { /** * Source code. @@ -142,11 +142,11 @@ ol.expression.Lexer = function(source) { * Scan the next token and throw if it isn't a punctuator that matches input. * @param {string} value Token value. */ -ol.expression.Lexer.prototype.expect = function(value) { +ol.expr.Lexer.prototype.expect = function(value) { var match = this.match(value); if (!match) { - throw new ol.expression.UnexpectedToken({ - type: ol.expression.TokenType.UNKNOWN, + throw new ol.expr.UnexpectedToken({ + type: ol.expr.TokenType.UNKNOWN, value: this.getCurrentChar_(), index: this.index_ }); @@ -161,7 +161,7 @@ ol.expression.Lexer.prototype.expect = function(value) { * @param {number} delta Delta by which the index is advanced. * @private */ -ol.expression.Lexer.prototype.increment_ = function(delta) { +ol.expr.Lexer.prototype.increment_ = function(delta) { this.index_ += delta; }; @@ -173,10 +173,10 @@ ol.expression.Lexer.prototype.increment_ = function(delta) { * @return {boolean} The character is a decimal digit. * @private */ -ol.expression.Lexer.prototype.isDecimalDigit_ = function(code) { +ol.expr.Lexer.prototype.isDecimalDigit_ = function(code) { return ( - code >= ol.expression.Char.DIGIT_0 && - code <= ol.expression.Char.DIGIT_9); + code >= ol.expr.Char.DIGIT_0 && + code <= ol.expr.Char.DIGIT_9); }; @@ -187,7 +187,7 @@ ol.expression.Lexer.prototype.isDecimalDigit_ = function(code) { * @return {boolean} The identifier is a future reserved word. * @private */ -ol.expression.Lexer.prototype.isFutureReservedWord_ = function(id) { +ol.expr.Lexer.prototype.isFutureReservedWord_ = function(id) { return ( id === 'class' || id === 'enum' || @@ -205,12 +205,12 @@ ol.expression.Lexer.prototype.isFutureReservedWord_ = function(id) { * @return {boolean} The character is a hex digit. * @private */ -ol.expression.Lexer.prototype.isHexDigit_ = function(code) { +ol.expr.Lexer.prototype.isHexDigit_ = function(code) { return this.isDecimalDigit_(code) || - (code >= ol.expression.Char.LOWER_A && - code <= ol.expression.Char.LOWER_F) || - (code >= ol.expression.Char.UPPER_A && - code <= ol.expression.Char.UPPER_F); + (code >= ol.expr.Char.LOWER_A && + code <= ol.expr.Char.LOWER_F) || + (code >= ol.expr.Char.UPPER_A && + code <= ol.expr.Char.UPPER_F); }; @@ -222,10 +222,10 @@ ol.expression.Lexer.prototype.isHexDigit_ = function(code) { * @return {boolean} The character is a valid identifier part. * @private */ -ol.expression.Lexer.prototype.isIdentifierPart_ = function(code) { +ol.expr.Lexer.prototype.isIdentifierPart_ = function(code) { return this.isIdentifierStart_(code) || - (code >= ol.expression.Char.DIGIT_0 && - code <= ol.expression.Char.DIGIT_9); + (code >= ol.expr.Char.DIGIT_0 && + code <= ol.expr.Char.DIGIT_9); }; @@ -237,20 +237,20 @@ ol.expression.Lexer.prototype.isIdentifierPart_ = function(code) { * @return {boolean} The character is a valid identifier start. * @private */ -ol.expression.Lexer.prototype.isIdentifierStart_ = function(code) { - return (code === ol.expression.Char.DOLLAR) || - (code === ol.expression.Char.UNDERSCORE) || - (code >= ol.expression.Char.UPPER_A && - code <= ol.expression.Char.UPPER_Z) || - (code >= ol.expression.Char.LOWER_A && - code <= ol.expression.Char.LOWER_Z); +ol.expr.Lexer.prototype.isIdentifierStart_ = function(code) { + return (code === ol.expr.Char.DOLLAR) || + (code === ol.expr.Char.UNDERSCORE) || + (code >= ol.expr.Char.UPPER_A && + code <= ol.expr.Char.UPPER_Z) || + (code >= ol.expr.Char.LOWER_A && + code <= ol.expr.Char.LOWER_Z); }; /** * Determine if the given identifier is an ECMAScript keyword. These cannot * be used as identifiers in programs. There is no real reason these could not - * be used in ol expressions - but they are reserved for future use. + * be used in ol.exprs - but they are reserved for future use. * * http://www.ecma-international.org/ecma-262/5.1/#sec-7.6.1.1 * @@ -258,7 +258,7 @@ ol.expression.Lexer.prototype.isIdentifierStart_ = function(code) { * @return {boolean} The identifier is a keyword. * @private */ -ol.expression.Lexer.prototype.isKeyword_ = function(id) { +ol.expr.Lexer.prototype.isKeyword_ = function(id) { return ( id === 'break' || id === 'case' || @@ -296,11 +296,11 @@ ol.expression.Lexer.prototype.isKeyword_ = function(id) { * @return {boolean} The character is a line terminator. * @private */ -ol.expression.Lexer.prototype.isLineTerminator_ = function(code) { - return (code === ol.expression.Char.LINE_FEED) || - (code === ol.expression.Char.CARRIAGE_RETURN) || - (code === ol.expression.Char.LINE_SEPARATOR) || - (code === ol.expression.Char.PARAGRAPH_SEPARATOR); +ol.expr.Lexer.prototype.isLineTerminator_ = function(code) { + return (code === ol.expr.Char.LINE_FEED) || + (code === ol.expr.Char.CARRIAGE_RETURN) || + (code === ol.expr.Char.LINE_SEPARATOR) || + (code === ol.expr.Char.PARAGRAPH_SEPARATOR); }; @@ -311,10 +311,10 @@ ol.expression.Lexer.prototype.isLineTerminator_ = function(code) { * @return {boolean} The character is an octal digit. * @private */ -ol.expression.Lexer.prototype.isOctalDigit_ = function(code) { +ol.expr.Lexer.prototype.isOctalDigit_ = function(code) { return ( - code >= ol.expression.Char.DIGIT_0 && - code <= ol.expression.Char.DIGIT_7); + code >= ol.expr.Char.DIGIT_0 && + code <= ol.expr.Char.DIGIT_7); }; @@ -325,12 +325,12 @@ ol.expression.Lexer.prototype.isOctalDigit_ = function(code) { * @return {boolean} The character is whitespace. * @private */ -ol.expression.Lexer.prototype.isWhitespace_ = function(code) { - return (code === ol.expression.Char.SPACE) || - (code === ol.expression.Char.TAB) || - (code === ol.expression.Char.VERTICAL_TAB) || - (code === ol.expression.Char.FORM_FEED) || - (code === ol.expression.Char.NONBREAKING_SPACE) || +ol.expr.Lexer.prototype.isWhitespace_ = function(code) { + return (code === ol.expr.Char.SPACE) || + (code === ol.expr.Char.TAB) || + (code === ol.expr.Char.VERTICAL_TAB) || + (code === ol.expr.Char.FORM_FEED) || + (code === ol.expr.Char.NONBREAKING_SPACE) || (code >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005' + '\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF' .indexOf(String.fromCharCode(code)) > 0); @@ -344,7 +344,7 @@ ol.expression.Lexer.prototype.isWhitespace_ = function(code) { * @return {number} The character code. * @private */ -ol.expression.Lexer.prototype.getCharCode_ = function(delta) { +ol.expr.Lexer.prototype.getCharCode_ = function(delta) { return this.source_.charCodeAt(this.index_ + delta); }; @@ -355,7 +355,7 @@ ol.expression.Lexer.prototype.getCharCode_ = function(delta) { * @return {string} The current character. * @private */ -ol.expression.Lexer.prototype.getCurrentChar_ = function() { +ol.expr.Lexer.prototype.getCurrentChar_ = function() { return this.source_[this.index_]; }; @@ -366,7 +366,7 @@ ol.expression.Lexer.prototype.getCurrentChar_ = function() { * @return {number} The current character code. * @private */ -ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { +ol.expr.Lexer.prototype.getCurrentCharCode_ = function() { return this.getCharCode_(0); }; @@ -376,10 +376,10 @@ ol.expression.Lexer.prototype.getCurrentCharCode_ = function() { * @param {string} value Punctuator value. * @return {boolean} The token matches. */ -ol.expression.Lexer.prototype.match = function(value) { +ol.expr.Lexer.prototype.match = function(value) { var token = this.peek(); return ( - token.type === ol.expression.TokenType.PUNCTUATOR && + token.type === ol.expr.TokenType.PUNCTUATOR && token.value === value); }; @@ -387,28 +387,28 @@ ol.expression.Lexer.prototype.match = function(value) { /** * Scan the next token. * - * @return {ol.expression.Token} Next token. + * @return {ol.expr.Token} Next token. */ -ol.expression.Lexer.prototype.next = function() { +ol.expr.Lexer.prototype.next = function() { var code = this.skipWhitespace_(); if (this.index_ >= this.length_) { return { - type: ol.expression.TokenType.EOF, + type: ol.expr.TokenType.EOF, value: null, index: this.index_ }; } // check for common punctuation - if (code === ol.expression.Char.LEFT_PAREN || - code === ol.expression.Char.RIGHT_PAREN) { + if (code === ol.expr.Char.LEFT_PAREN || + code === ol.expr.Char.RIGHT_PAREN) { return this.scanPunctuator_(code); } // check for string literal - if (code === ol.expression.Char.SINGLE_QUOTE || - code === ol.expression.Char.DOUBLE_QUOTE) { + if (code === ol.expr.Char.SINGLE_QUOTE || + code === ol.expr.Char.DOUBLE_QUOTE) { return this.scanStringLiteral_(code); } @@ -418,7 +418,7 @@ ol.expression.Lexer.prototype.next = function() { } // check dot punctuation or decimal - if (code === ol.expression.Char.DOT) { + if (code === ol.expr.Char.DOT) { if (this.isDecimalDigit_(this.getCharCode_(1))) { return this.scanNumericLiteral_(code); } @@ -438,9 +438,9 @@ ol.expression.Lexer.prototype.next = function() { /** * Peek at the next token, but don't advance the index. * - * @return {ol.expression.Token} The upcoming token. + * @return {ol.expr.Token} The upcoming token. */ -ol.expression.Lexer.prototype.peek = function() { +ol.expr.Lexer.prototype.peek = function() { var currentIndex = this.index_; var token = this.next(); this.nextIndex_ = this.index_; @@ -453,10 +453,10 @@ ol.expression.Lexer.prototype.peek = function() { * Scan hex literal as numeric token. * * @param {number} code The current character code. - * @return {ol.expression.Token} Numeric literal token. + * @return {ol.expr.Token} Numeric literal token. * @private */ -ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { +ol.expr.Lexer.prototype.scanHexLiteral_ = function(code) { var str = ''; var start = this.index_ - 2; @@ -470,8 +470,8 @@ ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { } if (str.length === 0 || this.isIdentifierStart_(code)) { - throw new ol.expression.UnexpectedToken({ - type: ol.expression.TokenType.UNKNOWN, + throw new ol.expr.UnexpectedToken({ + type: ol.expr.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ }); @@ -480,7 +480,7 @@ ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { goog.asserts.assert(!isNaN(parseInt('0x' + str, 16)), 'Valid hex: ' + str); return { - type: ol.expression.TokenType.NUMERIC_LITERAL, + type: ol.expr.TokenType.NUMERIC_LITERAL, value: parseInt('0x' + str, 16), index: start }; @@ -491,10 +491,10 @@ ol.expression.Lexer.prototype.scanHexLiteral_ = function(code) { * Scan identifier token. * * @param {number} code The current character code. - * @return {ol.expression.Token} Identifier token. + * @return {ol.expr.Token} Identifier token. * @private */ -ol.expression.Lexer.prototype.scanIdentifier_ = function(code) { +ol.expr.Lexer.prototype.scanIdentifier_ = function(code) { goog.asserts.assert(this.isIdentifierStart_(code), 'Must be called with a valid identifier'); @@ -513,15 +513,15 @@ ol.expression.Lexer.prototype.scanIdentifier_ = function(code) { var type; if (id.length === 1) { - type = ol.expression.TokenType.IDENTIFIER; + type = ol.expr.TokenType.IDENTIFIER; } else if (this.isKeyword_(id)) { - type = ol.expression.TokenType.KEYWORD; + type = ol.expr.TokenType.KEYWORD; } else if (id === 'null') { - type = ol.expression.TokenType.NULL_LITERAL; + type = ol.expr.TokenType.NULL_LITERAL; } else if (id === 'true' || id === 'false') { - type = ol.expression.TokenType.BOOLEAN_LITERAL; + type = ol.expr.TokenType.BOOLEAN_LITERAL; } else { - type = ol.expression.TokenType.IDENTIFIER; + type = ol.expr.TokenType.IDENTIFIER; } return { @@ -537,26 +537,26 @@ ol.expression.Lexer.prototype.scanIdentifier_ = function(code) { * http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.3 * * @param {number} code The current character code. - * @return {ol.expression.Token} Numeric literal token. + * @return {ol.expr.Token} Numeric literal token. * @private */ -ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { +ol.expr.Lexer.prototype.scanNumericLiteral_ = function(code) { goog.asserts.assert( - code === ol.expression.Char.DOT || this.isDecimalDigit_(code), + code === ol.expr.Char.DOT || this.isDecimalDigit_(code), 'Valid start for numeric literal: ' + String.fromCharCode(code)); // start assembling numeric string var str = ''; var start = this.index_; - if (code !== ol.expression.Char.DOT) { + if (code !== ol.expr.Char.DOT) { - if (code === ol.expression.Char.DIGIT_0) { + if (code === ol.expr.Char.DIGIT_0) { var nextCode = this.getCharCode_(1); // hex literals start with 0X or 0x - if (nextCode === ol.expression.Char.UPPER_X || - nextCode === ol.expression.Char.LOWER_X) { + if (nextCode === ol.expr.Char.UPPER_X || + nextCode === ol.expr.Char.LOWER_X) { this.increment_(2); return this.scanHexLiteral_(this.getCurrentCharCode_()); } @@ -569,8 +569,8 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { // numbers like 09 not allowed if (this.isDecimalDigit_(nextCode)) { - throw new ol.expression.UnexpectedToken({ - type: ol.expression.TokenType.UNKNOWN, + throw new ol.expr.UnexpectedToken({ + type: ol.expr.TokenType.UNKNOWN, value: String.fromCharCode(nextCode), index: this.index_ }); @@ -586,7 +586,7 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { } // scan fractional part - if (code === ol.expression.Char.DOT) { + if (code === ol.expr.Char.DOT) { str += String.fromCharCode(code); this.increment_(1); code = this.getCurrentCharCode_(); @@ -600,22 +600,22 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { } // scan exponent - if (code === ol.expression.Char.UPPER_E || - code === ol.expression.Char.LOWER_E) { + if (code === ol.expr.Char.UPPER_E || + code === ol.expr.Char.LOWER_E) { str += 'E'; this.increment_(1); code = this.getCurrentCharCode_(); - if (code === ol.expression.Char.PLUS || - code === ol.expression.Char.MINUS) { + if (code === ol.expr.Char.PLUS || + code === ol.expr.Char.MINUS) { str += String.fromCharCode(code); this.increment_(1); code = this.getCurrentCharCode_(); } if (!this.isDecimalDigit_(code)) { - throw new ol.expression.UnexpectedToken({ - type: ol.expression.TokenType.UNKNOWN, + throw new ol.expr.UnexpectedToken({ + type: ol.expr.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ }); @@ -630,8 +630,8 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { } if (this.isIdentifierStart_(code)) { - throw new ol.expression.UnexpectedToken({ - type: ol.expression.TokenType.UNKNOWN, + throw new ol.expr.UnexpectedToken({ + type: ol.expr.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ }); @@ -640,7 +640,7 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { goog.asserts.assert(!isNaN(parseFloat(str)), 'Valid number: ' + str); return { - type: ol.expression.TokenType.NUMERIC_LITERAL, + type: ol.expr.TokenType.NUMERIC_LITERAL, value: parseFloat(str), index: start }; @@ -652,10 +652,10 @@ ol.expression.Lexer.prototype.scanNumericLiteral_ = function(code) { * Scan octal literal as numeric token. * * @param {number} code The current character code. - * @return {ol.expression.Token} Numeric literal token. + * @return {ol.expr.Token} Numeric literal token. * @private */ -ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { +ol.expr.Lexer.prototype.scanOctalLiteral_ = function(code) { goog.asserts.assert(this.isOctalDigit_(code)); var str = '0' + String.fromCharCode(code); @@ -674,8 +674,8 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { code = this.getCurrentCharCode_(); if (this.isIdentifierStart_(code) || this.isDecimalDigit_(code)) { - throw new ol.expression.UnexpectedToken({ - type: ol.expression.TokenType.UNKNOWN, + throw new ol.expr.UnexpectedToken({ + type: ol.expr.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ }); @@ -684,7 +684,7 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { goog.asserts.assert(!isNaN(parseInt(str, 8)), 'Valid octal: ' + str); return { - type: ol.expression.TokenType.NUMERIC_LITERAL, + type: ol.expr.TokenType.NUMERIC_LITERAL, value: parseInt(str, 8), index: start }; @@ -695,28 +695,28 @@ ol.expression.Lexer.prototype.scanOctalLiteral_ = function(code) { * Scan punctuator token (a subset of allowed tokens in 7.7). * * @param {number} code The current character code. - * @return {ol.expression.Token} Punctuator token. + * @return {ol.expr.Token} Punctuator token. * @private */ -ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { +ol.expr.Lexer.prototype.scanPunctuator_ = function(code) { var start = this.index_; // single char punctuation that also doesn't start longer punctuation // (we disallow assignment, so no += etc.) - if (code === ol.expression.Char.DOT || - code === ol.expression.Char.LEFT_PAREN || - code === ol.expression.Char.RIGHT_PAREN || - code === ol.expression.Char.COMMA || - code === ol.expression.Char.PLUS || - code === ol.expression.Char.MINUS || - code === ol.expression.Char.STAR || - code === ol.expression.Char.SLASH || - code === ol.expression.Char.PERCENT || - code === ol.expression.Char.TILDE) { + if (code === ol.expr.Char.DOT || + code === ol.expr.Char.LEFT_PAREN || + code === ol.expr.Char.RIGHT_PAREN || + code === ol.expr.Char.COMMA || + code === ol.expr.Char.PLUS || + code === ol.expr.Char.MINUS || + code === ol.expr.Char.STAR || + code === ol.expr.Char.SLASH || + code === ol.expr.Char.PERCENT || + code === ol.expr.Char.TILDE) { this.increment_(1); return { - type: ol.expression.TokenType.PUNCTUATOR, + type: ol.expr.TokenType.PUNCTUATOR, value: String.fromCharCode(code), index: start }; @@ -726,34 +726,34 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { var nextCode = this.getCharCode_(1); // assignment or comparison (and we don't allow assignment) - if (nextCode === ol.expression.Char.EQUAL) { - if (code === ol.expression.Char.BANG || code === ol.expression.Char.EQUAL) { + if (nextCode === ol.expr.Char.EQUAL) { + if (code === ol.expr.Char.BANG || code === ol.expr.Char.EQUAL) { // we're looking at !=, ==, !==, or === this.increment_(2); // check for triple - if (this.getCurrentCharCode_() === ol.expression.Char.EQUAL) { + if (this.getCurrentCharCode_() === ol.expr.Char.EQUAL) { this.increment_(1); return { - type: ol.expression.TokenType.PUNCTUATOR, + type: ol.expr.TokenType.PUNCTUATOR, value: String.fromCharCode(code) + '==', index: start }; } else { // != or == return { - type: ol.expression.TokenType.PUNCTUATOR, + type: ol.expr.TokenType.PUNCTUATOR, value: String.fromCharCode(code) + '=', index: start }; } } - if (code === ol.expression.Char.GREATER || - code === ol.expression.Char.LESS) { + if (code === ol.expr.Char.GREATER || + code === ol.expr.Char.LESS) { this.increment_(2); return { - type: ol.expression.TokenType.PUNCTUATOR, + type: ol.expr.TokenType.PUNCTUATOR, value: String.fromCharCode(code) + '=', index: start }; @@ -762,13 +762,13 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { // remaining 2-charcter punctuators are || and && if (code === nextCode && - (code === ol.expression.Char.PIPE || - code === ol.expression.Char.AMPERSAND)) { + (code === ol.expr.Char.PIPE || + code === ol.expr.Char.AMPERSAND)) { this.increment_(2); var str = String.fromCharCode(code); return { - type: ol.expression.TokenType.PUNCTUATOR, + type: ol.expr.TokenType.PUNCTUATOR, value: str + str, index: start }; @@ -778,22 +778,22 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { // and the allowed 3-character punctuators (!==, ===) are already consumed // other single character punctuators - if (code === ol.expression.Char.GREATER || - code === ol.expression.Char.LESS || - code === ol.expression.Char.BANG || - code === ol.expression.Char.AMPERSAND || - code === ol.expression.Char.PIPE) { + if (code === ol.expr.Char.GREATER || + code === ol.expr.Char.LESS || + code === ol.expr.Char.BANG || + code === ol.expr.Char.AMPERSAND || + code === ol.expr.Char.PIPE) { this.increment_(1); return { - type: ol.expression.TokenType.PUNCTUATOR, + type: ol.expr.TokenType.PUNCTUATOR, value: String.fromCharCode(code), index: start }; } - throw new ol.expression.UnexpectedToken({ - type: ol.expression.TokenType.UNKNOWN, + throw new ol.expr.UnexpectedToken({ + type: ol.expr.TokenType.UNKNOWN, value: String.fromCharCode(code), index: this.index_ }); @@ -804,12 +804,12 @@ ol.expression.Lexer.prototype.scanPunctuator_ = function(code) { * Scan string literal token. * * @param {number} quote The current character code. - * @return {ol.expression.Token} String literal token. + * @return {ol.expr.Token} String literal token. * @private */ -ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { - goog.asserts.assert(quote === ol.expression.Char.SINGLE_QUOTE || - quote === ol.expression.Char.DOUBLE_QUOTE, +ol.expr.Lexer.prototype.scanStringLiteral_ = function(quote) { + goog.asserts.assert(quote === ol.expr.Char.SINGLE_QUOTE || + quote === ol.expr.Char.DOUBLE_QUOTE, 'Strings must start with a quote: ' + String.fromCharCode(quote)); var start = this.index_; @@ -825,7 +825,7 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { break; } // look for escaped quote or backslash - if (code === ol.expression.Char.BACKSLASH) { + if (code === ol.expr.Char.BACKSLASH) { str += this.getCurrentChar_(); this.increment_(1); } else { @@ -835,11 +835,11 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { if (quote !== 0) { // unterminated string literal - throw new ol.expression.UnexpectedToken(this.peek()); + throw new ol.expr.UnexpectedToken(this.peek()); } return { - type: ol.expression.TokenType.STRING_LITERAL, + type: ol.expr.TokenType.STRING_LITERAL, value: str, index: start }; @@ -849,7 +849,7 @@ ol.expression.Lexer.prototype.scanStringLiteral_ = function(quote) { /** * After peeking, skip may be called to advance the cursor without re-scanning. */ -ol.expression.Lexer.prototype.skip = function() { +ol.expr.Lexer.prototype.skip = function() { this.index_ = this.nextIndex_; }; @@ -859,7 +859,7 @@ ol.expression.Lexer.prototype.skip = function() { * @return {number} The character code of the first non-whitespace character. * @private */ -ol.expression.Lexer.prototype.skipWhitespace_ = function() { +ol.expr.Lexer.prototype.skipWhitespace_ = function() { var code = NaN; while (this.index_ < this.length_) { code = this.getCurrentCharCode_(); @@ -876,25 +876,25 @@ ol.expression.Lexer.prototype.skipWhitespace_ = function() { /** * Error object for unexpected tokens. - * @param {ol.expression.Token} token The unexpected token. + * @param {ol.expr.Token} token The unexpected token. * @param {string=} opt_message Custom error message. * @constructor * @extends {goog.debug.Error} */ -ol.expression.UnexpectedToken = function(token, opt_message) { +ol.expr.UnexpectedToken = function(token, opt_message) { var message = goog.isDef(opt_message) ? opt_message : 'Unexpected token ' + token.value + ' at index ' + token.index; goog.debug.Error.call(this, message); /** - * @type {ol.expression.Token} + * @type {ol.expr.Token} */ this.token = token; }; -goog.inherits(ol.expression.UnexpectedToken, goog.debug.Error); +goog.inherits(ol.expr.UnexpectedToken, goog.debug.Error); /** @override */ -ol.expression.UnexpectedToken.prototype.name = 'UnexpectedToken'; +ol.expr.UnexpectedToken.prototype.name = 'UnexpectedToken'; diff --git a/src/ol/expression/parser.js b/src/ol/expr/parser.js similarity index 57% rename from src/ol/expression/parser.js rename to src/ol/expr/parser.js index 5b1544d496..042a5fc82f 100644 --- a/src/ol/expression/parser.js +++ b/src/ol/expr/parser.js @@ -13,31 +13,31 @@ * Copyright (C) 2011 Ariya Hidayat */ -goog.provide('ol.expression.Parser'); +goog.provide('ol.expr.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'); -goog.require('ol.expression.Identifier'); -goog.require('ol.expression.Lexer'); -goog.require('ol.expression.Literal'); -goog.require('ol.expression.Logical'); -goog.require('ol.expression.LogicalOp'); -goog.require('ol.expression.Math'); -goog.require('ol.expression.MathOp'); -goog.require('ol.expression.Member'); -goog.require('ol.expression.Not'); -goog.require('ol.expression.Token'); -goog.require('ol.expression.TokenType'); -goog.require('ol.expression.UnexpectedToken'); +goog.require('ol.expr.Call'); +goog.require('ol.expr.Comparison'); +goog.require('ol.expr.ComparisonOp'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Identifier'); +goog.require('ol.expr.Lexer'); +goog.require('ol.expr.Literal'); +goog.require('ol.expr.Logical'); +goog.require('ol.expr.LogicalOp'); +goog.require('ol.expr.Math'); +goog.require('ol.expr.MathOp'); +goog.require('ol.expr.Member'); +goog.require('ol.expr.Not'); +goog.require('ol.expr.Token'); +goog.require('ol.expr.TokenType'); +goog.require('ol.expr.UnexpectedToken'); /** - * Instances of ol.expression.Parser parse a very limited set of ECMAScript + * Instances of ol.expr.Parser parse a very limited set of ECMAScript * expressions (http://www.ecma-international.org/ecma-262/5.1/#sec-11). * * - Primary Expression (11.1): @@ -60,50 +60,50 @@ goog.require('ol.expression.UnexpectedToken'); * * @constructor */ -ol.expression.Parser = function() { +ol.expr.Parser = function() { }; /** * Determine the precedence for the given token. * - * @param {ol.expression.Token} token A token. + * @param {ol.expr.Token} token A token. * @return {number} The precedence for the given token. Higher gets more * precedence. * @private */ -ol.expression.Parser.prototype.binaryPrecedence_ = function(token) { +ol.expr.Parser.prototype.binaryPrecedence_ = function(token) { var precedence = 0; - if (token.type !== ol.expression.TokenType.PUNCTUATOR) { + if (token.type !== ol.expr.TokenType.PUNCTUATOR) { return precedence; } switch (token.value) { - case ol.expression.LogicalOp.OR: + case ol.expr.LogicalOp.OR: precedence = 1; break; - case ol.expression.LogicalOp.AND: + case ol.expr.LogicalOp.AND: precedence = 2; break; - case ol.expression.ComparisonOp.EQ: - case ol.expression.ComparisonOp.NEQ: - case ol.expression.ComparisonOp.STRICT_EQ: - case ol.expression.ComparisonOp.STRICT_NEQ: + case ol.expr.ComparisonOp.EQ: + case ol.expr.ComparisonOp.NEQ: + case ol.expr.ComparisonOp.STRICT_EQ: + case ol.expr.ComparisonOp.STRICT_NEQ: precedence = 3; break; - case ol.expression.ComparisonOp.GT: - case ol.expression.ComparisonOp.LT: - case ol.expression.ComparisonOp.GTE: - case ol.expression.ComparisonOp.LTE: + case ol.expr.ComparisonOp.GT: + case ol.expr.ComparisonOp.LT: + case ol.expr.ComparisonOp.GTE: + case ol.expr.ComparisonOp.LTE: precedence = 4; break; - case ol.expression.MathOp.ADD: - case ol.expression.MathOp.SUBTRACT: + case ol.expr.MathOp.ADD: + case ol.expr.MathOp.SUBTRACT: precedence = 5; break; - case ol.expression.MathOp.MULTIPLY: - case ol.expression.MathOp.DIVIDE: - case ol.expression.MathOp.MOD: + case ol.expr.MathOp.MULTIPLY: + case ol.expr.MathOp.DIVIDE: + case ol.expr.MathOp.MOD: precedence = 6; break; default: @@ -119,25 +119,25 @@ ol.expression.Parser.prototype.binaryPrecedence_ = function(token) { * Create a binary expression. * * @param {string} operator Operator. - * @param {ol.expression.Expression} left Left expression. - * @param {ol.expression.Expression} right Right expression. - * @return {ol.expression.Expression} The expression. + * @param {ol.expr.Expression} left Left expression. + * @param {ol.expr.Expression} right Right expression. + * @return {ol.expr.Expression} The expression. * @private */ -ol.expression.Parser.prototype.createBinaryExpression_ = function(operator, +ol.expr.Parser.prototype.createBinaryExpression_ = function(operator, left, right) { var expr; - if (ol.expression.Comparison.isValidOp(operator)) { - expr = new ol.expression.Comparison( - /** @type {ol.expression.ComparisonOp.} */ (operator), + if (ol.expr.Comparison.isValidOp(operator)) { + expr = new ol.expr.Comparison( + /** @type {ol.expr.ComparisonOp.} */ (operator), left, right); - } else if (ol.expression.Logical.isValidOp(operator)) { - expr = new ol.expression.Logical( - /** @type {ol.expression.LogicalOp.} */ (operator), + } else if (ol.expr.Logical.isValidOp(operator)) { + expr = new ol.expr.Logical( + /** @type {ol.expr.LogicalOp.} */ (operator), left, right); - } else if (ol.expression.Math.isValidOp(operator)) { - expr = new ol.expression.Math( - /** @type {ol.expression.MathOp.} */ (operator), + } else if (ol.expr.Math.isValidOp(operator)) { + expr = new ol.expr.Math( + /** @type {ol.expr.MathOp.} */ (operator), left, right); } else { throw new Error('Unsupported binary operator: ' + operator); @@ -149,13 +149,13 @@ ol.expression.Parser.prototype.createBinaryExpression_ = function(operator, /** * Create a call expression. * - * @param {ol.expression.Expression} callee Expression for function. - * @param {Array.} args Arguments array. - * @return {ol.expression.Call} Call expression. + * @param {ol.expr.Expression} callee Expression for function. + * @param {Array.} args Arguments array. + * @return {ol.expr.Call} Call expression. * @private */ -ol.expression.Parser.prototype.createCallExpression_ = function(callee, args) { - return new ol.expression.Call(callee, args); +ol.expr.Parser.prototype.createCallExpression_ = function(callee, args) { + return new ol.expr.Call(callee, args); }; @@ -163,11 +163,11 @@ ol.expression.Parser.prototype.createCallExpression_ = function(callee, args) { * Create an identifier expression. * * @param {string} name Identifier name. - * @return {ol.expression.Identifier} Identifier expression. + * @return {ol.expr.Identifier} Identifier expression. * @private */ -ol.expression.Parser.prototype.createIdentifier_ = function(name) { - return new ol.expression.Identifier(name); +ol.expr.Parser.prototype.createIdentifier_ = function(name) { + return new ol.expr.Identifier(name); }; @@ -175,26 +175,26 @@ ol.expression.Parser.prototype.createIdentifier_ = function(name) { * Create a literal expression. * * @param {string|number|boolean|null} value Literal value. - * @return {ol.expression.Literal} The literal expression. + * @return {ol.expr.Literal} The literal expression. * @private */ -ol.expression.Parser.prototype.createLiteral_ = function(value) { - return new ol.expression.Literal(value); +ol.expr.Parser.prototype.createLiteral_ = function(value) { + return new ol.expr.Literal(value); }; /** * Create a member expression. * - * // TODO: make exp {ol.expression.Member|ol.expression.Identifier} - * @param {ol.expression.Expression} object Expression. - * @param {ol.expression.Identifier} property Member name. - * @return {ol.expression.Member} The member expression. + * // TODO: make exp {ol.expr.Member|ol.expr.Identifier} + * @param {ol.expr.Expression} object Expression. + * @param {ol.expr.Identifier} property Member name. + * @return {ol.expr.Member} The member expression. * @private */ -ol.expression.Parser.prototype.createMemberExpression_ = function(object, +ol.expr.Parser.prototype.createMemberExpression_ = function(object, property) { - return new ol.expression.Member(object, property); + return new ol.expr.Member(object, property); }; @@ -203,18 +203,18 @@ ol.expression.Parser.prototype.createMemberExpression_ = function(object, * "!". For +/-, we apply the operator to literal expressions and return * another literal. * - * @param {ol.expression.Token} op Operator. - * @param {ol.expression.Expression} argument Expression. - * @return {ol.expression.Expression} The unary expression. + * @param {ol.expr.Token} op Operator. + * @param {ol.expr.Expression} argument Expression. + * @return {ol.expr.Expression} The unary expression. * @private */ -ol.expression.Parser.prototype.createUnaryExpression_ = function(op, argument) { +ol.expr.Parser.prototype.createUnaryExpression_ = function(op, argument) { goog.asserts.assert(op.value === '!' || op.value === '+' || op.value === '-'); var expr; if (op.value === '!') { - expr = new ol.expression.Not(argument); - } else if (!(argument instanceof ol.expression.Literal)) { - throw new ol.expression.UnexpectedToken(op); + expr = new ol.expr.Not(argument); + } else if (!(argument instanceof ol.expr.Literal)) { + throw new ol.expr.UnexpectedToken(op); } else { // we've got +/- literal if (op.value === '+') { @@ -233,14 +233,14 @@ ol.expression.Parser.prototype.createUnaryExpression_ = function(op, argument) { * Parse an expression. * * @param {string} source Expression source. - * @return {ol.expression.Expression} Expression. + * @return {ol.expr.Expression} Expression. */ -ol.expression.Parser.prototype.parse = function(source) { - var lexer = new ol.expression.Lexer(source); +ol.expr.Parser.prototype.parse = function(source) { + var lexer = new ol.expr.Lexer(source); var expr = this.parseExpression_(lexer); var token = lexer.peek(); - if (token.type !== ol.expression.TokenType.EOF) { - throw new ol.expression.UnexpectedToken(token); + if (token.type !== ol.expr.TokenType.EOF) { + throw new ol.expr.UnexpectedToken(token); } return expr; }; @@ -250,11 +250,11 @@ ol.expression.Parser.prototype.parse = function(source) { * Parse call arguments * http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.4 * - * @param {ol.expression.Lexer} lexer Lexer. - * @return {Array.} Arguments. + * @param {ol.expr.Lexer} lexer Lexer. + * @return {Array.} Arguments. * @private */ -ol.expression.Parser.prototype.parseArguments_ = function(lexer) { +ol.expr.Parser.prototype.parseArguments_ = function(lexer) { var args = []; lexer.expect('('); @@ -292,11 +292,11 @@ ol.expression.Parser.prototype.parseArguments_ = function(lexer) { * - Binary Logical Operators (`&&`, `||`) * http://www.ecma-international.org/ecma-262/5.1/#sec-11.11 * - * @param {ol.expression.Lexer} lexer Lexer. - * @return {ol.expression.Expression} Expression. + * @param {ol.expr.Lexer} lexer Lexer. + * @return {ol.expr.Expression} Expression. * @private */ -ol.expression.Parser.prototype.parseBinaryExpression_ = function(lexer) { +ol.expr.Parser.prototype.parseBinaryExpression_ = function(lexer) { var left = this.parseUnaryExpression_(lexer); var operator = lexer.peek(); @@ -343,11 +343,11 @@ ol.expression.Parser.prototype.parseBinaryExpression_ = function(lexer) { * Parse a group expression. * http://www.ecma-international.org/ecma-262/5.1/#sec-11.1.6 * - * @param {ol.expression.Lexer} lexer Lexer. - * @return {ol.expression.Expression} Expression. + * @param {ol.expr.Lexer} lexer Lexer. + * @return {ol.expr.Expression} Expression. * @private */ -ol.expression.Parser.prototype.parseGroupExpression_ = function(lexer) { +ol.expr.Parser.prototype.parseGroupExpression_ = function(lexer) { lexer.expect('('); var expr = this.parseExpression_(lexer); lexer.expect(')'); @@ -360,18 +360,18 @@ ol.expression.Parser.prototype.parseGroupExpression_ = function(lexer) { * and Call Expressions. * http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 * - * @param {ol.expression.Lexer} lexer Lexer. - * @return {ol.expression.Expression} Expression. + * @param {ol.expr.Lexer} lexer Lexer. + * @return {ol.expr.Expression} Expression. * @private */ -ol.expression.Parser.prototype.parseLeftHandSideExpression_ = function(lexer) { +ol.expr.Parser.prototype.parseLeftHandSideExpression_ = function(lexer) { var expr = this.parsePrimaryExpression_(lexer); var token = lexer.peek(); if (token.value === '(') { // only allow calls on identifiers (e.g. `foo()` not `foo.bar()`) - if (!(expr instanceof ol.expression.Identifier)) { + if (!(expr instanceof ol.expr.Identifier)) { // TODO: more helpful error messages for restricted syntax - throw new ol.expression.UnexpectedToken(token); + throw new ol.expr.UnexpectedToken(token); } var args = this.parseArguments_(lexer); expr = this.createCallExpression_(expr, args); @@ -391,19 +391,19 @@ ol.expression.Parser.prototype.parseLeftHandSideExpression_ = function(lexer) { * Parse non-computed member. * http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 * - * @param {ol.expression.Lexer} lexer Lexer. - * @return {ol.expression.Identifier} Expression. + * @param {ol.expr.Lexer} lexer Lexer. + * @return {ol.expr.Identifier} Expression. * @private */ -ol.expression.Parser.prototype.parseNonComputedMember_ = function(lexer) { +ol.expr.Parser.prototype.parseNonComputedMember_ = function(lexer) { lexer.expect('.'); var token = lexer.next(); - if (token.type !== ol.expression.TokenType.IDENTIFIER && - token.type !== ol.expression.TokenType.KEYWORD && - token.type !== ol.expression.TokenType.BOOLEAN_LITERAL && - token.type !== ol.expression.TokenType.NULL_LITERAL) { - throw new ol.expression.UnexpectedToken(token); + if (token.type !== ol.expr.TokenType.IDENTIFIER && + token.type !== ol.expr.TokenType.KEYWORD && + token.type !== ol.expr.TokenType.BOOLEAN_LITERAL && + token.type !== ol.expr.TokenType.NULL_LITERAL) { + throw new ol.expr.UnexpectedToken(token); } return this.createIdentifier_(String(token.value)); @@ -414,11 +414,11 @@ ol.expression.Parser.prototype.parseNonComputedMember_ = function(lexer) { * Parse primary expression. * http://www.ecma-international.org/ecma-262/5.1/#sec-11.1 * - * @param {ol.expression.Lexer} lexer Lexer. - * @return {ol.expression.Expression} Expression. + * @param {ol.expr.Lexer} lexer Lexer. + * @return {ol.expr.Expression} Expression. * @private */ -ol.expression.Parser.prototype.parsePrimaryExpression_ = function(lexer) { +ol.expr.Parser.prototype.parsePrimaryExpression_ = function(lexer) { var token = lexer.peek(); if (token.value === '(') { return this.parseGroupExpression_(lexer); @@ -426,19 +426,19 @@ ol.expression.Parser.prototype.parsePrimaryExpression_ = function(lexer) { lexer.skip(); var expr; var type = token.type; - if (type === ol.expression.TokenType.IDENTIFIER) { + if (type === ol.expr.TokenType.IDENTIFIER) { expr = this.createIdentifier_(/** @type {string} */ (token.value)); - } else if (type === ol.expression.TokenType.STRING_LITERAL || - type === ol.expression.TokenType.NUMERIC_LITERAL) { + } else if (type === ol.expr.TokenType.STRING_LITERAL || + type === ol.expr.TokenType.NUMERIC_LITERAL) { // numeric and string literals are already the correct type expr = this.createLiteral_(token.value); - } else if (type === ol.expression.TokenType.BOOLEAN_LITERAL) { + } else if (type === ol.expr.TokenType.BOOLEAN_LITERAL) { // because booleans are valid member properties, tokens are still string expr = this.createLiteral_(token.value === 'true'); - } else if (type === ol.expression.TokenType.NULL_LITERAL) { + } else if (type === ol.expr.TokenType.NULL_LITERAL) { expr = this.createLiteral_(null); } else { - throw new ol.expression.UnexpectedToken(token); + throw new ol.expr.UnexpectedToken(token); } return expr; }; @@ -448,14 +448,14 @@ ol.expression.Parser.prototype.parsePrimaryExpression_ = function(lexer) { * Parse expression with a unary operator. Limited to logical not operator. * http://www.ecma-international.org/ecma-262/5.1/#sec-11.4 * - * @param {ol.expression.Lexer} lexer Lexer. - * @return {ol.expression.Expression} Expression. + * @param {ol.expr.Lexer} lexer Lexer. + * @return {ol.expr.Expression} Expression. * @private */ -ol.expression.Parser.prototype.parseUnaryExpression_ = function(lexer) { +ol.expr.Parser.prototype.parseUnaryExpression_ = function(lexer) { var expr; var operator = lexer.peek(); - if (operator.type !== ol.expression.TokenType.PUNCTUATOR) { + if (operator.type !== ol.expr.TokenType.PUNCTUATOR) { expr = this.parseLeftHandSideExpression_(lexer); } else if (operator.value === '!' || operator.value === '-' || operator.value === '+') { @@ -472,10 +472,10 @@ ol.expression.Parser.prototype.parseUnaryExpression_ = function(lexer) { /** * Parse an expression. * - * @param {ol.expression.Lexer} lexer Lexer. - * @return {ol.expression.Expression} Expression. + * @param {ol.expr.Lexer} lexer Lexer. + * @return {ol.expr.Expression} Expression. * @private */ -ol.expression.Parser.prototype.parseExpression_ = function(lexer) { +ol.expr.Parser.prototype.parseExpression_ = function(lexer) { return this.parseBinaryExpression_(lexer); }; diff --git a/src/ol/expression.jsdoc b/src/ol/expression.jsdoc deleted file mode 100644 index 93f3016f63..0000000000 --- a/src/ol/expression.jsdoc +++ /dev/null @@ -1,3 +0,0 @@ -/** - * @namespace ol.expression - */ diff --git a/src/ol/expression/expression.exports b/src/ol/expression/expression.exports deleted file mode 100644 index e3702d1e8b..0000000000 --- a/src/ol/expression/expression.exports +++ /dev/null @@ -1,2 +0,0 @@ -@exportSymbol ol.expression.parse -@exportSymbol ol.expression.register diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 261fd23f1d..3487f187d8 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -5,10 +5,10 @@ goog.require('goog.asserts'); goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('ol.Feature'); -goog.require('ol.expression'); -goog.require('ol.expression.Literal'); -goog.require('ol.expression.Logical'); -goog.require('ol.expression.LogicalOp'); +goog.require('ol.expr'); +goog.require('ol.expr.Literal'); +goog.require('ol.expr.Logical'); +goog.require('ol.expr.LogicalOp'); goog.require('ol.geom.GeometryType'); goog.require('ol.geom.SharedVertices'); goog.require('ol.layer.Layer'); @@ -83,7 +83,7 @@ ol.layer.FeatureCache.prototype.add = function(feature) { /** - * @param {ol.expression.Expression=} opt_expr Expression for filtering. + * @param {ol.expr.Expression=} opt_expr Expression for filtering. * @return {Object.} Object of features, keyed by id. */ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_expr) { @@ -92,48 +92,48 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_expr) { features = this.idLookup_; } else { // check for geometryType or extent expression - var name = ol.expression.isLibCall(opt_expr); + var name = ol.expr.isLibCall(opt_expr); if (name === 'geometryType') { - var args = /** @type {ol.expression.Call} */ (opt_expr).getArgs(); + var args = /** @type {ol.expr.Call} */ (opt_expr).getArgs(); goog.asserts.assert(args.length === 1); - goog.asserts.assert(args[0] instanceof ol.expression.Literal); - var type = /** @type {ol.expression.Literal } */ (args[0]).evaluate(); + goog.asserts.assert(args[0] instanceof ol.expr.Literal); + var type = /** @type {ol.expr.Literal } */ (args[0]).evaluate(); goog.asserts.assertString(type); features = this.geometryTypeIndex_[type]; } else if (name === 'extent') { - var args = /** @type {ol.expression.Call} */ (opt_expr).getArgs(); + var args = /** @type {ol.expr.Call} */ (opt_expr).getArgs(); goog.asserts.assert(args.length === 4); var extent = []; for (var i = 0; i < 4; ++i) { - goog.asserts.assert(args[i] instanceof ol.expression.Literal); - extent[i] = /** @type {ol.expression.Literal} */ (args[i]).evaluate(); + goog.asserts.assert(args[i] instanceof ol.expr.Literal); + extent[i] = /** @type {ol.expr.Literal} */ (args[i]).evaluate(); goog.asserts.assertNumber(extent[i]); } features = this.rTree_.searchReturningObject(extent); } else { // not a call expression, check logical - if (opt_expr instanceof ol.expression.Logical) { - var op = /** @type {ol.expression.Logical} */ (opt_expr).getOperator(); - if (op === ol.expression.LogicalOp.AND) { + if (opt_expr instanceof ol.expr.Logical) { + var op = /** @type {ol.expr.Logical} */ (opt_expr).getOperator(); + if (op === ol.expr.LogicalOp.AND) { var expressions = [opt_expr.getLeft(), opt_expr.getRight()]; var expr, args, type, extent; for (var i = 0; i <= 1; ++i) { expr = expressions[i]; - name = ol.expression.isLibCall(expr); + name = ol.expr.isLibCall(expr); if (name === 'geometryType') { - args = /** @type {ol.expression.Call} */ (expr).getArgs(); + args = /** @type {ol.expr.Call} */ (expr).getArgs(); goog.asserts.assert(args.length === 1); - goog.asserts.assert(args[0] instanceof ol.expression.Literal); - type = /** @type {ol.expression.Literal } */ (args[0]).evaluate(); + goog.asserts.assert(args[0] instanceof ol.expr.Literal); + type = /** @type {ol.expr.Literal } */ (args[0]).evaluate(); goog.asserts.assertString(type); } else if (name === 'extent') { - args = /** @type {ol.expression.Call} */ (expr).getArgs(); + args = /** @type {ol.expr.Call} */ (expr).getArgs(); goog.asserts.assert(args.length === 4); extent = []; for (var j = 0; j < 4; ++j) { - goog.asserts.assert(args[j] instanceof ol.expression.Literal); + goog.asserts.assert(args[j] instanceof ol.expr.Literal); extent[j] = - /** @type {ol.expression.Literal} */ (args[j]).evaluate(); + /** @type {ol.expr.Literal} */ (args[j]).evaluate(); goog.asserts.assertNumber(extent[j]); } } @@ -152,7 +152,7 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_expr) { features = {}; for (i in candidates) { feature = candidates[i]; - if (ol.expression.evaluateFeature(opt_expr, feature)) { + if (ol.expr.evaluateFeature(opt_expr, feature)) { features[i] = feature; } } @@ -278,7 +278,7 @@ ol.layer.Vector.prototype.getVectorSource = function() { /** - * @param {ol.expression.Expression=} opt_expr Expression for filtering. + * @param {ol.expr.Expression=} opt_expr Expression for filtering. * @return {Array.} Array of features. */ ol.layer.Vector.prototype.getFeatures = function(opt_expr) { @@ -288,7 +288,7 @@ ol.layer.Vector.prototype.getFeatures = function(opt_expr) { /** - * @param {ol.expression.Expression=} opt_expr Expression for filtering. + * @param {ol.expr.Expression=} opt_expr Expression for filtering. * @return {Object.} Features. */ ol.layer.Vector.prototype.getFeaturesObject = function(opt_expr) { diff --git a/src/ol/style/icon.js b/src/ol/style/icon.js index f4116b6c79..9be3af41a8 100644 --- a/src/ol/style/icon.js +++ b/src/ol/style/icon.js @@ -3,9 +3,9 @@ goog.provide('ol.style.IconLiteral'); goog.provide('ol.style.IconType'); goog.require('goog.asserts'); -goog.require('ol.expression'); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Literal'); +goog.require('ol.expr'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Literal'); goog.require('ol.style.Point'); goog.require('ol.style.PointLiteral'); @@ -70,47 +70,47 @@ ol.style.Icon = function(options) { goog.asserts.assert(options.url, 'url must be set'); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ - this.url_ = (options.url instanceof ol.expression.Expression) ? - options.url : new ol.expression.Literal(options.url); + this.url_ = (options.url instanceof ol.expr.Expression) ? + options.url : new ol.expr.Literal(options.url); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.width_ = !goog.isDef(options.width) ? null : - (options.width instanceof ol.expression.Expression) ? - options.width : new ol.expression.Literal(options.width); + (options.width instanceof ol.expr.Expression) ? + options.width : new ol.expr.Literal(options.width); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.height_ = !goog.isDef(options.height) ? null : - (options.height instanceof ol.expression.Expression) ? - options.height : new ol.expression.Literal(options.height); + (options.height instanceof ol.expr.Expression) ? + options.height : new ol.expr.Literal(options.height); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.expression.Literal(ol.style.IconDefaults.opacity) : - (options.opacity instanceof ol.expression.Expression) ? - options.opacity : new ol.expression.Literal(options.opacity); + new ol.expr.Literal(ol.style.IconDefaults.opacity) : + (options.opacity instanceof ol.expr.Expression) ? + options.opacity : new ol.expr.Literal(options.opacity); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.rotation_ = !goog.isDef(options.rotation) ? - new ol.expression.Literal(ol.style.IconDefaults.rotation) : - (options.rotation instanceof ol.expression.Expression) ? - options.rotation : new ol.expression.Literal(options.rotation); + new ol.expr.Literal(ol.style.IconDefaults.rotation) : + (options.rotation instanceof ol.expr.Expression) ? + options.rotation : new ol.expr.Literal(options.rotation); }; @@ -121,26 +121,26 @@ ol.style.Icon = function(options) { */ ol.style.Icon.prototype.createLiteral = function(opt_feature) { - var url = ol.expression.evaluateFeature(this.url_, opt_feature); + var url = ol.expr.evaluateFeature(this.url_, opt_feature); goog.asserts.assertString(url, 'url must be a string'); goog.asserts.assert(url != '#', 'url must not be "#"'); var width; if (!goog.isNull(this.width_)) { - width = ol.expression.evaluateFeature(this.width_, opt_feature); + width = ol.expr.evaluateFeature(this.width_, opt_feature); goog.asserts.assertNumber(width, 'width must be a number'); } var height; if (!goog.isNull(this.height_)) { - height = ol.expression.evaluateFeature(this.height_, opt_feature); + height = ol.expr.evaluateFeature(this.height_, opt_feature); goog.asserts.assertNumber(height, 'height must be a number'); } - var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); + var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); - var rotation = ol.expression.evaluateFeature(this.rotation_, opt_feature); + var rotation = ol.expr.evaluateFeature(this.rotation_, opt_feature); goog.asserts.assertNumber(rotation, 'rotation must be a number'); return new ol.style.IconLiteral({ diff --git a/src/ol/style/line.js b/src/ol/style/line.js index 5c9e249580..5fc17a2e7a 100644 --- a/src/ol/style/line.js +++ b/src/ol/style/line.js @@ -2,9 +2,9 @@ goog.provide('ol.style.Line'); goog.provide('ol.style.LineLiteral'); goog.require('goog.asserts'); -goog.require('ol.expression'); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Literal'); +goog.require('ol.expr'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Literal'); goog.require('ol.style.Symbolizer'); goog.require('ol.style.SymbolizerLiteral'); @@ -64,31 +64,31 @@ ol.style.Line = function(options) { goog.base(this); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.strokeColor_ = !goog.isDef(options.strokeColor) ? - new ol.expression.Literal(ol.style.LineDefaults.strokeColor) : - (options.strokeColor instanceof ol.expression.Expression) ? - options.strokeColor : new ol.expression.Literal(options.strokeColor); + new ol.expr.Literal(ol.style.LineDefaults.strokeColor) : + (options.strokeColor instanceof ol.expr.Expression) ? + options.strokeColor : new ol.expr.Literal(options.strokeColor); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.strokeWidth_ = !goog.isDef(options.strokeWidth) ? - new ol.expression.Literal(ol.style.LineDefaults.strokeWidth) : - (options.strokeWidth instanceof ol.expression.Expression) ? - options.strokeWidth : new ol.expression.Literal(options.strokeWidth); + new ol.expr.Literal(ol.style.LineDefaults.strokeWidth) : + (options.strokeWidth instanceof ol.expr.Expression) ? + options.strokeWidth : new ol.expr.Literal(options.strokeWidth); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.expression.Literal(ol.style.LineDefaults.opacity) : - (options.opacity instanceof ol.expression.Expression) ? - options.opacity : new ol.expression.Literal(options.opacity); + new ol.expr.Literal(ol.style.LineDefaults.opacity) : + (options.opacity instanceof ol.expr.Expression) ? + options.opacity : new ol.expr.Literal(options.opacity); }; goog.inherits(ol.style.Line, ol.style.Symbolizer); @@ -100,15 +100,15 @@ goog.inherits(ol.style.Line, ol.style.Symbolizer); */ ol.style.Line.prototype.createLiteral = function(opt_feature) { - var strokeColor = ol.expression.evaluateFeature( + var strokeColor = ol.expr.evaluateFeature( this.strokeColor_, opt_feature); goog.asserts.assertString(strokeColor, 'strokeColor must be a string'); - var strokeWidth = ol.expression.evaluateFeature( + var strokeWidth = ol.expr.evaluateFeature( this.strokeWidth_, opt_feature); goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number'); - var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); + var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.LineLiteral({ diff --git a/src/ol/style/polygon.js b/src/ol/style/polygon.js index df683438a6..57fa4afb6b 100644 --- a/src/ol/style/polygon.js +++ b/src/ol/style/polygon.js @@ -2,9 +2,9 @@ goog.provide('ol.style.Polygon'); goog.provide('ol.style.PolygonLiteral'); goog.require('goog.asserts'); -goog.require('ol.expression'); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Literal'); +goog.require('ol.expr'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Literal'); goog.require('ol.style.Symbolizer'); goog.require('ol.style.SymbolizerLiteral'); @@ -81,13 +81,13 @@ ol.style.Polygon = function(options) { goog.base(this); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.fillColor_ = !goog.isDefAndNotNull(options.fillColor) ? null : - (options.fillColor instanceof ol.expression.Expression) ? - options.fillColor : new ol.expression.Literal(options.fillColor); + (options.fillColor instanceof ol.expr.Expression) ? + options.fillColor : new ol.expr.Literal(options.fillColor); // stroke handling - if any stroke property is supplied, use defaults var strokeColor = null, @@ -97,32 +97,32 @@ ol.style.Polygon = function(options) { goog.isDefAndNotNull(options.strokeWidth)) { if (goog.isDefAndNotNull(options.strokeColor)) { - strokeColor = (options.strokeColor instanceof ol.expression.Expression) ? + strokeColor = (options.strokeColor instanceof ol.expr.Expression) ? options.strokeColor : - new ol.expression.Literal(options.strokeColor); + new ol.expr.Literal(options.strokeColor); } else { - strokeColor = new ol.expression.Literal( + strokeColor = new ol.expr.Literal( /** @type {string} */ (ol.style.PolygonDefaults.strokeColor)); } if (goog.isDefAndNotNull(options.strokeWidth)) { - strokeWidth = (options.strokeWidth instanceof ol.expression.Expression) ? + strokeWidth = (options.strokeWidth instanceof ol.expr.Expression) ? options.strokeWidth : - new ol.expression.Literal(options.strokeWidth); + new ol.expr.Literal(options.strokeWidth); } else { - strokeWidth = new ol.expression.Literal( + strokeWidth = new ol.expr.Literal( /** @type {number} */ (ol.style.PolygonDefaults.strokeWidth)); } } /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.strokeColor_ = strokeColor; /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.strokeWidth_ = strokeWidth; @@ -133,13 +133,13 @@ ol.style.Polygon = function(options) { 'Stroke or fill properties must be provided'); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.expression.Literal(ol.style.PolygonDefaults.opacity) : - (options.opacity instanceof ol.expression.Expression) ? - options.opacity : new ol.expression.Literal(options.opacity); + new ol.expr.Literal(ol.style.PolygonDefaults.opacity) : + (options.opacity instanceof ol.expr.Expression) ? + options.opacity : new ol.expr.Literal(options.opacity); }; goog.inherits(ol.style.Polygon, ol.style.Symbolizer); @@ -153,19 +153,19 @@ ol.style.Polygon.prototype.createLiteral = function(opt_feature) { var fillColor; if (!goog.isNull(this.fillColor_)) { - fillColor = ol.expression.evaluateFeature(this.fillColor_, opt_feature); + fillColor = ol.expr.evaluateFeature(this.fillColor_, opt_feature); goog.asserts.assertString(fillColor, 'fillColor must be a string'); } var strokeColor; if (!goog.isNull(this.strokeColor_)) { - strokeColor = ol.expression.evaluateFeature(this.strokeColor_, opt_feature); + strokeColor = ol.expr.evaluateFeature(this.strokeColor_, opt_feature); goog.asserts.assertString(strokeColor, 'strokeColor must be a string'); } var strokeWidth; if (!goog.isNull(this.strokeWidth_)) { - strokeWidth = ol.expression.evaluateFeature(this.strokeWidth_, opt_feature); + strokeWidth = ol.expr.evaluateFeature(this.strokeWidth_, opt_feature); goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number'); } @@ -174,7 +174,7 @@ ol.style.Polygon.prototype.createLiteral = function(opt_feature) { (goog.isDef(strokeColor) && goog.isDef(strokeWidth)), 'either fillColor or strokeColor and strokeWidth must be defined'); - var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); + var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.PolygonLiteral({ diff --git a/src/ol/style/rule.js b/src/ol/style/rule.js index 4410fdfc15..97ecb5b84c 100644 --- a/src/ol/style/rule.js +++ b/src/ol/style/rule.js @@ -3,8 +3,8 @@ goog.provide('ol.style.Rule'); goog.require('goog.asserts'); goog.require('ol.Feature'); -goog.require('ol.expression'); -goog.require('ol.expression.Expression'); +goog.require('ol.expr'); +goog.require('ol.expr.Expression'); goog.require('ol.style.Symbolizer'); @@ -19,15 +19,15 @@ ol.style.Rule = function(options) { var filter = null; if (goog.isDef(options.filter)) { if (goog.isString(options.filter)) { - filter = ol.expression.parse(options.filter); + filter = ol.expr.parse(options.filter); } else { - goog.asserts.assert(options.filter instanceof ol.expression.Expression); + goog.asserts.assert(options.filter instanceof ol.expr.Expression); filter = options.filter; } } /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.filter_ = filter; @@ -48,7 +48,7 @@ ol.style.Rule = function(options) { */ ol.style.Rule.prototype.applies = function(feature) { return goog.isNull(this.filter_) ? - true : !!ol.expression.evaluateFeature(this.filter_, feature); + true : !!ol.expr.evaluateFeature(this.filter_, feature); }; diff --git a/src/ol/style/shape.js b/src/ol/style/shape.js index 5c8802b47f..751cf66ab2 100644 --- a/src/ol/style/shape.js +++ b/src/ol/style/shape.js @@ -3,9 +3,9 @@ goog.provide('ol.style.ShapeLiteral'); goog.provide('ol.style.ShapeType'); goog.require('goog.asserts'); -goog.require('ol.expression'); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Literal'); +goog.require('ol.expr'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Literal'); goog.require('ol.style.Point'); goog.require('ol.style.PointLiteral'); @@ -107,22 +107,22 @@ ol.style.Shape = function(options) { options.type : ol.style.ShapeDefaults.type); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.size_ = !goog.isDef(options.size) ? - new ol.expression.Literal(ol.style.ShapeDefaults.size) : - (options.size instanceof ol.expression.Expression) ? - options.size : new ol.expression.Literal(options.size); + new ol.expr.Literal(ol.style.ShapeDefaults.size) : + (options.size instanceof ol.expr.Expression) ? + options.size : new ol.expr.Literal(options.size); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.fillColor_ = !goog.isDefAndNotNull(options.fillColor) ? null : - (options.fillColor instanceof ol.expression.Expression) ? - options.fillColor : new ol.expression.Literal(options.fillColor); + (options.fillColor instanceof ol.expr.Expression) ? + options.fillColor : new ol.expr.Literal(options.fillColor); // stroke handling - if any stroke property is supplied, use defaults var strokeColor = null, @@ -132,33 +132,33 @@ ol.style.Shape = function(options) { goog.isDefAndNotNull(options.strokeWidth)) { if (goog.isDefAndNotNull(options.strokeColor)) { - strokeColor = (options.strokeColor instanceof ol.expression.Expression) ? + strokeColor = (options.strokeColor instanceof ol.expr.Expression) ? options.strokeColor : - new ol.expression.Literal(options.strokeColor); + new ol.expr.Literal(options.strokeColor); } else { - strokeColor = new ol.expression.Literal( + strokeColor = new ol.expr.Literal( /** @type {string} */ (ol.style.ShapeDefaults.strokeColor)); } if (goog.isDefAndNotNull(options.strokeWidth)) { - strokeWidth = (options.strokeWidth instanceof ol.expression.Expression) ? + strokeWidth = (options.strokeWidth instanceof ol.expr.Expression) ? options.strokeWidth : - new ol.expression.Literal(options.strokeWidth); + new ol.expr.Literal(options.strokeWidth); } else { - strokeWidth = new ol.expression.Literal( + strokeWidth = new ol.expr.Literal( /** @type {number} */ (ol.style.ShapeDefaults.strokeWidth)); } } /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.strokeColor_ = strokeColor; /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.strokeWidth_ = strokeWidth; @@ -169,13 +169,13 @@ ol.style.Shape = function(options) { 'Stroke or fill properties must be provided'); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.expression.Literal(ol.style.ShapeDefaults.opacity) : - (options.opacity instanceof ol.expression.Expression) ? - options.opacity : new ol.expression.Literal(options.opacity); + new ol.expr.Literal(ol.style.ShapeDefaults.opacity) : + (options.opacity instanceof ol.expr.Expression) ? + options.opacity : new ol.expr.Literal(options.opacity); }; @@ -186,24 +186,24 @@ ol.style.Shape = function(options) { */ ol.style.Shape.prototype.createLiteral = function(opt_feature) { - var size = ol.expression.evaluateFeature(this.size_, opt_feature); + var size = ol.expr.evaluateFeature(this.size_, opt_feature); goog.asserts.assertNumber(size, 'size must be a number'); var fillColor; if (!goog.isNull(this.fillColor_)) { - fillColor = ol.expression.evaluateFeature(this.fillColor_, opt_feature); + fillColor = ol.expr.evaluateFeature(this.fillColor_, opt_feature); goog.asserts.assertString(fillColor, 'fillColor must be a string'); } var strokeColor; if (!goog.isNull(this.strokeColor_)) { - strokeColor = ol.expression.evaluateFeature(this.strokeColor_, opt_feature); + strokeColor = ol.expr.evaluateFeature(this.strokeColor_, opt_feature); goog.asserts.assertString(strokeColor, 'strokeColor must be a string'); } var strokeWidth; if (!goog.isNull(this.strokeWidth_)) { - strokeWidth = ol.expression.evaluateFeature(this.strokeWidth_, opt_feature); + strokeWidth = ol.expr.evaluateFeature(this.strokeWidth_, opt_feature); goog.asserts.assertNumber(strokeWidth, 'strokeWidth must be a number'); } @@ -212,7 +212,7 @@ ol.style.Shape.prototype.createLiteral = function(opt_feature) { (goog.isDef(strokeColor) && goog.isDef(strokeWidth)), 'either fillColor or strokeColor and strokeWidth must be defined'); - var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); + var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.ShapeLiteral({ diff --git a/src/ol/style/text.js b/src/ol/style/text.js index 1c8b4005f9..a1862bc776 100644 --- a/src/ol/style/text.js +++ b/src/ol/style/text.js @@ -2,9 +2,9 @@ goog.provide('ol.style.Text'); goog.provide('ol.style.TextLiteral'); goog.require('goog.asserts'); -goog.require('ol.expression'); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Literal'); +goog.require('ol.expr'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Literal'); goog.require('ol.style.Symbolizer'); goog.require('ol.style.SymbolizerLiteral'); @@ -71,47 +71,47 @@ ol.style.TextLiteral.prototype.equals = function(textLiteral) { ol.style.Text = function(options) { /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.color_ = !goog.isDef(options.color) ? - new ol.expression.Literal(ol.style.TextDefaults.color) : - (options.color instanceof ol.expression.Expression) ? - options.color : new ol.expression.Literal(options.color); + new ol.expr.Literal(ol.style.TextDefaults.color) : + (options.color instanceof ol.expr.Expression) ? + options.color : new ol.expr.Literal(options.color); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.fontFamily_ = !goog.isDef(options.fontFamily) ? - new ol.expression.Literal(ol.style.TextDefaults.fontFamily) : - (options.fontFamily instanceof ol.expression.Expression) ? - options.fontFamily : new ol.expression.Literal(options.fontFamily); + new ol.expr.Literal(ol.style.TextDefaults.fontFamily) : + (options.fontFamily instanceof ol.expr.Expression) ? + options.fontFamily : new ol.expr.Literal(options.fontFamily); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.fontSize_ = !goog.isDef(options.fontSize) ? - new ol.expression.Literal(ol.style.TextDefaults.fontSize) : - (options.fontSize instanceof ol.expression.Expression) ? - options.fontSize : new ol.expression.Literal(options.fontSize); + new ol.expr.Literal(ol.style.TextDefaults.fontSize) : + (options.fontSize instanceof ol.expr.Expression) ? + options.fontSize : new ol.expr.Literal(options.fontSize); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ - this.text_ = (options.text instanceof ol.expression.Expression) ? - options.text : new ol.expression.Literal(options.text); + this.text_ = (options.text instanceof ol.expr.Expression) ? + options.text : new ol.expr.Literal(options.text); /** - * @type {ol.expression.Expression} + * @type {ol.expr.Expression} * @private */ this.opacity_ = !goog.isDef(options.opacity) ? - new ol.expression.Literal(ol.style.TextDefaults.opacity) : - (options.opacity instanceof ol.expression.Expression) ? - options.opacity : new ol.expression.Literal(options.opacity); + new ol.expr.Literal(ol.style.TextDefaults.opacity) : + (options.opacity instanceof ol.expr.Expression) ? + options.opacity : new ol.expr.Literal(options.opacity); }; goog.inherits(ol.style.Text, ol.style.Symbolizer); @@ -123,19 +123,19 @@ goog.inherits(ol.style.Text, ol.style.Symbolizer); */ ol.style.Text.prototype.createLiteral = function(opt_feature) { - var color = ol.expression.evaluateFeature(this.color_, opt_feature); + var color = ol.expr.evaluateFeature(this.color_, opt_feature); goog.asserts.assertString(color, 'color must be a string'); - var fontFamily = ol.expression.evaluateFeature(this.fontFamily_, opt_feature); + var fontFamily = ol.expr.evaluateFeature(this.fontFamily_, opt_feature); goog.asserts.assertString(fontFamily, 'fontFamily must be a string'); - var fontSize = ol.expression.evaluateFeature(this.fontSize_, opt_feature); + var fontSize = ol.expr.evaluateFeature(this.fontSize_, opt_feature); goog.asserts.assertNumber(fontSize, 'fontSize must be a number'); - var text = ol.expression.evaluateFeature(this.text_, opt_feature); + var text = ol.expr.evaluateFeature(this.text_, opt_feature); goog.asserts.assertString(text, 'text must be a string'); - var opacity = ol.expression.evaluateFeature(this.opacity_, opt_feature); + var opacity = ol.expr.evaluateFeature(this.opacity_, opt_feature); goog.asserts.assertNumber(opacity, 'opacity must be a number'); return new ol.style.TextLiteral({ diff --git a/test/spec/ol/expression/expression.test.js b/test/spec/ol/expr/expression.test.js similarity index 68% rename from test/spec/ol/expression/expression.test.js rename to test/spec/ol/expr/expression.test.js index 7b330b4d1c..6e2946ebd2 100644 --- a/test/spec/ol/expression/expression.test.js +++ b/test/spec/ol/expr/expression.test.js @@ -1,33 +1,33 @@ goog.provide('ol.test.expression'); -describe('ol.expression.parse()', function() { +describe('ol.expr.parse()', function() { it('parses a subset of ECMAScript 5.1 expressions', function() { - var expr = ol.expression.parse('foo'); - expect(expr).to.be.a(ol.expression.Expression); + var expr = ol.expr.parse('foo'); + expect(expr).to.be.a(ol.expr.Expression); }); describe('11.1 - primary expressions', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.1 it('parses identifier expressions', function() { - var expr = ol.expression.parse('foo'); - expect(expr).to.be.a(ol.expression.Identifier); + var expr = ol.expr.parse('foo'); + expect(expr).to.be.a(ol.expr.Identifier); expect(expr.evaluate({foo: 'bar'})).to.be('bar'); }); it('consumes whitespace as expected', function() { - var expr = ol.expression.parse(' foo '); - expect(expr).to.be.a(ol.expression.Identifier); + var expr = ol.expr.parse(' foo '); + expect(expr).to.be.a(ol.expr.Identifier); expect(expr.evaluate({foo: 'bar'})).to.be('bar'); }); it('throws on invalid identifier expressions', function() { expect(function() { - ol.expression.parse('3foo'); + ol.expr.parse('3foo'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('f'); expect(token.index).to.be(1); @@ -35,33 +35,33 @@ describe('ol.expression.parse()', function() { }); it('parses string literal expressions', function() { - var expr = ol.expression.parse('"foo"'); - expect(expr).to.be.a(ol.expression.Literal); + var expr = ol.expr.parse('"foo"'); + expect(expr).to.be.a(ol.expr.Literal); expect(expr.evaluate()).to.be('foo'); }); it('throws on unterminated string', function() { expect(function() { - ol.expression.parse('"foo'); + ol.expr.parse('"foo'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; - expect(token.type).to.be(ol.expression.TokenType.EOF); + expect(token.type).to.be(ol.expr.TokenType.EOF); expect(token.index).to.be(4); }); }); it('parses numeric literal expressions', function() { - var expr = ol.expression.parse('.42e+2'); - expect(expr).to.be.a(ol.expression.Literal); + var expr = ol.expr.parse('.42e+2'); + expect(expr).to.be.a(ol.expr.Literal); expect(expr.evaluate()).to.be(42); }); it('throws on invalid number', function() { expect(function() { - ol.expression.parse('.42eX'); + ol.expr.parse('.42eX'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('X'); expect(token.index).to.be(4); @@ -69,14 +69,14 @@ describe('ol.expression.parse()', function() { }); it('parses boolean literal expressions', function() { - var expr = ol.expression.parse('false'); - expect(expr).to.be.a(ol.expression.Literal); + var expr = ol.expr.parse('false'); + expect(expr).to.be.a(ol.expr.Literal); expect(expr.evaluate()).to.be(false); }); it('parses null literal expressions', function() { - var expr = ol.expression.parse('null'); - expect(expr).to.be.a(ol.expression.Literal); + var expr = ol.expr.parse('null'); + expect(expr).to.be.a(ol.expr.Literal); expect(expr.evaluate()).to.be(null); }); @@ -86,24 +86,24 @@ describe('ol.expression.parse()', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.2 it('parses member expressions with dot notation', function() { - var expr = ol.expression.parse('foo.bar.baz'); - expect(expr).to.be.a(ol.expression.Member); + var expr = ol.expr.parse('foo.bar.baz'); + expect(expr).to.be.a(ol.expr.Member); var scope = {foo: {bar: {baz: 42}}}; expect(expr.evaluate(scope)).to.be(42); }); it('consumes whitespace as expected', function() { - var expr = ol.expression.parse(' foo . bar . baz '); - expect(expr).to.be.a(ol.expression.Member); + var expr = ol.expr.parse(' foo . bar . baz '); + expect(expr).to.be.a(ol.expr.Member); var scope = {foo: {bar: {baz: 42}}}; expect(expr.evaluate(scope)).to.be(42); }); it('throws on invalid member expression', function() { expect(function() { - ol.expression.parse('foo.4bar'); + ol.expr.parse('foo.4bar'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('b'); expect(token.index).to.be(5); @@ -111,8 +111,8 @@ describe('ol.expression.parse()', function() { }); it('parses call expressions with literal arguments', function() { - var expr = ol.expression.parse('foo(42, "bar")'); - expect(expr).to.be.a(ol.expression.Call); + var expr = ol.expr.parse('foo(42, "bar")'); + expect(expr).to.be.a(ol.expr.Call); var scope = { foo: function(num, str) { expect(num).to.be(42); @@ -125,9 +125,9 @@ describe('ol.expression.parse()', function() { it('throws on calls with unterminated arguments', function() { expect(function() { - ol.expression.parse('foo(42,)'); + ol.expr.parse('foo(42,)'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be(')'); expect(token.index).to.be(7); @@ -146,8 +146,8 @@ describe('ol.expression.parse()', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.4 it('parses logical not operator', function() { - var expr = ol.expression.parse('!foo'); - expect(expr).to.be.a(ol.expression.Not); + var expr = ol.expr.parse('!foo'); + expect(expr).to.be.a(ol.expr.Not); expect(expr.evaluate({foo: true})).to.be(false); expect(expr.evaluate({foo: false})).to.be(true); expect(expr.evaluate({foo: ''})).to.be(true); @@ -155,8 +155,8 @@ describe('ol.expression.parse()', function() { }); it('consumes whitespace as expected', function() { - var expr = ol.expression.parse(' ! foo'); - expect(expr).to.be.a(ol.expression.Not); + var expr = ol.expr.parse(' ! foo'); + expect(expr).to.be.a(ol.expr.Not); expect(expr.evaluate({foo: true})).to.be(false); expect(expr.evaluate({foo: false})).to.be(true); }); @@ -167,38 +167,38 @@ describe('ol.expression.parse()', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.5 it('parses * operator', function() { - var expr = ol.expression.parse('foo*bar'); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse('foo*bar'); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 10, bar: 20})).to.be(200); }); it('consumes whitespace as expected with *', function() { - var expr = ol.expression.parse(' foo * bar '); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse(' foo * bar '); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 15, bar: 2})).to.be(30); }); it('parses / operator', function() { - var expr = ol.expression.parse('foo/12'); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse('foo/12'); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 10})).to.be(10 / 12); }); it('consumes whitespace as expected with /', function() { - var expr = ol.expression.parse(' 4 / bar '); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse(' 4 / bar '); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({bar: 3})).to.be(4 / 3); }); it('parses % operator', function() { - var expr = ol.expression.parse('12%foo'); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse('12%foo'); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 10})).to.be(2); }); it('consumes whitespace as expected with %', function() { - var expr = ol.expression.parse(' 4 %bar '); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse(' 4 %bar '); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({bar: 3})).to.be(1); }); @@ -208,26 +208,26 @@ describe('ol.expression.parse()', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.6 it('parses + operator', function() { - var expr = ol.expression.parse('foo+bar'); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse('foo+bar'); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 10, bar: 20})).to.be(30); }); it('consumes whitespace as expected with +', function() { - var expr = ol.expression.parse(' foo +10 '); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse(' foo +10 '); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 15})).to.be(25); }); it('parses - operator', function() { - var expr = ol.expression.parse('foo-bar'); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse('foo-bar'); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 10, bar: 20})).to.be(-10); }); it('consumes whitespace as expected with -', function() { - var expr = ol.expression.parse(' foo- 10 '); - expect(expr).to.be.a(ol.expression.Math); + var expr = ol.expr.parse(' foo- 10 '); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 15})).to.be(5); }); @@ -242,44 +242,44 @@ describe('ol.expression.parse()', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.8 it('parses < operator', function() { - var expr = ol.expression.parse('foo operator', function() { - var expr = ol.expression.parse('foo>bar'); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse('foo>bar'); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 10, bar: 20})).to.be(false); expect(expr.evaluate({foo: 100, bar: 20})).to.be(true); }); it('consumes whitespace as expected with >', function() { - var expr = ol.expression.parse(' foo> 10 '); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse(' foo> 10 '); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 15})).to.be(true); expect(expr.evaluate({foo: 5})).to.be(false); }); it('parses <= operator', function() { - var expr = ol.expression.parse('foo<=bar'); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse('foo<=bar'); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 10, bar: 20})).to.be(true); expect(expr.evaluate({foo: 100, bar: 20})).to.be(false); expect(expr.evaluate({foo: 20, bar: 20})).to.be(true); }); it('consumes whitespace as expected with <=', function() { - var expr = ol.expression.parse(' foo<= 10 '); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse(' foo<= 10 '); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 15})).to.be(false); expect(expr.evaluate({foo: 5})).to.be(true); expect(expr.evaluate({foo: 10})).to.be(true); @@ -287,9 +287,9 @@ describe('ol.expression.parse()', function() { it('throws for invalid spacing with <=', function() { expect(function() { - ol.expression.parse(' foo< = 10 '); + ol.expr.parse(' foo< = 10 '); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('='); expect(token.index).to.be(6); @@ -297,16 +297,16 @@ describe('ol.expression.parse()', function() { }); it('parses >= operator', function() { - var expr = ol.expression.parse('foo>=bar'); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse('foo>=bar'); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 10, bar: 20})).to.be(false); expect(expr.evaluate({foo: 100, bar: 20})).to.be(true); expect(expr.evaluate({foo: 20, bar: 20})).to.be(true); }); it('consumes whitespace as expected with >=', function() { - var expr = ol.expression.parse(' foo >=10 '); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse(' foo >=10 '); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 15})).to.be(true); expect(expr.evaluate({foo: 5})).to.be(false); expect(expr.evaluate({foo: 10})).to.be(true); @@ -314,9 +314,9 @@ describe('ol.expression.parse()', function() { it('throws for invalid spacing with >=', function() { expect(function() { - ol.expression.parse(' 10 > =foo '); + ol.expr.parse(' 10 > =foo '); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('='); expect(token.index).to.be(6); @@ -329,16 +329,16 @@ describe('ol.expression.parse()', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.9 it('parses == operator', function() { - var expr = ol.expression.parse('foo==42'); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse('foo==42'); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 42})).to.be(true); expect(expr.evaluate({foo: 41})).to.be(false); expect(expr.evaluate({foo: '42'})).to.be(true); }); it('consumes whitespace as expected with ==', function() { - var expr = ol.expression.parse(' 42 ==foo '); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse(' 42 ==foo '); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 42})).to.be(true); expect(expr.evaluate({foo: 41})).to.be(false); expect(expr.evaluate({foo: '42'})).to.be(true); @@ -346,9 +346,9 @@ describe('ol.expression.parse()', function() { it('throws for invalid spacing with ==', function() { expect(function() { - ol.expression.parse(' 10 = =foo '); + ol.expr.parse(' 10 = =foo '); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('='); expect(token.index).to.be(4); @@ -356,16 +356,16 @@ describe('ol.expression.parse()', function() { }); it('parses != operator', function() { - var expr = ol.expression.parse('foo!=42'); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse('foo!=42'); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 42})).to.be(false); expect(expr.evaluate({foo: 41})).to.be(true); expect(expr.evaluate({foo: '42'})).to.be(false); }); it('consumes whitespace as expected with !=', function() { - var expr = ol.expression.parse(' 42 !=foo '); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse(' 42 !=foo '); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 42})).to.be(false); expect(expr.evaluate({foo: 41})).to.be(true); expect(expr.evaluate({foo: '42'})).to.be(false); @@ -373,9 +373,9 @@ describe('ol.expression.parse()', function() { it('throws for invalid spacing with !=', function() { expect(function() { - ol.expression.parse(' 10! =foo '); + ol.expr.parse(' 10! =foo '); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('!'); expect(token.index).to.be(3); @@ -383,16 +383,16 @@ describe('ol.expression.parse()', function() { }); it('parses === operator', function() { - var expr = ol.expression.parse('42===foo'); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse('42===foo'); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 42})).to.be(true); expect(expr.evaluate({foo: 41})).to.be(false); expect(expr.evaluate({foo: '42'})).to.be(false); }); it('consumes whitespace as expected with ===', function() { - var expr = ol.expression.parse(' foo ===42 '); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse(' foo ===42 '); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 42})).to.be(true); expect(expr.evaluate({foo: 41})).to.be(false); expect(expr.evaluate({foo: '42'})).to.be(false); @@ -400,9 +400,9 @@ describe('ol.expression.parse()', function() { it('throws for invalid spacing with ===', function() { expect(function() { - ol.expression.parse(' 10 = == foo '); + ol.expr.parse(' 10 = == foo '); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('='); expect(token.index).to.be(4); @@ -410,16 +410,16 @@ describe('ol.expression.parse()', function() { }); it('parses !== operator', function() { - var expr = ol.expression.parse('foo!==42'); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse('foo!==42'); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 42})).to.be(false); expect(expr.evaluate({foo: 41})).to.be(true); expect(expr.evaluate({foo: '42'})).to.be(true); }); it('consumes whitespace as expected with !==', function() { - var expr = ol.expression.parse(' 42 !== foo '); - expect(expr).to.be.a(ol.expression.Comparison); + var expr = ol.expr.parse(' 42 !== foo '); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate({foo: 42})).to.be(false); expect(expr.evaluate({foo: 41})).to.be(true); expect(expr.evaluate({foo: '42'})).to.be(true); @@ -427,9 +427,9 @@ describe('ol.expression.parse()', function() { it('throws for invalid spacing with !==', function() { expect(function() { - ol.expression.parse(' 10 != = foo '); + ol.expr.parse(' 10 != = foo '); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('='); expect(token.index).to.be(7); @@ -446,8 +446,8 @@ describe('ol.expression.parse()', function() { // http://www.ecma-international.org/ecma-262/5.1/#sec-11.11 it('parses && operator', function() { - var expr = ol.expression.parse('foo&&bar'); - expect(expr).to.be.a(ol.expression.Logical); + var expr = ol.expr.parse('foo&&bar'); + expect(expr).to.be.a(ol.expr.Logical); expect(expr.evaluate({foo: true, bar: true})).to.be(true); expect(expr.evaluate({foo: true, bar: false})).to.be(false); expect(expr.evaluate({foo: false, bar: true})).to.be(false); @@ -455,8 +455,8 @@ describe('ol.expression.parse()', function() { }); it('consumes space as expected with &&', function() { - var expr = ol.expression.parse(' foo && bar '); - expect(expr).to.be.a(ol.expression.Logical); + var expr = ol.expr.parse(' foo && bar '); + expect(expr).to.be.a(ol.expr.Logical); expect(expr.evaluate({foo: true, bar: true})).to.be(true); expect(expr.evaluate({foo: true, bar: false})).to.be(false); expect(expr.evaluate({foo: false, bar: true})).to.be(false); @@ -465,9 +465,9 @@ describe('ol.expression.parse()', function() { it('throws for invalid spacing with &&', function() { expect(function() { - ol.expression.parse('true & & false'); + ol.expr.parse('true & & false'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('&'); expect(token.index).to.be(5); @@ -475,8 +475,8 @@ describe('ol.expression.parse()', function() { }); it('parses || operator', function() { - var expr = ol.expression.parse('foo||bar'); - expect(expr).to.be.a(ol.expression.Logical); + var expr = ol.expr.parse('foo||bar'); + expect(expr).to.be.a(ol.expr.Logical); expect(expr.evaluate({foo: true, bar: true})).to.be(true); expect(expr.evaluate({foo: true, bar: false})).to.be(true); expect(expr.evaluate({foo: false, bar: true})).to.be(true); @@ -484,8 +484,8 @@ describe('ol.expression.parse()', function() { }); it('consumes space as expected with ||', function() { - var expr = ol.expression.parse(' foo || bar '); - expect(expr).to.be.a(ol.expression.Logical); + var expr = ol.expr.parse(' foo || bar '); + expect(expr).to.be.a(ol.expr.Logical); expect(expr.evaluate({foo: true, bar: true})).to.be(true); expect(expr.evaluate({foo: true, bar: false})).to.be(true); expect(expr.evaluate({foo: false, bar: true})).to.be(true); @@ -494,9 +494,9 @@ describe('ol.expression.parse()', function() { it('throws for invalid spacing with ||', function() { expect(function() { - ol.expression.parse('true | | false'); + ol.expr.parse('true | | false'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('|'); expect(token.index).to.be(5); @@ -522,10 +522,10 @@ describe('ol.expression.parse()', function() { }); -describe('ol.expression.lib', function() { +describe('ol.expr.lib', function() { - var parse = ol.expression.parse; - var evaluate = ol.expression.evaluateFeature; + var parse = ol.expr.parse; + var evaluate = ol.expr.evaluateFeature; describe('extent()', function() { @@ -611,48 +611,50 @@ describe('ol.expression.lib', function() { }); -describe('ol.expression.register()', function() { +describe('ol.expr.register()', function() { var spy; beforeEach(function() { spy = sinon.spy(); }); - it('registers custom functions in ol.expression.lib', function() { - ol.expression.register('someFunc', spy); - expect(ol.expression.lib.someFunc).to.be(spy); + it('registers custom functions in ol.expr.lib', function() { + ol.expr.register('someFunc', spy); + expect(ol.expr.lib.someFunc).to.be(spy); }); it('allows custom functions to be called', function() { - ol.expression.register('myFunc', spy); - var expr = ol.expression.parse('myFunc(42)'); - expr.evaluate(null, ol.expression.lib); + ol.expr.register('myFunc', spy); + var expr = ol.expr.parse('myFunc(42)'); + expr.evaluate(null, ol.expr.lib); expect(spy.calledOnce); expect(spy.calledWithExactly(42)); }); it('allows custom functions to be called with identifiers', function() { - ol.expression.register('myFunc', spy); - var expr = ol.expression.parse('myFunc(foo, 42)'); - expr.evaluate({foo: 'bar'}, ol.expression.lib); + ol.expr.register('myFunc', spy); + var expr = ol.expr.parse('myFunc(foo, 42)'); + expr.evaluate({foo: 'bar'}, ol.expr.lib); expect(spy.calledOnce); expect(spy.calledWithExactly('bar', 42)); }); it('allows custom functions to be called with custom this obj', function() { - ol.expression.register('myFunc', spy); - var expr = ol.expression.parse('myFunc(foo, 42)'); + ol.expr.register('myFunc', spy); + var expr = ol.expr.parse('myFunc(foo, 42)'); var that = {}; - expr.evaluate({foo: 'bar'}, ol.expression.lib, that); + expr.evaluate({foo: 'bar'}, ol.expr.lib, that); expect(spy.calledOnce); expect(spy.calledWithExactly('bar', 42)); expect(spy.calledOn(that)); }); - it('allows overriding existing ol.expression.lib functions', function() { - expect(ol.expression.lib.extent).not.to.be(spy); - ol.expression.register('extent', spy); - expect(ol.expression.lib.extent).to.be(spy); + it('allows overriding existing ol.expr.lib functions', function() { + var orig = ol.expr.lib.extent; + expect(orig).not.to.be(spy); + ol.expr.register('extent', spy); + expect(ol.expr.lib.extent).to.be(spy); + ol.expr.lib.extent = orig; }); }); @@ -660,18 +662,18 @@ describe('ol.expression.register()', function() { goog.require('ol.Feature'); -goog.require('ol.expression'); -goog.require('ol.expression.Call'); -goog.require('ol.expression.Comparison'); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Identifier'); -goog.require('ol.expression.Literal'); -goog.require('ol.expression.Logical'); -goog.require('ol.expression.Math'); -goog.require('ol.expression.Member'); -goog.require('ol.expression.Not'); -goog.require('ol.expression.TokenType'); -goog.require('ol.expression.UnexpectedToken'); +goog.require('ol.expr'); +goog.require('ol.expr.Call'); +goog.require('ol.expr.Comparison'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Identifier'); +goog.require('ol.expr.Literal'); +goog.require('ol.expr.Logical'); +goog.require('ol.expr.Math'); +goog.require('ol.expr.Member'); +goog.require('ol.expr.Not'); +goog.require('ol.expr.TokenType'); +goog.require('ol.expr.UnexpectedToken'); goog.require('ol.geom.LineString'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); diff --git a/test/spec/ol/expr/expressions.test.js b/test/spec/ol/expr/expressions.test.js new file mode 100644 index 0000000000..c64ea7cd52 --- /dev/null +++ b/test/spec/ol/expr/expressions.test.js @@ -0,0 +1,638 @@ +goog.provide('ol.test.expression.Expression'); + + +describe('ol.expr.Call', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expr.Call( + new ol.expr.Identifier('sqrt'), + [new ol.expr.Literal(42)]); + expect(expr).to.be.a(ol.expr.Expression); + expect(expr).to.be.a(ol.expr.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.expr.Call( + new ol.expr.Identifier('sqrt'), + [new ol.expr.Literal(42)]); + expect(expr.evaluate(fns)).to.be(Math.sqrt(42)); + }); + + it('accepts a separate scope for functions', function() { + var expr = new ol.expr.Call( + new ol.expr.Identifier('sqrt'), + [new ol.expr.Identifier('foo')]); + expect(expr.evaluate({foo: 42}, fns)).to.be(Math.sqrt(42)); + }); + + it('accepts multiple expression arguments', function() { + var expr = new ol.expr.Call( + new ol.expr.Identifier('strConcat'), + [ + new ol.expr.Identifier('foo'), + new ol.expr.Literal(' comes after '), + new ol.expr.Math( + ol.expr.MathOp.SUBTRACT, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(1)) + ]); + expect(expr.evaluate({foo: 42}, fns)).to.be('42 comes after 41'); + }); + + it('accepts optional this arg', function() { + var expr = new ol.expr.Call( + new ol.expr.Identifier('discouraged'), []); + + var thisArg = { + message: 'avoid this' + }; + + expect(expr.evaluate(fns, null, thisArg)).to.be('avoid this'); + }); + + }); + + var callee = new ol.expr.Identifier('sqrt'); + var args = [new ol.expr.Literal(42)]; + var expr = new ol.expr.Call(callee, args); + + describe('#getArgs()', function() { + it('gets the callee expression', function() { + expect(expr.getArgs()).to.be(args); + }); + }); + + describe('#getCallee()', function() { + it('gets the callee expression', function() { + expect(expr.getCallee()).to.be(callee); + }); + }); + +}); + + +describe('ol.expr.Comparison', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.EQ, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + expect(expr).to.be.a(ol.expr.Expression); + expect(expr).to.be.a(ol.expr.Comparison); + }); + }); + + describe('#evaluate()', function() { + it('compares with ==', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.EQ, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: '42'})).to.be(true); + expect(expr.evaluate({foo: true})).to.be(false); + expect(expr.evaluate({bar: true})).to.be(false); + }); + + it('compares with !=', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.NEQ, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: '42'})).to.be(false); + expect(expr.evaluate({foo: true})).to.be(true); + expect(expr.evaluate({bar: true})).to.be(true); + }); + + it('compares with ===', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.STRICT_EQ, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: '42'})).to.be(false); + expect(expr.evaluate({foo: true})).to.be(false); + expect(expr.evaluate({bar: true})).to.be(false); + }); + + it('compares with !==', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.STRICT_NEQ, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: '42'})).to.be(true); + expect(expr.evaluate({foo: true})).to.be(true); + expect(expr.evaluate({bar: true})).to.be(true); + }); + + it('compares with >', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.GT, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: 41})).to.be(false); + expect(expr.evaluate({foo: 43})).to.be(true); + }); + + it('compares with <', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.LT, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(false); + expect(expr.evaluate({foo: 41})).to.be(true); + expect(expr.evaluate({foo: 43})).to.be(false); + }); + + it('compares with >=', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.GTE, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: 41})).to.be(false); + expect(expr.evaluate({foo: 43})).to.be(true); + }); + + it('compares with <=', function() { + var expr = new ol.expr.Comparison( + ol.expr.ComparisonOp.LTE, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(42)); + + expect(expr.evaluate({foo: 42})).to.be(true); + expect(expr.evaluate({foo: 41})).to.be(true); + expect(expr.evaluate({foo: 43})).to.be(false); + }); + }); + + describe('#isValidOp()', function() { + it('determines if a string is a valid operator', function() { + expect(ol.expr.Comparison.isValidOp('<')).to.be(true); + expect(ol.expr.Comparison.isValidOp('<')).to.be(true); + expect(ol.expr.Comparison.isValidOp('<=')).to.be(true); + expect(ol.expr.Comparison.isValidOp('<=')).to.be(true); + expect(ol.expr.Comparison.isValidOp('==')).to.be(true); + expect(ol.expr.Comparison.isValidOp('!=')).to.be(true); + expect(ol.expr.Comparison.isValidOp('===')).to.be(true); + expect(ol.expr.Comparison.isValidOp('!==')).to.be(true); + + expect(ol.expr.Comparison.isValidOp('')).to.be(false); + expect(ol.expr.Comparison.isValidOp('+')).to.be(false); + expect(ol.expr.Comparison.isValidOp('-')).to.be(false); + expect(ol.expr.Comparison.isValidOp('&&')).to.be(false); + }); + }); + + var op = ol.expr.ComparisonOp.LTE; + var left = new ol.expr.Identifier('foo'); + var right = new ol.expr.Literal(42); + var expr = new ol.expr.Comparison(op, left, right); + + describe('#getOperator()', function() { + it('gets the operator', function() { + expect(expr.getOperator()).to.be(op); + }); + }); + + describe('#getLeft()', function() { + it('gets the left expression', function() { + expect(expr.getLeft()).to.be(left); + }); + }); + + describe('#getRight()', function() { + it('gets the right expression', function() { + expect(expr.getRight()).to.be(right); + }); + }); + +}); + +describe('ol.expr.Identifier', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expr.Identifier('foo'); + expect(expr).to.be.a(ol.expr.Expression); + expect(expr).to.be.a(ol.expr.Identifier); + }); + }); + + describe('#evaluate()', function() { + it('returns a number from the scope', function() { + var expr = new ol.expr.Identifier('foo'); + expect(expr.evaluate({foo: 42})).to.be(42); + }); + + it('returns a string from the scope', function() { + var expr = new ol.expr.Identifier('foo'); + expect(expr.evaluate({foo: 'chicken'})).to.be('chicken'); + }); + + it('returns a boolean from the scope', function() { + var expr = new ol.expr.Identifier('bar'); + expect(expr.evaluate({bar: false})).to.be(false); + expect(expr.evaluate({bar: true})).to.be(true); + }); + + it('returns a null from the scope', function() { + var expr = new ol.expr.Identifier('nada'); + expect(expr.evaluate({nada: null})).to.be(null); + }); + + it('works for unicode identifiers', function() { + var expr = new ol.expr.Identifier('\u03c0'); + expect(expr.evaluate({'\u03c0': Math.PI})).to.be(Math.PI); + }); + }); + + describe('#getName()', function() { + var expr = new ol.expr.Identifier('asdf'); + expect(expr.getName()).to.be('asdf'); + }); + +}); + +describe('ol.expr.Literal', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expr.Literal(true); + expect(expr).to.be.a(ol.expr.Expression); + expect(expr).to.be.a(ol.expr.Literal); + }); + }); + + describe('#evaluate()', function() { + it('works for numeric literal', function() { + var expr = new ol.expr.Literal(42e-11); + expect(expr.evaluate()).to.be(4.2e-10); + }); + + it('works for string literal', function() { + var expr = new ol.expr.Literal('asdf'); + expect(expr.evaluate()).to.be('asdf'); + }); + + it('works for boolean literal', function() { + var expr = new ol.expr.Literal(true); + expect(expr.evaluate()).to.be(true); + }); + + it('works for null literal', function() { + var expr = new ol.expr.Literal(null); + expect(expr.evaluate()).to.be(null); + }); + }); + + describe('#getValue()', function() { + var expr = new ol.expr.Literal('asdf'); + expect(expr.getValue()).to.be('asdf'); + }); + +}); + + +describe('ol.expr.Logical', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expr.Logical( + ol.expr.LogicalOp.OR, + new ol.expr.Identifier('foo'), + new ol.expr.Identifier('bar')); + expect(expr).to.be.a(ol.expr.Expression); + expect(expr).to.be.a(ol.expr.Logical); + }); + }); + + describe('#evaluate()', function() { + it('applies || to resolved identifiers', function() { + var expr = new ol.expr.Logical( + ol.expr.LogicalOp.OR, + new ol.expr.Identifier('foo'), + new ol.expr.Identifier('bar')); + + expect(expr.evaluate({foo: true, bar: true})).to.be(true); + expect(expr.evaluate({foo: true, bar: false})).to.be(true); + expect(expr.evaluate({foo: false, bar: true})).to.be(true); + expect(expr.evaluate({foo: false, bar: false})).to.be(false); + }); + + it('applies && to resolved identifiers', function() { + var expr = new ol.expr.Logical( + ol.expr.LogicalOp.AND, + new ol.expr.Identifier('foo'), + new ol.expr.Identifier('bar')); + + expect(expr.evaluate({foo: true, bar: true})).to.be(true); + expect(expr.evaluate({foo: true, bar: false})).to.be(false); + expect(expr.evaluate({foo: false, bar: true})).to.be(false); + expect(expr.evaluate({foo: false, bar: false})).to.be(false); + }); + }); + + describe('#isValidOp()', function() { + it('determines if a string is a valid operator', function() { + expect(ol.expr.Logical.isValidOp('||')).to.be(true); + expect(ol.expr.Logical.isValidOp('&&')).to.be(true); + + expect(ol.expr.Logical.isValidOp('')).to.be(false); + expect(ol.expr.Logical.isValidOp('+')).to.be(false); + expect(ol.expr.Logical.isValidOp('<')).to.be(false); + expect(ol.expr.Logical.isValidOp('|')).to.be(false); + }); + }); + + var op = ol.expr.LogicalOp.AND; + var left = new ol.expr.Identifier('foo'); + var right = new ol.expr.Literal(false); + var expr = new ol.expr.Logical(op, left, right); + + describe('#getOperator()', function() { + it('gets the operator', function() { + expect(expr.getOperator()).to.be(op); + }); + }); + + describe('#getLeft()', function() { + it('gets the left expression', function() { + expect(expr.getLeft()).to.be(left); + }); + }); + + describe('#getRight()', function() { + it('gets the right expression', function() { + expect(expr.getRight()).to.be(right); + }); + }); + +}); + +describe('ol.expr.Math', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expr.Math( + ol.expr.MathOp.ADD, + new ol.expr.Literal(40), + new ol.expr.Literal(2)); + expect(expr).to.be.a(ol.expr.Expression); + expect(expr).to.be.a(ol.expr.Math); + }); + }); + + describe('#evaluate()', function() { + it('does + with numeric literal', function() { + var expr = new ol.expr.Math( + ol.expr.MathOp.ADD, + new ol.expr.Literal(40), + new ol.expr.Literal(2)); + + expect(expr.evaluate()).to.be(42); + }); + + it('does + with string literal (note: subject to change)', function() { + var expr = new ol.expr.Math( + ol.expr.MathOp.ADD, + new ol.expr.Literal('foo'), + new ol.expr.Literal('bar')); + + expect(expr.evaluate()).to.be('foobar'); + }); + + it('does + with identifiers', function() { + var expr = new ol.expr.Math( + ol.expr.MathOp.ADD, + new ol.expr.Identifier('foo'), + new ol.expr.Identifier('bar')); + + expect(expr.evaluate({foo: 40, bar: 2})).to.be(42); + }); + + it('does - with identifiers', function() { + var expr = new ol.expr.Math( + ol.expr.MathOp.SUBTRACT, + new ol.expr.Identifier('foo'), + new ol.expr.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.expr.Math( + ol.expr.MathOp.SUBTRACT, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(2)); + + expect(expr.evaluate({foo: '40'})).to.be(38); + }); + + it('does * with identifiers', function() { + var expr = new ol.expr.Math( + ol.expr.MathOp.MULTIPLY, + new ol.expr.Literal(2), + new ol.expr.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.expr.Math( + ol.expr.MathOp.MULTIPLY, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(2)); + + expect(expr.evaluate({foo: '21'})).to.be(42); + }); + + it('does % with identifiers', function() { + var expr = new ol.expr.Math( + ol.expr.MathOp.MOD, + new ol.expr.Literal(97), + new ol.expr.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.expr.Math( + ol.expr.MathOp.MOD, + new ol.expr.Identifier('foo'), + new ol.expr.Literal(100)); + + expect(expr.evaluate({foo: '150'})).to.be(50); + }); + }); + + describe('#isValidOp()', function() { + it('determines if a string is a valid operator', function() { + expect(ol.expr.Math.isValidOp('+')).to.be(true); + expect(ol.expr.Math.isValidOp('-')).to.be(true); + expect(ol.expr.Math.isValidOp('*')).to.be(true); + expect(ol.expr.Math.isValidOp('/')).to.be(true); + expect(ol.expr.Math.isValidOp('%')).to.be(true); + + expect(ol.expr.Math.isValidOp('')).to.be(false); + expect(ol.expr.Math.isValidOp('|')).to.be(false); + expect(ol.expr.Math.isValidOp('&')).to.be(false); + expect(ol.expr.Math.isValidOp('<')).to.be(false); + expect(ol.expr.Math.isValidOp('||')).to.be(false); + expect(ol.expr.Math.isValidOp('.')).to.be(false); + }); + }); + + var op = ol.expr.MathOp.MOD; + var left = new ol.expr.Identifier('foo'); + var right = new ol.expr.Literal(20); + var expr = new ol.expr.Math(op, left, right); + + describe('#getOperator()', function() { + it('gets the operator', function() { + expect(expr.getOperator()).to.be(op); + }); + }); + + describe('#getLeft()', function() { + it('gets the left expression', function() { + expect(expr.getLeft()).to.be(left); + }); + }); + + describe('#getRight()', function() { + it('gets the right expression', function() { + expect(expr.getRight()).to.be(right); + }); + }); + +}); + +describe('ol.expr.Member', function() { + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expr.Member( + new ol.expr.Identifier('foo'), + new ol.expr.Identifier('bar')); + + expect(expr).to.be.a(ol.expr.Expression); + expect(expr).to.be.a(ol.expr.Member); + }); + }); + + describe('#evaluate()', function() { + it('accesses an object property', function() { + + var expr = new ol.expr.Member( + new ol.expr.Identifier('foo'), + new ol.expr.Identifier('bar')); + + var scope = {foo: {bar: 42}}; + expect(expr.evaluate(scope)).to.be(42); + }); + }); + + var object = new ol.expr.Identifier('foo'); + var property = new ol.expr.Identifier('bar'); + var expr = new ol.expr.Member(object, property); + + describe('#getObject()', function() { + expect(expr.getObject()).to.be(object); + }); + + describe('#getProperty()', function() { + expect(expr.getProperty()).to.be(property); + }); + +}); + + +describe('ol.expr.Not', function() { + + describe('constructor', function() { + it('creates a new expression', function() { + var expr = new ol.expr.Not( + new ol.expr.Literal(true)); + expect(expr).to.be.a(ol.expr.Expression); + expect(expr).to.be.a(ol.expr.Not); + }); + }); + + describe('#evaluate()', function() { + it('returns the logical complement', function() { + var expr = new ol.expr.Not(new ol.expr.Literal(true)); + expect(expr.evaluate()).to.be(false); + + expr = new ol.expr.Not(new ol.expr.Literal(false)); + expect(expr.evaluate()).to.be(true); + }); + + it('negates a truthy string', function() { + var expr = new ol.expr.Not(new ol.expr.Literal('asdf')); + expect(expr.evaluate()).to.be(false); + }); + + it('negates a falsy string', function() { + var expr = new ol.expr.Not(new ol.expr.Literal('')); + expect(expr.evaluate()).to.be(true); + }); + + it('negates a truthy number', function() { + var expr = new ol.expr.Not(new ol.expr.Literal(42)); + expect(expr.evaluate()).to.be(false); + }); + + it('negates a falsy number', function() { + var expr = new ol.expr.Not(new ol.expr.Literal(NaN)); + expect(expr.evaluate()).to.be(true); + }); + }); + + describe('#getArgument()', function() { + var argument = new ol.expr.Literal(true); + var expr = new ol.expr.Not(argument); + expect(expr.getArgument()).to.be(argument); + }); + +}); + + +goog.require('ol.expr.Call'); +goog.require('ol.expr.Comparison'); +goog.require('ol.expr.ComparisonOp'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Identifier'); +goog.require('ol.expr.Literal'); +goog.require('ol.expr.Logical'); +goog.require('ol.expr.LogicalOp'); +goog.require('ol.expr.Math'); +goog.require('ol.expr.MathOp'); +goog.require('ol.expr.Member'); +goog.require('ol.expr.Not'); diff --git a/test/spec/ol/expression/lexer.test.js b/test/spec/ol/expr/lexer.test.js similarity index 67% rename from test/spec/ol/expression/lexer.test.js rename to test/spec/ol/expr/lexer.test.js index 828a58726c..2bf0b86b76 100644 --- a/test/spec/ol/expression/lexer.test.js +++ b/test/spec/ol/expr/lexer.test.js @@ -1,11 +1,11 @@ goog.provide('ol.test.expression.Lexer'); -describe('ol.expression.Lexer', function() { +describe('ol.expr.Lexer', function() { describe('constructor', function() { it('creates a new lexer', function() { - var lexer = new ol.expression.Lexer('foo'); - expect(lexer).to.be.a(ol.expression.Lexer); + var lexer = new ol.expr.Lexer('foo'); + expect(lexer).to.be.a(ol.expr.Lexer); }); }); @@ -13,30 +13,30 @@ describe('ol.expression.Lexer', function() { it('returns one token at a time', function() { var source = 'foo === "bar"'; - var lexer = new ol.expression.Lexer(source); + var lexer = new ol.expr.Lexer(source); // scan first token var token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); expect(token.value).to.be('foo'); // scan second token token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); expect(token.value).to.be('==='); // scan third token token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); expect(token.value).to.be('bar'); // scan again token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.EOF); + expect(token.type).to.be(ol.expr.TokenType.EOF); // and again token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.EOF); + expect(token.type).to.be(ol.expr.TokenType.EOF); }); }); @@ -45,45 +45,45 @@ describe('ol.expression.Lexer', function() { var lexer; beforeEach(function() { - lexer = new ol.expression.Lexer('foo > 42 && bar == "chicken"'); + lexer = new ol.expr.Lexer('foo > 42 && bar == "chicken"'); }); it('looks ahead without consuming token', function() { var token = lexer.peek(); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); expect(token.value).to.be('foo'); token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); expect(token.value).to.be('foo'); }); it('works after a couple scans', function() { var token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); expect(token.value).to.be('foo'); token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); expect(token.value).to.be('>'); token = lexer.peek(); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); expect(token.value).to.be(42); token = lexer.next(); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); expect(token.value).to.be(42); }); it('returns the same thing when called multiple times', function() { var token = lexer.peek(); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); expect(token.value).to.be('foo'); for (var i = 0; i < 10; ++i) { token = lexer.peek(); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); expect(token.value).to.be('foo'); } }); @@ -94,11 +94,11 @@ describe('ol.expression.Lexer', function() { describe('#scanIdentifier_()', function() { function scan(source, part) { - var lexer = new ol.expression.Lexer(source); + var lexer = new ol.expr.Lexer(source); var token = lexer.scanIdentifier_(lexer.getCurrentCharCode_()); if (!part) { expect(token.index).to.be(0); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); } return token; } @@ -106,61 +106,61 @@ describe('ol.expression.Lexer', function() { it('works for short identifiers', function() { var token = scan('a'); expect(token.value).to.be('a'); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); }); it('works for longer identifiers', function() { var token = scan('foo'); expect(token.value).to.be('foo'); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); }); it('works for $ anywhere', function() { var token = scan('$foo$bar$'); expect(token.value).to.be('$foo$bar$'); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); }); it('works for _ anywhere', function() { var token = scan('_foo_bar_'); expect(token.value).to.be('_foo_bar_'); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); }); it('works for keywords', function() { var token = scan('delete'); expect(token.value).to.be('delete'); - expect(token.type).to.be(ol.expression.TokenType.KEYWORD); + expect(token.type).to.be(ol.expr.TokenType.KEYWORD); }); it('works for null', function() { var token = scan('null'); expect(token.value).to.be('null'); - expect(token.type).to.be(ol.expression.TokenType.NULL_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NULL_LITERAL); }); it('works for boolean true', function() { var token = scan('true'); expect(token.value).to.be('true'); - expect(token.type).to.be(ol.expression.TokenType.BOOLEAN_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.BOOLEAN_LITERAL); }); it('works for boolean false', function() { var token = scan('false'); expect(token.value).to.be('false'); - expect(token.type).to.be(ol.expression.TokenType.BOOLEAN_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.BOOLEAN_LITERAL); }); it('works with unicode escape sequences', function() { var token = scan('\u006f\u006c\u0033'); expect(token.value).to.be('ol3'); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); }); it('works with hex escape sequences', function() { var token = scan('\x6f\x6c\x33'); expect(token.value).to.be('ol3'); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); }); it('throws for identifiers starting with a number', function() { @@ -178,7 +178,7 @@ describe('ol.expression.Lexer', function() { it('only scans valid identifier part', function() { var token = scan('foo>bar', true); expect(token.value).to.be('foo'); - expect(token.type).to.be(ol.expression.TokenType.IDENTIFIER); + expect(token.type).to.be(ol.expr.TokenType.IDENTIFIER); }); }); @@ -186,24 +186,24 @@ describe('ol.expression.Lexer', function() { describe('#scanNumericLiteral_()', function() { function scan(source) { - var lexer = new ol.expression.Lexer(source); + var lexer = new ol.expr.Lexer(source); var token = lexer.scanNumericLiteral_(lexer.getCurrentCharCode_()); expect(token.index).to.be(0); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return token; } it('works for integers', function() { var token = scan('123'); expect(token.value).to.be(123); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); }); it('throws for bogus integer', function() { expect(function() { scan('123z'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('z'); expect(token.index).to.be(3); @@ -213,14 +213,14 @@ describe('ol.expression.Lexer', function() { it('works for float', function() { var token = scan('123.456'); expect(token.value).to.be(123.456); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); }); it('throws for bogus float', function() { expect(function() { scan('123.4x4'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('x'); expect(token.index).to.be(5); @@ -230,26 +230,26 @@ describe('ol.expression.Lexer', function() { it('works with exponent', function() { var token = scan('1.234e5'); expect(token.value).to.be(1.234e5); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); }); it('works with explicit positive exponent', function() { var token = scan('1.234e+5'); expect(token.value).to.be(1.234e5); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); }); it('works with negative exponent', function() { var token = scan('1.234e-5'); expect(token.value).to.be(1.234e-5); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); }); it('throws for bogus float', function() { expect(function() { scan('1.234eo4'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('o'); expect(token.index).to.be(6); @@ -259,7 +259,7 @@ describe('ol.expression.Lexer', function() { it('works with octals', function() { var token = scan('02322'); expect(token.value).to.be(1234); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); }); it('throws for bogus octal', function() { @@ -267,7 +267,7 @@ describe('ol.expression.Lexer', function() { expect(function() { scan('02392'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('9'); expect(token.index).to.be(3); @@ -277,7 +277,7 @@ describe('ol.expression.Lexer', function() { it('works with hex', function() { var token = scan('0x4d2'); expect(token.value).to.be(1234); - expect(token.type).to.be(ol.expression.TokenType.NUMERIC_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.NUMERIC_LITERAL); }); it('throws for bogus hex', function() { @@ -285,7 +285,7 @@ describe('ol.expression.Lexer', function() { expect(function() { scan('0x4G'); }).throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; expect(token.value).to.be('G'); expect(token.index).to.be(3); @@ -297,89 +297,89 @@ describe('ol.expression.Lexer', function() { describe('#scanPunctuator_()', function() { function scan(source) { - var lexer = new ol.expression.Lexer(source); + var lexer = new ol.expr.Lexer(source); var token = lexer.scanPunctuator_(lexer.getCurrentCharCode_()); expect(token.index).to.be(0); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return token; } it('works for dot', function() { var token = scan('.'); expect(token.value).to.be('.'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for bang', function() { var token = scan('!'); expect(token.value).to.be('!'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for double equal', function() { var token = scan('=='); expect(token.value).to.be('=='); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for triple equal', function() { var token = scan('==='); expect(token.value).to.be('==='); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for not double equal', function() { var token = scan('!='); expect(token.value).to.be('!='); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for not triple equal', function() { var token = scan('!=='); expect(token.value).to.be('!=='); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for logical or', function() { var token = scan('||'); expect(token.value).to.be('||'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for logical and', function() { var token = scan('&&'); expect(token.value).to.be('&&'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for plus', function() { var token = scan('+'); expect(token.value).to.be('+'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for minus', function() { var token = scan('-'); expect(token.value).to.be('-'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for star', function() { var token = scan('*'); expect(token.value).to.be('*'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for slash', function() { var token = scan('/'); expect(token.value).to.be('/'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); it('works for percent', function() { var token = scan('%'); expect(token.value).to.be('%'); - expect(token.type).to.be(ol.expression.TokenType.PUNCTUATOR); + expect(token.type).to.be(ol.expr.TokenType.PUNCTUATOR); }); }); @@ -387,62 +387,62 @@ describe('ol.expression.Lexer', function() { describe('#scanStringLiteral_()', function() { function scan(source) { - var lexer = new ol.expression.Lexer(source); + var lexer = new ol.expr.Lexer(source); var token = lexer.scanStringLiteral_(lexer.getCurrentCharCode_()); expect(token.index).to.be(0); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return token; } it('parses double quoted string', function() { var token = scan('"my string"'); expect(token.value).to.be('my string'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses double quoted string with internal single quotes', function() { var token = scan('"my \'quoted\' string"'); expect(token.value).to.be('my \'quoted\' string'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses double quoted string with escaped double quotes', function() { var token = scan('"my \\"quoted\\" string"'); expect(token.value).to.be('my "quoted" string'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses double quoted string with escaped backslash', function() { var token = scan('"my \\\ string"'); expect(token.value).to.be('my \ string'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses double quoted string with unicode escape sequences', function() { var token = scan('"\u006f\u006c\u0033"'); expect(token.value).to.be('ol3'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses double quoted string with hex escape sequences', function() { var token = scan('"\x6f\x6c\x33"'); expect(token.value).to.be('ol3'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses double quoted string with tab', function() { var token = scan('"a\ttab"'); expect(token.value).to.be('a\ttab'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('throws on unterminated double quote', function() { expect(function() { scan('"never \'ending\' string'); }).to.throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; - expect(token.type).to.be(ol.expression.TokenType.EOF); + expect(token.type).to.be(ol.expr.TokenType.EOF); expect(token.index).to.be(22); }); }); @@ -450,52 +450,52 @@ describe('ol.expression.Lexer', function() { it('parses single quoted string', function() { var token = scan('\'my string\''); expect(token.value).to.be('my string'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses single quoted string with internal double quotes', function() { var token = scan('\'my "quoted" string\''); expect(token.value).to.be('my "quoted" string'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses single quoted string with escaped single quotes', function() { var token = scan('\'my \\\'quoted\\\' string\''); expect(token.value).to.be('my \'quoted\' string'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses single quoted string with escaped backslash', function() { var token = scan('\'my \\\ string\''); expect(token.value).to.be('my \ string'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses single quoted string with unicode escape sequences', function() { var token = scan('\'\u006f\u006c\u0033\''); expect(token.value).to.be('ol3'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses single quoted string with hex escape sequences', function() { var token = scan('\'\x6f\x6c\x33\''); expect(token.value).to.be('ol3'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('parses single quoted string with tab', function() { var token = scan('\'a\ttab\''); expect(token.value).to.be('a\ttab'); - expect(token.type).to.be(ol.expression.TokenType.STRING_LITERAL); + expect(token.type).to.be(ol.expr.TokenType.STRING_LITERAL); }); it('throws on unterminated single quote', function() { expect(function() { scan('\'never "ending" string'); }).to.throwException(function(err) { - expect(err).to.be.an(ol.expression.UnexpectedToken); + expect(err).to.be.an(ol.expr.UnexpectedToken); var token = err.token; - expect(token.type).to.be(ol.expression.TokenType.EOF); + expect(token.type).to.be(ol.expr.TokenType.EOF); expect(token.index).to.be(22); }); }); @@ -504,6 +504,6 @@ describe('ol.expression.Lexer', function() { }); -goog.require('ol.expression.Lexer'); -goog.require('ol.expression.TokenType'); -goog.require('ol.expression.UnexpectedToken'); +goog.require('ol.expr.Lexer'); +goog.require('ol.expr.TokenType'); +goog.require('ol.expr.UnexpectedToken'); diff --git a/test/spec/ol/expression/parser.test.js b/test/spec/ol/expr/parser.test.js similarity index 68% rename from test/spec/ol/expression/parser.test.js rename to test/spec/ol/expr/parser.test.js index d832dd60e6..ac0f640a46 100644 --- a/test/spec/ol/expression/parser.test.js +++ b/test/spec/ol/expr/parser.test.js @@ -1,21 +1,21 @@ goog.provide('ol.test.expression.Parser'); -describe('ol.expression.Parser', function() { +describe('ol.expr.Parser', function() { describe('constructor', function() { it('creates a new expression parser', function() { - var parser = new ol.expression.Parser(); - expect(parser).to.be.a(ol.expression.Parser); + var parser = new ol.expr.Parser(); + expect(parser).to.be.a(ol.expr.Parser); }); }); describe('#parseArguments_()', function() { function parse(source) { - var lexer = new ol.expression.Lexer(source); - var parser = new ol.expression.Parser(); + var lexer = new ol.expr.Lexer(source); + var parser = new ol.expr.Parser(); var expr = parser.parseArguments_(lexer); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return expr; } @@ -23,13 +23,13 @@ describe('ol.expression.Parser', function() { var args = parse('(1/3, "foo", true)'); expect(args).length(3); - expect(args[0]).to.be.a(ol.expression.Math); + expect(args[0]).to.be.a(ol.expr.Math); expect(args[0].evaluate()).to.be(1 / 3); - expect(args[1]).to.be.a(ol.expression.Literal); + expect(args[1]).to.be.a(ol.expr.Literal); expect(args[1].evaluate()).to.be('foo'); - expect(args[2]).to.be.a(ol.expression.Literal); + expect(args[2]).to.be.a(ol.expr.Literal); expect(args[2].evaluate()).to.be(true); }); @@ -50,16 +50,16 @@ describe('ol.expression.Parser', function() { describe('#parseBinaryExpression_()', function() { function parse(source) { - var lexer = new ol.expression.Lexer(source); - var parser = new ol.expression.Parser(); + var lexer = new ol.expr.Lexer(source); + var parser = new ol.expr.Parser(); var expr = parser.parseBinaryExpression_(lexer); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return expr; } it('works with multiplicitave operators', function() { var expr = parse('4 * 1e4'); - expect(expr).to.be.a(ol.expression.Math); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate()).to.be(40000); expect(parse('10/3').evaluate()).to.be(10 / 3); @@ -67,7 +67,7 @@ describe('ol.expression.Parser', function() { it('works with additive operators', function() { var expr = parse('4 +1e4'); - expect(expr).to.be.a(ol.expression.Math); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate()).to.be(10004); expect(parse('10-3').evaluate()).to.be(7); @@ -75,7 +75,7 @@ describe('ol.expression.Parser', function() { it('works with relational operators', function() { var expr = parse('4 < 1e4'); - expect(expr).to.be.a(ol.expression.Comparison); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate()).to.be(true); expect(parse('10<3').evaluate()).to.be(false); @@ -86,7 +86,7 @@ describe('ol.expression.Parser', function() { it('works with equality operators', function() { var expr = parse('4 == 1e4'); - expect(expr).to.be.a(ol.expression.Comparison); + expect(expr).to.be.a(ol.expr.Comparison); expect(expr.evaluate()).to.be(false); expect(parse('10!=3').evaluate()).to.be(true); @@ -97,7 +97,7 @@ describe('ol.expression.Parser', function() { it('works with binary logical operators', function() { var expr = parse('true && false'); - expect(expr).to.be.a(ol.expression.Logical); + expect(expr).to.be.a(ol.expr.Logical); expect(expr.evaluate()).to.be(false); expect(parse('false||true').evaluate()).to.be(true); @@ -125,16 +125,16 @@ describe('ol.expression.Parser', function() { describe('#parseGroupExpression_()', function() { function parse(source) { - var lexer = new ol.expression.Lexer(source); - var parser = new ol.expression.Parser(); + var lexer = new ol.expr.Lexer(source); + var parser = new ol.expr.Parser(); var expr = parser.parseGroupExpression_(lexer); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return expr; } it('parses grouped expressions', function() { var expr = parse('(3 * (foo + 2))'); - expect(expr).to.be.a(ol.expression.Math); + expect(expr).to.be.a(ol.expr.Math); expect(expr.evaluate({foo: 3})).to.be(15); }); @@ -143,16 +143,16 @@ describe('ol.expression.Parser', function() { describe('#parseLeftHandSideExpression_()', function() { function parse(source) { - var lexer = new ol.expression.Lexer(source); - var parser = new ol.expression.Parser(); + var lexer = new ol.expr.Lexer(source); + var parser = new ol.expr.Parser(); var expr = parser.parseLeftHandSideExpression_(lexer); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return expr; } it('parses member expressions', function() { var expr = parse('foo.bar.bam'); - expect(expr).to.be.a(ol.expression.Member); + expect(expr).to.be.a(ol.expr.Member); }); it('throws on invalid member expression', function() { @@ -163,7 +163,7 @@ describe('ol.expression.Parser', function() { it('parses call expressions', function() { var expr = parse('foo(bar)'); - expect(expr).to.be.a(ol.expression.Call); + expect(expr).to.be.a(ol.expr.Call); var fns = { foo: function(arg) { expect(arguments).length(1); @@ -189,34 +189,34 @@ describe('ol.expression.Parser', function() { describe('#parsePrimaryExpression_()', function() { function parse(source) { - var lexer = new ol.expression.Lexer(source); - var parser = new ol.expression.Parser(); + var lexer = new ol.expr.Lexer(source); + var parser = new ol.expr.Parser(); var expr = parser.parsePrimaryExpression_(lexer); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return expr; } it('parses string literal', function() { var expr = parse('"foo"'); - expect(expr).to.be.a(ol.expression.Literal); + expect(expr).to.be.a(ol.expr.Literal); expect(expr.evaluate()).to.be('foo'); }); it('parses numeric literal', function() { var expr = parse('.42e2'); - expect(expr).to.be.a(ol.expression.Literal); + expect(expr).to.be.a(ol.expr.Literal); expect(expr.evaluate()).to.be(42); }); it('parses boolean literal', function() { var expr = parse('.42e2'); - expect(expr).to.be.a(ol.expression.Literal); + expect(expr).to.be.a(ol.expr.Literal); expect(expr.evaluate()).to.be(42); }); it('parses null literal', function() { var expr = parse('null'); - expect(expr).to.be.a(ol.expression.Literal); + expect(expr).to.be.a(ol.expr.Literal); expect(expr.evaluate()).to.be(null); }); @@ -225,34 +225,34 @@ describe('ol.expression.Parser', function() { describe('#parseUnaryExpression_()', function() { function parse(source) { - var lexer = new ol.expression.Lexer(source); - var parser = new ol.expression.Parser(); + var lexer = new ol.expr.Lexer(source); + var parser = new ol.expr.Parser(); var expr = parser.parseUnaryExpression_(lexer); - expect(lexer.peek().type).to.be(ol.expression.TokenType.EOF); + expect(lexer.peek().type).to.be(ol.expr.TokenType.EOF); return expr; } it('parses logical not', function() { var expr = parse('!foo'); - expect(expr).to.be.a(ol.expression.Not); + expect(expr).to.be.a(ol.expr.Not); expect(expr.evaluate({foo: true})).to.be(false); }); it('works with string literal', function() { var expr = parse('!"foo"'); - expect(expr).to.be.a(ol.expression.Not); + expect(expr).to.be.a(ol.expr.Not); expect(expr.evaluate()).to.be(false); }); it('works with empty string', function() { var expr = parse('!""'); - expect(expr).to.be.a(ol.expression.Not); + expect(expr).to.be.a(ol.expr.Not); expect(expr.evaluate()).to.be(true); }); it('works with null', function() { var expr = parse('!null'); - expect(expr).to.be.a(ol.expression.Not); + expect(expr).to.be.a(ol.expr.Not); expect(expr.evaluate()).to.be(true); }); @@ -262,14 +262,14 @@ describe('ol.expression.Parser', function() { }); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Call'); -goog.require('ol.expression.Comparison'); -goog.require('ol.expression.Lexer'); -goog.require('ol.expression.Literal'); -goog.require('ol.expression.Logical'); -goog.require('ol.expression.Math'); -goog.require('ol.expression.Member'); -goog.require('ol.expression.Not'); -goog.require('ol.expression.Parser'); -goog.require('ol.expression.TokenType'); +goog.require('ol.expr.Expression'); +goog.require('ol.expr.Call'); +goog.require('ol.expr.Comparison'); +goog.require('ol.expr.Lexer'); +goog.require('ol.expr.Literal'); +goog.require('ol.expr.Logical'); +goog.require('ol.expr.Math'); +goog.require('ol.expr.Member'); +goog.require('ol.expr.Not'); +goog.require('ol.expr.Parser'); +goog.require('ol.expr.TokenType'); diff --git a/test/spec/ol/expression/expressions.test.js b/test/spec/ol/expression/expressions.test.js deleted file mode 100644 index fa49ec31a4..0000000000 --- a/test/spec/ol/expression/expressions.test.js +++ /dev/null @@ -1,638 +0,0 @@ -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'); - }); - - }); - - var callee = new ol.expression.Identifier('sqrt'); - var args = [new ol.expression.Literal(42)]; - var expr = new ol.expression.Call(callee, args); - - describe('#getArgs()', function() { - it('gets the callee expression', function() { - expect(expr.getArgs()).to.be(args); - }); - }); - - describe('#getCallee()', function() { - it('gets the callee expression', function() { - expect(expr.getCallee()).to.be(callee); - }); - }); - -}); - - -describe('ol.expression.Comparison', function() { - - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.EQ, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.Comparison); - }); - }); - - describe('#evaluate()', function() { - it('compares with ==', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.EQ, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - - expect(expr.evaluate({foo: 42})).to.be(true); - expect(expr.evaluate({foo: '42'})).to.be(true); - expect(expr.evaluate({foo: true})).to.be(false); - expect(expr.evaluate({bar: true})).to.be(false); - }); - - it('compares with !=', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.NEQ, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - - expect(expr.evaluate({foo: 42})).to.be(false); - expect(expr.evaluate({foo: '42'})).to.be(false); - expect(expr.evaluate({foo: true})).to.be(true); - expect(expr.evaluate({bar: true})).to.be(true); - }); - - it('compares with ===', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.STRICT_EQ, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - - expect(expr.evaluate({foo: 42})).to.be(true); - expect(expr.evaluate({foo: '42'})).to.be(false); - expect(expr.evaluate({foo: true})).to.be(false); - expect(expr.evaluate({bar: true})).to.be(false); - }); - - it('compares with !==', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.STRICT_NEQ, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - - expect(expr.evaluate({foo: 42})).to.be(false); - expect(expr.evaluate({foo: '42'})).to.be(true); - expect(expr.evaluate({foo: true})).to.be(true); - expect(expr.evaluate({bar: true})).to.be(true); - }); - - it('compares with >', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.GT, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - - expect(expr.evaluate({foo: 42})).to.be(false); - expect(expr.evaluate({foo: 41})).to.be(false); - expect(expr.evaluate({foo: 43})).to.be(true); - }); - - it('compares with <', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.LT, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - - expect(expr.evaluate({foo: 42})).to.be(false); - expect(expr.evaluate({foo: 41})).to.be(true); - expect(expr.evaluate({foo: 43})).to.be(false); - }); - - it('compares with >=', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.GTE, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - - expect(expr.evaluate({foo: 42})).to.be(true); - expect(expr.evaluate({foo: 41})).to.be(false); - expect(expr.evaluate({foo: 43})).to.be(true); - }); - - it('compares with <=', function() { - var expr = new ol.expression.Comparison( - ol.expression.ComparisonOp.LTE, - new ol.expression.Identifier('foo'), - new ol.expression.Literal(42)); - - expect(expr.evaluate({foo: 42})).to.be(true); - expect(expr.evaluate({foo: 41})).to.be(true); - expect(expr.evaluate({foo: 43})).to.be(false); - }); - }); - - describe('#isValidOp()', function() { - it('determines if a string is a valid operator', function() { - expect(ol.expression.Comparison.isValidOp('<')).to.be(true); - expect(ol.expression.Comparison.isValidOp('<')).to.be(true); - expect(ol.expression.Comparison.isValidOp('<=')).to.be(true); - expect(ol.expression.Comparison.isValidOp('<=')).to.be(true); - expect(ol.expression.Comparison.isValidOp('==')).to.be(true); - expect(ol.expression.Comparison.isValidOp('!=')).to.be(true); - expect(ol.expression.Comparison.isValidOp('===')).to.be(true); - expect(ol.expression.Comparison.isValidOp('!==')).to.be(true); - - expect(ol.expression.Comparison.isValidOp('')).to.be(false); - expect(ol.expression.Comparison.isValidOp('+')).to.be(false); - expect(ol.expression.Comparison.isValidOp('-')).to.be(false); - expect(ol.expression.Comparison.isValidOp('&&')).to.be(false); - }); - }); - - var op = ol.expression.ComparisonOp.LTE; - var left = new ol.expression.Identifier('foo'); - var right = new ol.expression.Literal(42); - var expr = new ol.expression.Comparison(op, left, right); - - describe('#getOperator()', function() { - it('gets the operator', function() { - expect(expr.getOperator()).to.be(op); - }); - }); - - describe('#getLeft()', function() { - it('gets the left expression', function() { - expect(expr.getLeft()).to.be(left); - }); - }); - - describe('#getRight()', function() { - it('gets the right expression', function() { - expect(expr.getRight()).to.be(right); - }); - }); - -}); - -describe('ol.expression.Identifier', function() { - - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.Identifier('foo'); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.Identifier); - }); - }); - - describe('#evaluate()', function() { - it('returns a number from the scope', function() { - var expr = new ol.expression.Identifier('foo'); - expect(expr.evaluate({foo: 42})).to.be(42); - }); - - it('returns a string from the scope', function() { - var expr = new ol.expression.Identifier('foo'); - expect(expr.evaluate({foo: 'chicken'})).to.be('chicken'); - }); - - it('returns a boolean from the scope', function() { - var expr = new ol.expression.Identifier('bar'); - expect(expr.evaluate({bar: false})).to.be(false); - expect(expr.evaluate({bar: true})).to.be(true); - }); - - it('returns a null from the scope', function() { - var expr = new ol.expression.Identifier('nada'); - expect(expr.evaluate({nada: null})).to.be(null); - }); - - it('works for unicode identifiers', function() { - var expr = new ol.expression.Identifier('\u03c0'); - expect(expr.evaluate({'\u03c0': Math.PI})).to.be(Math.PI); - }); - }); - - describe('#getName()', function() { - var expr = new ol.expression.Identifier('asdf'); - expect(expr.getName()).to.be('asdf'); - }); - -}); - -describe('ol.expression.Literal', function() { - - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.Literal(true); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.Literal); - }); - }); - - describe('#evaluate()', function() { - it('works for numeric literal', function() { - var expr = new ol.expression.Literal(42e-11); - expect(expr.evaluate()).to.be(4.2e-10); - }); - - it('works for string literal', function() { - var expr = new ol.expression.Literal('asdf'); - expect(expr.evaluate()).to.be('asdf'); - }); - - it('works for boolean literal', function() { - var expr = new ol.expression.Literal(true); - expect(expr.evaluate()).to.be(true); - }); - - it('works for null literal', function() { - var expr = new ol.expression.Literal(null); - expect(expr.evaluate()).to.be(null); - }); - }); - - describe('#getValue()', function() { - var expr = new ol.expression.Literal('asdf'); - expect(expr.getValue()).to.be('asdf'); - }); - -}); - - -describe('ol.expression.Logical', function() { - - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.Logical( - ol.expression.LogicalOp.OR, - new ol.expression.Identifier('foo'), - new ol.expression.Identifier('bar')); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.Logical); - }); - }); - - describe('#evaluate()', function() { - it('applies || to resolved identifiers', function() { - var expr = new ol.expression.Logical( - ol.expression.LogicalOp.OR, - new ol.expression.Identifier('foo'), - new ol.expression.Identifier('bar')); - - expect(expr.evaluate({foo: true, bar: true})).to.be(true); - expect(expr.evaluate({foo: true, bar: false})).to.be(true); - expect(expr.evaluate({foo: false, bar: true})).to.be(true); - expect(expr.evaluate({foo: false, bar: false})).to.be(false); - }); - - it('applies && to resolved identifiers', function() { - var expr = new ol.expression.Logical( - ol.expression.LogicalOp.AND, - new ol.expression.Identifier('foo'), - new ol.expression.Identifier('bar')); - - expect(expr.evaluate({foo: true, bar: true})).to.be(true); - expect(expr.evaluate({foo: true, bar: false})).to.be(false); - expect(expr.evaluate({foo: false, bar: true})).to.be(false); - expect(expr.evaluate({foo: false, bar: false})).to.be(false); - }); - }); - - describe('#isValidOp()', function() { - it('determines if a string is a valid operator', function() { - expect(ol.expression.Logical.isValidOp('||')).to.be(true); - expect(ol.expression.Logical.isValidOp('&&')).to.be(true); - - expect(ol.expression.Logical.isValidOp('')).to.be(false); - expect(ol.expression.Logical.isValidOp('+')).to.be(false); - expect(ol.expression.Logical.isValidOp('<')).to.be(false); - expect(ol.expression.Logical.isValidOp('|')).to.be(false); - }); - }); - - var op = ol.expression.LogicalOp.AND; - var left = new ol.expression.Identifier('foo'); - var right = new ol.expression.Literal(false); - var expr = new ol.expression.Logical(op, left, right); - - describe('#getOperator()', function() { - it('gets the operator', function() { - expect(expr.getOperator()).to.be(op); - }); - }); - - describe('#getLeft()', function() { - it('gets the left expression', function() { - expect(expr.getLeft()).to.be(left); - }); - }); - - describe('#getRight()', function() { - it('gets the right expression', function() { - expect(expr.getRight()).to.be(right); - }); - }); - -}); - -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('#isValidOp()', function() { - it('determines if a string is a valid operator', function() { - expect(ol.expression.Math.isValidOp('+')).to.be(true); - expect(ol.expression.Math.isValidOp('-')).to.be(true); - expect(ol.expression.Math.isValidOp('*')).to.be(true); - expect(ol.expression.Math.isValidOp('/')).to.be(true); - expect(ol.expression.Math.isValidOp('%')).to.be(true); - - expect(ol.expression.Math.isValidOp('')).to.be(false); - expect(ol.expression.Math.isValidOp('|')).to.be(false); - expect(ol.expression.Math.isValidOp('&')).to.be(false); - expect(ol.expression.Math.isValidOp('<')).to.be(false); - expect(ol.expression.Math.isValidOp('||')).to.be(false); - expect(ol.expression.Math.isValidOp('.')).to.be(false); - }); - }); - - var op = ol.expression.MathOp.MOD; - var left = new ol.expression.Identifier('foo'); - var right = new ol.expression.Literal(20); - var expr = new ol.expression.Math(op, left, right); - - describe('#getOperator()', function() { - it('gets the operator', function() { - expect(expr.getOperator()).to.be(op); - }); - }); - - describe('#getLeft()', function() { - it('gets the left expression', function() { - expect(expr.getLeft()).to.be(left); - }); - }); - - describe('#getRight()', function() { - it('gets the right expression', function() { - expect(expr.getRight()).to.be(right); - }); - }); - -}); - -describe('ol.expression.Member', function() { - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.Member( - new ol.expression.Identifier('foo'), - new ol.expression.Identifier('bar')); - - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.Member); - }); - }); - - describe('#evaluate()', function() { - it('accesses an object property', function() { - - var expr = new ol.expression.Member( - new ol.expression.Identifier('foo'), - new ol.expression.Identifier('bar')); - - var scope = {foo: {bar: 42}}; - expect(expr.evaluate(scope)).to.be(42); - }); - }); - - var object = new ol.expression.Identifier('foo'); - var property = new ol.expression.Identifier('bar'); - var expr = new ol.expression.Member(object, property); - - describe('#getObject()', function() { - expect(expr.getObject()).to.be(object); - }); - - describe('#getProperty()', function() { - expect(expr.getProperty()).to.be(property); - }); - -}); - - -describe('ol.expression.Not', function() { - - describe('constructor', function() { - it('creates a new expression', function() { - var expr = new ol.expression.Not( - new ol.expression.Literal(true)); - expect(expr).to.be.a(ol.expression.Expression); - expect(expr).to.be.a(ol.expression.Not); - }); - }); - - describe('#evaluate()', function() { - it('returns the logical complement', function() { - var expr = new ol.expression.Not(new ol.expression.Literal(true)); - expect(expr.evaluate()).to.be(false); - - expr = new ol.expression.Not(new ol.expression.Literal(false)); - expect(expr.evaluate()).to.be(true); - }); - - it('negates a truthy string', function() { - var expr = new ol.expression.Not(new ol.expression.Literal('asdf')); - expect(expr.evaluate()).to.be(false); - }); - - it('negates a falsy string', function() { - var expr = new ol.expression.Not(new ol.expression.Literal('')); - expect(expr.evaluate()).to.be(true); - }); - - it('negates a truthy number', function() { - var expr = new ol.expression.Not(new ol.expression.Literal(42)); - expect(expr.evaluate()).to.be(false); - }); - - it('negates a falsy number', function() { - var expr = new ol.expression.Not(new ol.expression.Literal(NaN)); - expect(expr.evaluate()).to.be(true); - }); - }); - - describe('#getArgument()', function() { - var argument = new ol.expression.Literal(true); - var expr = new ol.expression.Not(argument); - expect(expr.getArgument()).to.be(argument); - }); - -}); - - -goog.require('ol.expression.Call'); -goog.require('ol.expression.Comparison'); -goog.require('ol.expression.ComparisonOp'); -goog.require('ol.expression.Expression'); -goog.require('ol.expression.Identifier'); -goog.require('ol.expression.Literal'); -goog.require('ol.expression.Logical'); -goog.require('ol.expression.LogicalOp'); -goog.require('ol.expression.Math'); -goog.require('ol.expression.MathOp'); -goog.require('ol.expression.Member'); -goog.require('ol.expression.Not'); diff --git a/test/spec/ol/layer/vectorlayer.test.js b/test/spec/ol/layer/vectorlayer.test.js index d1773d65b9..1525617d6a 100644 --- a/test/spec/ol/layer/vectorlayer.test.js +++ b/test/spec/ol/layer/vectorlayer.test.js @@ -50,8 +50,8 @@ describe('ol.layer.Vector', function() { layer.addFeatures(features); }); - var geomFilter = ol.expression.parse('geometryType("linestring")'); - var extentFilter = ol.expression.parse('extent(16, 16.3, 48, 48.3)'); + var geomFilter = ol.expr.parse('geometryType("linestring")'); + var extentFilter = ol.expr.parse('extent(16, 16.3, 48, 48.3)'); it('can filter by geometry type using its GeometryType index', function() { sinon.spy(geomFilter, 'evaluate'); @@ -70,10 +70,10 @@ describe('ol.layer.Vector', function() { }); it('can filter by extent and geometry type using its index', function() { - var filter1 = new ol.expression.Logical( - ol.expression.LogicalOp.AND, geomFilter, extentFilter); - var filter2 = new ol.expression.Logical( - ol.expression.LogicalOp.AND, extentFilter, geomFilter); + var filter1 = new ol.expr.Logical( + ol.expr.LogicalOp.AND, geomFilter, extentFilter); + var filter2 = new ol.expr.Logical( + ol.expr.LogicalOp.AND, extentFilter, geomFilter); sinon.spy(filter1, 'evaluate'); sinon.spy(filter2, 'evaluate'); var subset1 = layer.getFeatures(filter1); @@ -85,8 +85,8 @@ describe('ol.layer.Vector', function() { }); it('can handle query using the filter\'s evaluate function', function() { - var filter = new ol.expression.Logical( - ol.expression.LogicalOp.OR, geomFilter, extentFilter); + var filter = new ol.expr.Logical( + ol.expr.LogicalOp.OR, geomFilter, extentFilter); sinon.spy(filter, 'evaluate'); var subset = layer.getFeatures(filter); expect(filter.evaluate).to.be.called(); @@ -107,7 +107,7 @@ describe('ol.layer.Vector', function() { symbolizers: [ new ol.style.Line({ strokeWidth: 2, - strokeColor: ol.expression.parse('colorProperty'), + strokeColor: ol.expr.parse('colorProperty'), opacity: 1 }) ] @@ -144,7 +144,7 @@ describe('ol.layer.Vector', function() { it('groups equal symbolizers also when defined on features', function() { var symbolizer = new ol.style.Line({ strokeWidth: 3, - strokeColor: ol.expression.parse('colorProperty'), + strokeColor: ol.expr.parse('colorProperty'), opacity: 1 }); var anotherSymbolizer = new ol.style.Line({ @@ -178,9 +178,9 @@ describe('ol.layer.Vector', function() { goog.require('goog.dispose'); goog.require('ol.Feature'); -goog.require('ol.expression'); -goog.require('ol.expression.Logical'); -goog.require('ol.expression.LogicalOp'); +goog.require('ol.expr'); +goog.require('ol.expr.Logical'); +goog.require('ol.expr.LogicalOp'); goog.require('ol.geom.LineString'); goog.require('ol.geom.Point'); goog.require('ol.proj'); diff --git a/test/spec/ol/style/line.test.js b/test/spec/ol/style/line.test.js index f57d4a71fa..6022486638 100644 --- a/test/spec/ol/style/line.test.js +++ b/test/spec/ol/style/line.test.js @@ -42,8 +42,8 @@ describe('ol.style.Line', function() { it('accepts expressions', function() { var symbolizer = new ol.style.Line({ - opacity: ol.expression.parse('value / 100'), - strokeWidth: ol.expression.parse('widthAttr') + opacity: ol.expr.parse('value / 100'), + strokeWidth: ol.expr.parse('widthAttr') }); expect(symbolizer).to.be.a(ol.style.Line); }); @@ -54,8 +54,8 @@ describe('ol.style.Line', function() { it('evaluates expressions with the given feature', function() { var symbolizer = new ol.style.Line({ - opacity: ol.expression.parse('value / 100'), - strokeWidth: ol.expression.parse('widthAttr') + opacity: ol.expr.parse('value / 100'), + strokeWidth: ol.expr.parse('widthAttr') }); var feature = new ol.Feature({ @@ -74,6 +74,6 @@ describe('ol.style.Line', function() { }); goog.require('ol.Feature'); -goog.require('ol.expression'); +goog.require('ol.expr'); goog.require('ol.style.Line'); goog.require('ol.style.LineLiteral'); diff --git a/test/spec/ol/style/polygon.test.js b/test/spec/ol/style/polygon.test.js index e8a9453899..6ce43ae867 100644 --- a/test/spec/ol/style/polygon.test.js +++ b/test/spec/ol/style/polygon.test.js @@ -45,8 +45,8 @@ describe('ol.style.Polygon', function() { it('accepts expressions', function() { var symbolizer = new ol.style.Polygon({ - opacity: ol.expression.parse('value / 100'), - fillColor: ol.expression.parse('fillAttr') + opacity: ol.expr.parse('value / 100'), + fillColor: ol.expr.parse('fillAttr') }); expect(symbolizer).to.be.a(ol.style.Polygon); }); @@ -57,8 +57,8 @@ describe('ol.style.Polygon', function() { it('evaluates expressions with the given feature', function() { var symbolizer = new ol.style.Polygon({ - opacity: ol.expression.parse('value / 100'), - fillColor: ol.expression.parse('fillAttr') + opacity: ol.expr.parse('value / 100'), + fillColor: ol.expr.parse('fillAttr') }); var feature = new ol.Feature({ @@ -90,6 +90,6 @@ describe('ol.style.Polygon', function() { }); goog.require('ol.Feature'); -goog.require('ol.expression'); +goog.require('ol.expr'); goog.require('ol.style.Polygon'); goog.require('ol.style.PolygonLiteral'); diff --git a/test/spec/ol/style/rule.test.js b/test/spec/ol/style/rule.test.js index 789d9de889..c2a2e2d821 100644 --- a/test/spec/ol/style/rule.test.js +++ b/test/spec/ol/style/rule.test.js @@ -13,14 +13,14 @@ describe('ol.style.Rule', function() { it('returns false when the rule does not apply', function() { rule = new ol.style.Rule({ - filter: new ol.expression.Literal(false) + filter: new ol.expr.Literal(false) }); expect(rule.applies(feature)).to.be(false); }); it('returns true when the rule applies', function() { rule = new ol.style.Rule({ - filter: new ol.expression.Literal(true) + filter: new ol.expr.Literal(true) }); expect(rule.applies(feature)).to.be(true); }); @@ -29,5 +29,5 @@ describe('ol.style.Rule', function() { }); goog.require('ol.Feature'); -goog.require('ol.expression.Literal'); +goog.require('ol.expr.Literal'); goog.require('ol.style.Rule'); diff --git a/test/spec/ol/style/shape.test.js b/test/spec/ol/style/shape.test.js index 90a1158f06..1e629eef1b 100644 --- a/test/spec/ol/style/shape.test.js +++ b/test/spec/ol/style/shape.test.js @@ -51,8 +51,8 @@ describe('ol.style.Shape', function() { it('accepts expressions', function() { var symbolizer = new ol.style.Shape({ - size: ol.expression.parse('sizeAttr'), - strokeColor: ol.expression.parse('color') + size: ol.expr.parse('sizeAttr'), + strokeColor: ol.expr.parse('color') }); expect(symbolizer).to.be.a(ol.style.Shape); }); @@ -63,8 +63,8 @@ describe('ol.style.Shape', function() { it('evaluates expressions with the given feature', function() { var symbolizer = new ol.style.Shape({ - size: ol.expression.parse('sizeAttr'), - opacity: ol.expression.parse('opacityAttr'), + size: ol.expr.parse('sizeAttr'), + opacity: ol.expr.parse('opacityAttr'), fillColor: '#BADA55' }); @@ -99,8 +99,8 @@ describe('ol.style.Shape', function() { it('applies default type if none provided', function() { var symbolizer = new ol.style.Shape({ - size: ol.expression.parse('sizeAttr'), - opacity: ol.expression.parse('opacityAttr'), + size: ol.expr.parse('sizeAttr'), + opacity: ol.expr.parse('opacityAttr'), fillColor: '#BADA55' }); @@ -120,7 +120,7 @@ describe('ol.style.Shape', function() { }); goog.require('ol.Feature'); -goog.require('ol.expression'); +goog.require('ol.expr'); goog.require('ol.style.Shape'); goog.require('ol.style.ShapeLiteral'); goog.require('ol.style.ShapeType'); From 1f23a245e4b147084b2ffdcc4d99bc86ee783334 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sun, 23 Jun 2013 16:27:16 -0600 Subject: [PATCH 79/84] More tests for unary expression --- test/spec/ol/expr/expression.test.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/spec/ol/expr/expression.test.js b/test/spec/ol/expr/expression.test.js index 6e2946ebd2..2fe1b11723 100644 --- a/test/spec/ol/expr/expression.test.js +++ b/test/spec/ol/expr/expression.test.js @@ -161,6 +161,29 @@ describe('ol.expr.parse()', function() { expect(expr.evaluate({foo: false})).to.be(true); }); + it('parses not preceeding call expression', function() { + var lib = { + foo: function() { + return true; + } + }; + var expr = ol.expr.parse('!foo()'); + expect(expr).to.be.a(ol.expr.Not); + expect(expr.evaluate(null, lib)).to.be(false); + }); + + it('parses not in call argument', function() { + var lib = { + foo: function(arg) { + return arg; + } + }; + var expr = ol.expr.parse('foo(!bar)'); + expect(expr).to.be.a(ol.expr.Call); + expect(expr.evaluate({bar: true}, lib)).to.be(false); + expect(expr.evaluate({bar: false}, lib)).to.be(true); + }); + }); describe('11.5 - multiplicitave operators', function() { From c81057780ae49e21c2c63d7ec23346b43d37c7b9 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sun, 23 Jun 2013 17:11:57 -0600 Subject: [PATCH 80/84] More tests for binary operators --- src/ol/expr/parser.js | 9 ++--- test/spec/ol/expr/expression.test.js | 52 ++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/ol/expr/parser.js b/src/ol/expr/parser.js index 042a5fc82f..f11cdd5603 100644 --- a/src/ol/expr/parser.js +++ b/src/ol/expr/parser.js @@ -310,8 +310,7 @@ ol.expr.Parser.prototype.parseBinaryExpression_ = function(lexer) { var right = this.parseUnaryExpression_(lexer); var stack = [left, operator, right]; - operator = lexer.peek(); - precedence = this.binaryPrecedence_(operator); + precedence = this.binaryPrecedence_(lexer.peek()); while (precedence > 0) { // TODO: cache operator precedence in stack while (stack.length > 2 && @@ -321,11 +320,9 @@ ol.expr.Parser.prototype.parseBinaryExpression_ = function(lexer) { left = stack.pop(); stack.push(this.createBinaryExpression_(operator.value, left, right)); } - lexer.skip(); - operator = lexer.peek(); - precedence = this.binaryPrecedence_(operator); - stack.push(operator); + stack.push(lexer.next()); stack.push(this.parseUnaryExpression_(lexer)); + precedence = this.binaryPrecedence_(lexer.peek()); } var i = stack.length - 1; diff --git a/test/spec/ol/expr/expression.test.js b/test/spec/ol/expr/expression.test.js index 2fe1b11723..3746bd9e55 100644 --- a/test/spec/ol/expr/expression.test.js +++ b/test/spec/ol/expr/expression.test.js @@ -225,6 +225,32 @@ describe('ol.expr.parse()', function() { expect(expr.evaluate({bar: 3})).to.be(1); }); + it('parses * in call argument', function() { + var lib = { + foo: function(arg) { + return arg; + } + }; + var expr = ol.expr.parse('foo(2 * bar)'); + expect(expr).to.be.a(ol.expr.Call); + expect(expr.evaluate({bar: 3}, lib)).to.be(6); + expect(expr.evaluate({bar: 4}, lib)).to.be(8); + }); + + it('parses * in left side of comparison expression', function() { + var expr = ol.expr.parse('foo * 2 >bar'); + expect(expr).to.be.a(ol.expr.Comparison); + expect(expr.evaluate({foo: 4, bar: 7})).to.be(true); + expect(expr.evaluate({foo: 4, bar: 8})).to.be(false); + }); + + it('parses * in right side of comparison expression', function() { + var expr = ol.expr.parse('foo > 2 * bar'); + expect(expr).to.be.a(ol.expr.Comparison); + expect(expr.evaluate({foo: 4, bar: 1})).to.be(true); + expect(expr.evaluate({foo: 4, bar: 2})).to.be(false); + }); + }); describe('11.6 - additive operators', function() { @@ -254,6 +280,32 @@ describe('ol.expr.parse()', function() { expect(expr.evaluate({foo: 15})).to.be(5); }); + it('parses + in call argument', function() { + var lib = { + foo: function(arg) { + return arg; + } + }; + var expr = ol.expr.parse('foo(2 + bar)'); + expect(expr).to.be.a(ol.expr.Call); + expect(expr.evaluate({bar: 3}, lib)).to.be(5); + expect(expr.evaluate({bar: 4}, lib)).to.be(6); + }); + + it('parses + in left side of comparison expression', function() { + var expr = ol.expr.parse('foo+2>bar'); + expect(expr).to.be.a(ol.expr.Comparison); + expect(expr.evaluate({foo: 4, bar: 5})).to.be(true); + expect(expr.evaluate({foo: 4, bar: 6})).to.be(false); + }); + + it('parses + in right side of comparison expression', function() { + var expr = ol.expr.parse('foo >2 +bar'); + expect(expr).to.be.a(ol.expr.Comparison); + expect(expr.evaluate({foo: 4, bar: 1})).to.be(true); + expect(expr.evaluate({foo: 4, bar: 2})).to.be(false); + }); + }); describe('11.7 - bitwise shift operators', function() { From 70fef738691832e9932feef50e8f13e6cfdb8e9f Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 24 Jun 2013 16:22:19 -0600 Subject: [PATCH 81/84] Testing operator precedence --- test/spec/ol/expr/expression.test.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/spec/ol/expr/expression.test.js b/test/spec/ol/expr/expression.test.js index 3746bd9e55..ca017825d8 100644 --- a/test/spec/ol/expr/expression.test.js +++ b/test/spec/ol/expr/expression.test.js @@ -237,6 +237,21 @@ describe('ol.expr.parse()', function() { expect(expr.evaluate({bar: 4}, lib)).to.be(8); }); + it('evaluates left to right for equal precedence', function() { + var expr = ol.expr.parse('2 / 4 * 20 % 15'); + expect(expr.evaluate()).to.be(10); + }); + + it('respects group precedence', function() { + expect(ol.expr.parse('2 / 4 * (20 % 15)').evaluate()).to.be(2.5); + expect(ol.expr.parse('2 / (4 * (20 % 15))').evaluate()).to.be(0.1); + expect(ol.expr.parse('2 / ((4 * 20) % 15)').evaluate()).to.be(0.4); + expect(ol.expr.parse('2 / (4 * 20) % 15').evaluate()).to.be(0.025); + expect(ol.expr.parse('(2 / (4 * 20)) % 15').evaluate()).to.be(0.025); + expect(ol.expr.parse('(2 / 4) * 20 % 15').evaluate()).to.be(10); + expect(ol.expr.parse('((2 / 4) * 20) % 15').evaluate()).to.be(10); + }); + it('parses * in left side of comparison expression', function() { var expr = ol.expr.parse('foo * 2 >bar'); expect(expr).to.be.a(ol.expr.Comparison); @@ -280,6 +295,17 @@ describe('ol.expr.parse()', function() { expect(expr.evaluate({foo: 15})).to.be(5); }); + it('respects precedence', function() { + expect(ol.expr.parse('2 + 4 * 20 - 15').evaluate()).to.be(67); + expect(ol.expr.parse('(2 + 4) * 20 - 15').evaluate()).to.be(105); + expect(ol.expr.parse('((2 + 4) * 20) - 15').evaluate()).to.be(105); + expect(ol.expr.parse('(2 + (4 * 20)) - 15').evaluate()).to.be(67); + expect(ol.expr.parse('2 + (4 * 20) - 15').evaluate()).to.be(67); + expect(ol.expr.parse('2 + ((4 * 20) - 15)').evaluate()).to.be(67); + expect(ol.expr.parse('2 + (4 * (20 - 15))').evaluate()).to.be(22); + expect(ol.expr.parse('2 + 4 * (20 - 15)').evaluate()).to.be(22); + }); + it('parses + in call argument', function() { var lib = { foo: function(arg) { From 1eaf82ead5c6c11e4c33ed93da6061c8e0c3c0ef Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 25 Jun 2013 11:54:34 -0600 Subject: [PATCH 82/84] Enum for checking built-in lib functions internally --- src/ol/expr/expression.js | 81 +++++++++++++++++++++---------------- src/ol/layer/vectorlayer.js | 9 +++-- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/ol/expr/expression.js b/src/ol/expr/expression.js index 8a60e5b2ad..3739ffc54b 100644 --- a/src/ol/expr/expression.js +++ b/src/ol/expr/expression.js @@ -1,4 +1,5 @@ goog.provide('ol.expr'); +goog.provide('ol.expr.functions'); goog.require('ol.Extent'); goog.require('ol.Feature'); @@ -83,43 +84,55 @@ ol.expr.isLibCall = function(expr) { * Library of well-known functions. These are available to expressions parsed * with `ol.expr.parse`. * - * @type {Object} + * @type {Object.} */ -ol.expr.lib = { +ol.expr.lib = {}; + + +/** + * Enumeration of library function names. + * + * @enum {string} + */ +ol.expr.functions = { + EXTENT: 'extent', + GEOMETRY_TYPE: 'geometryType' +}; + + +/** + * Determine if a feature's extent intersects the provided extent. + * @param {number} minX Minimum x-coordinate value. + * @param {number} maxX Maximum x-coordinate value. + * @param {number} minY Minimum y-coordinate value. + * @param {number} maxY Maximum y-coordinate value. + * @return {boolean} The provided extent intersects the feature's extent. + * @this {ol.Feature} + */ +ol.expr.lib[ol.expr.functions.EXTENT] = function(minX, maxX, minY, maxY) { + var intersects = false; + var geometry = this.getGeometry(); + if (geometry) { + intersects = ol.extent.intersects(geometry.getBounds(), + [minX, maxX, minY, maxY]); + } + return intersects; +}; - /** - * Determine if a feature's extent intersects the provided extent. - * @param {number} minX Minimum x-coordinate value. - * @param {number} maxX Maximum x-coordinate value. - * @param {number} minY Minimum y-coordinate value. - * @param {number} maxY Maximum y-coordinate value. - * @return {boolean} The provided extent intersects the feature's extent. - * @this {ol.Feature} - */ - 'extent': function(minX, maxX, minY, maxY) { - var intersects = false; - var geometry = this.getGeometry(); - if (geometry) { - intersects = ol.extent.intersects(geometry.getBounds(), - [minX, maxX, minY, maxY]); } - return intersects; - }, - - - /** - * Determine if a feature's default geometry is of the given type. - * @param {ol.geom.GeometryType} type Geometry type. - * @return {boolean} The feature's default geometry is of the given type. - * @this {ol.Feature} - */ - 'geometryType': function(type) { - var same = false; - var geometry = this.getGeometry(); - if (geometry) { - same = geometry.getType() === type; - } - return same; } +/** + * Determine if a feature's default geometry is of the given type. + * @param {ol.geom.GeometryType} type Geometry type. + * @return {boolean} The feature's default geometry is of the given type. + * @this {ol.Feature} + */ +ol.expr.lib[ol.expr.functions.GEOMETRY_TYPE] = function(type) { + var same = false; + var geometry = this.getGeometry(); + if (geometry) { + same = geometry.getType() === type; + } + return same; }; diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 3487f187d8..264c4c9fa4 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -9,6 +9,7 @@ goog.require('ol.expr'); goog.require('ol.expr.Literal'); goog.require('ol.expr.Logical'); goog.require('ol.expr.LogicalOp'); +goog.require('ol.expr.functions'); goog.require('ol.geom.GeometryType'); goog.require('ol.geom.SharedVertices'); goog.require('ol.layer.Layer'); @@ -93,14 +94,14 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_expr) { } else { // check for geometryType or extent expression var name = ol.expr.isLibCall(opt_expr); - if (name === 'geometryType') { + if (name === ol.expr.functions.GEOMETRY_TYPE) { var args = /** @type {ol.expr.Call} */ (opt_expr).getArgs(); goog.asserts.assert(args.length === 1); goog.asserts.assert(args[0] instanceof ol.expr.Literal); var type = /** @type {ol.expr.Literal } */ (args[0]).evaluate(); goog.asserts.assertString(type); features = this.geometryTypeIndex_[type]; - } else if (name === 'extent') { + } else if (name === ol.expr.functions.EXTENT) { var args = /** @type {ol.expr.Call} */ (opt_expr).getArgs(); goog.asserts.assert(args.length === 4); var extent = []; @@ -120,13 +121,13 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_expr) { for (var i = 0; i <= 1; ++i) { expr = expressions[i]; name = ol.expr.isLibCall(expr); - if (name === 'geometryType') { + if (name === ol.expr.functions.GEOMETRY_TYPE) { args = /** @type {ol.expr.Call} */ (expr).getArgs(); goog.asserts.assert(args.length === 1); goog.asserts.assert(args[0] instanceof ol.expr.Literal); type = /** @type {ol.expr.Literal } */ (args[0]).evaluate(); goog.asserts.assertString(type); - } else if (name === 'extent') { + } else if (name === ol.expr.functions.EXTENT) { args = /** @type {ol.expr.Call} */ (expr).getArgs(); goog.asserts.assert(args.length === 4); extent = []; From 4e5ef05e5e060f120a31b971baf3da2982ac821a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 25 Jun 2013 11:55:01 -0600 Subject: [PATCH 83/84] Expression for evaluating feature ids --- src/ol/expr/expression.js | 20 ++++++++++++ test/spec/ol/expr/expression.test.js | 48 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/ol/expr/expression.js b/src/ol/expr/expression.js index 3739ffc54b..81a511bc26 100644 --- a/src/ol/expr/expression.js +++ b/src/ol/expr/expression.js @@ -96,6 +96,7 @@ ol.expr.lib = {}; */ ol.expr.functions = { EXTENT: 'extent', + FID: 'fid', GEOMETRY_TYPE: 'geometryType' }; @@ -119,8 +120,27 @@ ol.expr.lib[ol.expr.functions.EXTENT] = function(minX, maxX, minY, maxY) { return intersects; }; + +/** + * Determine if the feature identifier matches any of the provided values. + * @param {...string} var_args Feature identifiers. + * @return {boolean} The feature's identifier matches one of the given values. + * @this {ol.Feature} + */ +ol.expr.lib[ol.expr.functions.FID] = function(var_args) { + var matches = false; + var id = this.getFeatureId(); + if (goog.isDef(id)) { + for (var i = 0, ii = arguments.length; i < ii; ++i) { + if (arguments[i] === id) { + matches = true; + break; + } } } + return matches; +}; + /** * Determine if a feature's default geometry is of the given type. diff --git a/test/spec/ol/expr/expression.test.js b/test/spec/ol/expr/expression.test.js index ca017825d8..30b52945d5 100644 --- a/test/spec/ol/expr/expression.test.js +++ b/test/spec/ol/expr/expression.test.js @@ -663,6 +663,54 @@ describe('ol.expr.lib', function() { }); + describe('fid()', function() { + + var one = new ol.Feature(); + one.setFeatureId('one'); + + var two = new ol.Feature(); + two.setFeatureId('two'); + + var three = new ol.Feature(); + three.setFeatureId('three'); + + var four = new ol.Feature(); + four.setFeatureId('four'); + + var odd = parse('fid("one", "three")'); + var even = parse('fid("two", "four")'); + var first = parse('fid("one")'); + var last = parse('fid("four")'); + var none = parse('fid("foo")'); + + it('evaluates to true if feature id matches', function() { + expect(evaluate(odd, one), true); + expect(evaluate(odd, three), true); + expect(evaluate(even, two), true); + expect(evaluate(even, four), true); + expect(evaluate(first, one), true); + expect(evaluate(last, four), true); + }); + + it('evaluates to false if feature id doesn\'t match', function() { + expect(evaluate(odd, two), false); + expect(evaluate(odd, four), false); + expect(evaluate(even, one), false); + expect(evaluate(even, three), false); + expect(evaluate(first, two), false); + expect(evaluate(first, three), false); + expect(evaluate(first, four), false); + expect(evaluate(last, one), false); + expect(evaluate(last, two), false); + expect(evaluate(last, three), false); + expect(evaluate(none, one), false); + expect(evaluate(none, two), false); + expect(evaluate(none, three), false); + expect(evaluate(none, four), false); + }); + + }); + describe('geometryType()', function() { var point = new ol.Feature({ From 71153d26d12d7d63357c298831a8c1e34b532b62 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 26 Jun 2013 17:01:27 -0600 Subject: [PATCH 84/84] Prefer if/else to switch --- src/ol/expr/expressions.js | 80 ++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/src/ol/expr/expressions.js b/src/ol/expr/expressions.js index 0b1d716f3c..bd827885ea 100644 --- a/src/ol/expr/expressions.js +++ b/src/ol/expr/expressions.js @@ -175,39 +175,30 @@ ol.expr.Comparison.isValidOp = (function() { /** * @inheritDoc */ -ol.expr.Comparison.prototype.evaluate = function(opt_scope, opt_fns, - opt_this) { +ol.expr.Comparison.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { var result; var rightVal = this.right_.evaluate(opt_scope, opt_fns, opt_this); var leftVal = this.left_.evaluate(opt_scope, opt_fns, opt_this); - switch (this.operator_) { - case ol.expr.ComparisonOp.EQ: - result = leftVal == rightVal; - break; - case ol.expr.ComparisonOp.NEQ: - result = leftVal != rightVal; - break; - case ol.expr.ComparisonOp.STRICT_EQ: - result = leftVal === rightVal; - break; - case ol.expr.ComparisonOp.STRICT_NEQ: - result = leftVal !== rightVal; - break; - case ol.expr.ComparisonOp.GT: - result = leftVal > rightVal; - break; - case ol.expr.ComparisonOp.LT: - result = leftVal < rightVal; - break; - case ol.expr.ComparisonOp.GTE: - result = leftVal >= rightVal; - break; - case ol.expr.ComparisonOp.LTE: - result = leftVal <= rightVal; - break; - default: - throw new Error('Unsupported comparison operator: ' + this.operator_); + var op = this.operator_; + if (op === ol.expr.ComparisonOp.EQ) { + result = leftVal == rightVal; + } else if (op === ol.expr.ComparisonOp.NEQ) { + result = leftVal != rightVal; + } else if (op === ol.expr.ComparisonOp.STRICT_EQ) { + result = leftVal === rightVal; + } else if (op === ol.expr.ComparisonOp.STRICT_NEQ) { + result = leftVal !== rightVal; + } else if (op === ol.expr.ComparisonOp.GT) { + result = leftVal > rightVal; + } else if (op === ol.expr.ComparisonOp.LT) { + result = leftVal < rightVal; + } else if (op === ol.expr.ComparisonOp.GTE) { + result = leftVal >= rightVal; + } else if (op === ol.expr.ComparisonOp.LTE) { + result = leftVal <= rightVal; + } else { + throw new Error('Unsupported comparison operator: ' + this.operator_); } return result; }; @@ -498,24 +489,19 @@ ol.expr.Math.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { * math functions where available elsewhere */ - switch (this.operator_) { - case ol.expr.MathOp.ADD: - result = leftVal + rightVal; - break; - case ol.expr.MathOp.SUBTRACT: - result = Number(leftVal) - Number(rightVal); - break; - case ol.expr.MathOp.MULTIPLY: - result = Number(leftVal) * Number(rightVal); - break; - case ol.expr.MathOp.DIVIDE: - result = Number(leftVal) / Number(rightVal); - break; - case ol.expr.MathOp.MOD: - result = Number(leftVal) % Number(rightVal); - break; - default: - throw new Error('Unsupported math operator: ' + this.operator_); + var op = this.operator_; + if (op === ol.expr.MathOp.ADD) { + result = leftVal + rightVal; + } else if (op === ol.expr.MathOp.SUBTRACT) { + result = Number(leftVal) - Number(rightVal); + } else if (op === ol.expr.MathOp.MULTIPLY) { + result = Number(leftVal) * Number(rightVal); + } else if (op === ol.expr.MathOp.DIVIDE) { + result = Number(leftVal) / Number(rightVal); + } else if (op === ol.expr.MathOp.MOD) { + result = Number(leftVal) % Number(rightVal); + } else { + throw new Error('Unsupported math operator: ' + this.operator_); } return result; };