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);
+};