/* 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 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ (function (global) { 'use strict'; var Token, TokenName, Syntax, Messages, source, index, length, delegate, lookahead, state; Token = { BooleanLiteral: 1, EOF: 2, Identifier: 3, Keyword: 4, NullLiteral: 5, NumericLiteral: 6, Punctuator: 7, StringLiteral: 8 }; TokenName = {}; TokenName[Token.BooleanLiteral] = 'Boolean'; TokenName[Token.EOF] = ''; TokenName[Token.Identifier] = 'Identifier'; TokenName[Token.Keyword] = 'Keyword'; TokenName[Token.NullLiteral] = 'Null'; TokenName[Token.NumericLiteral] = 'Numeric'; TokenName[Token.Punctuator] = 'Punctuator'; TokenName[Token.StringLiteral] = 'String'; Syntax = { ArrayExpression: 'ArrayExpression', BinaryExpression: 'BinaryExpression', CallExpression: 'CallExpression', ConditionalExpression: 'ConditionalExpression', EmptyStatement: 'EmptyStatement', ExpressionStatement: 'ExpressionStatement', Identifier: 'Identifier', Literal: 'Literal', LabeledStatement: 'LabeledStatement', LogicalExpression: 'LogicalExpression', MemberExpression: 'MemberExpression', ObjectExpression: 'ObjectExpression', Program: 'Program', Property: 'Property', ThisExpression: 'ThisExpression', UnaryExpression: 'UnaryExpression' }; // Error messages should be identical to V8. Messages = { UnexpectedToken: 'Unexpected token %0', UnknownLabel: 'Undefined label \'%0\'', Redeclaration: '%0 \'%1\' has already been declared' }; // Ensure the condition is true, otherwise throw an error. // This is only to have a better contract semantic, i.e. another safety net // to catch a logic error. The condition shall be fulfilled in normal case. // Do NOT use this to enforce a certain condition on any user input. function assert(condition, message) { if (!condition) { throw new Error('ASSERT: ' + message); } } function isDecimalDigit(ch) { return (ch >= 48 && ch <= 57); // 0..9 } // 7.2 White Space function isWhiteSpace(ch) { return (ch === 32) || // space (ch === 9) || // tab (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); } // 7.3 Line Terminators function isLineTerminator(ch) { return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); } // 7.6 Identifier Names and Identifiers function isIdentifierStart(ch) { return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) (ch >= 65 && ch <= 90) || // A..Z (ch >= 97 && ch <= 122); // a..z } function isIdentifierPart(ch) { return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) (ch >= 65 && ch <= 90) || // A..Z (ch >= 97 && ch <= 122) || // a..z (ch >= 48 && ch <= 57); // 0..9 } // 7.6.1.1 Keywords function isKeyword(id) { return (id === 'this') } // 7.4 Comments function skipWhitespace() { while (index < length && isWhiteSpace(source.charCodeAt(index))) { ++index; } } function getIdentifier() { var start, ch; start = index++; while (index < length) { ch = source.charCodeAt(index); if (isIdentifierPart(ch)) { ++index; } else { break; } } return source.slice(start, index); } function scanIdentifier() { var start, id, type; start = index; id = getIdentifier(); // There is no keyword or literal with only one character. // Thus, it must be an identifier. if (id.length === 1) { type = Token.Identifier; } else if (isKeyword(id)) { type = Token.Keyword; } else if (id === 'null') { type = Token.NullLiteral; } else if (id === 'true' || id === 'false') { type = Token.BooleanLiteral; } else { type = Token.Identifier; } return { type: type, value: id, range: [start, index] }; } // 7.7 Punctuators function scanPunctuator() { var start = index, code = source.charCodeAt(index), code2, ch1 = source[index], ch2; switch (code) { // Check for most common single-character punctuators. case 46: // . dot case 40: // ( open bracket case 41: // ) close bracket case 59: // ; semicolon case 44: // , comma case 123: // { open curly brace case 125: // } close curly brace case 91: // [ case 93: // ] case 58: // : case 63: // ? ++index; return { type: Token.Punctuator, value: String.fromCharCode(code), range: [start, index] }; default: code2 = source.charCodeAt(index + 1); // '=' (char #61) marks an assignment or comparison operator. if (code2 === 61) { switch (code) { case 37: // % case 38: // & case 42: // *: case 43: // + case 45: // - case 47: // / case 60: // < case 62: // > case 124: // | index += 2; return { type: Token.Punctuator, value: String.fromCharCode(code) + String.fromCharCode(code2), range: [start, index] }; case 33: // ! case 61: // = index += 2; // !== and === if (source.charCodeAt(index) === 61) { ++index; } return { type: Token.Punctuator, value: source.slice(start, index), range: [start, index] }; default: break; } } break; } // Peek more characters. ch2 = source[index + 1]; // Other 2-character punctuators: && || if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { index += 2; return { type: Token.Punctuator, value: ch1 + ch2, range: [start, index] }; } if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { ++index; return { type: Token.Punctuator, value: ch1, range: [start, index] }; } throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } // 7.8.3 Numeric Literals function scanNumericLiteral() { var number, start, ch; ch = source[index]; assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), 'Numeric literal must start with a decimal digit or a decimal point'); start = index; number = ''; if (ch !== '.') { number = source[index++]; ch = source[index]; // Hex number starts with '0x'. // Octal number starts with '0'. if (number === '0') { // decimal number starts with '0' such as '09' is illegal. if (ch && isDecimalDigit(ch.charCodeAt(0))) { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } } while (isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } ch = source[index]; } if (ch === '.') { number += source[index++]; while (isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } ch = source[index]; } if (ch === 'e' || ch === 'E') { number += source[index++]; ch = source[index]; if (ch === '+' || ch === '-') { number += source[index++]; } if (isDecimalDigit(source.charCodeAt(index))) { while (isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } } else { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } } if (isIdentifierStart(source.charCodeAt(index))) { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } return { type: Token.NumericLiteral, value: parseFloat(number), range: [start, index] }; } // 7.8.4 String Literals function scanStringLiteral() { var str = '', quote, start, ch, octal = false; quote = source[index]; assert((quote === '\'' || quote === '"'), 'String literal must starts with a quote'); start = index; ++index; while (index < length) { ch = source[index++]; if (ch === quote) { quote = ''; break; } else if (ch === '\\') { ch = source[index++]; if (!ch || !isLineTerminator(ch.charCodeAt(0))) { switch (ch) { case 'n': str += '\n'; break; case 'r': str += '\r'; break; case 't': str += '\t'; break; case 'b': str += '\b'; break; case 'f': str += '\f'; break; case 'v': str += '\x0B'; break; default: str += ch; break; } } else { if (ch === '\r' && source[index] === '\n') { ++index; } } } else if (isLineTerminator(ch.charCodeAt(0))) { break; } else { str += ch; } } if (quote !== '') { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } return { type: Token.StringLiteral, value: str, octal: octal, range: [start, index] }; } function isIdentifierName(token) { return token.type === Token.Identifier || token.type === Token.Keyword || token.type === Token.BooleanLiteral || token.type === Token.NullLiteral; } function advance() { var ch; skipWhitespace(); if (index >= length) { return { type: Token.EOF, range: [index, index] }; } ch = source.charCodeAt(index); // Very common: ( and ) and ; if (ch === 40 || ch === 41 || ch === 58) { return scanPunctuator(); } // String literal starts with single quote (#39) or double quote (#34). if (ch === 39 || ch === 34) { return scanStringLiteral(); } if (isIdentifierStart(ch)) { return scanIdentifier(); } // Dot (.) char #46 can also start a floating-point number, hence the need // to check the next character. if (ch === 46) { if (isDecimalDigit(source.charCodeAt(index + 1))) { return scanNumericLiteral(); } return scanPunctuator(); } if (isDecimalDigit(ch)) { return scanNumericLiteral(); } return scanPunctuator(); } function lex() { var token; token = lookahead; index = token.range[1]; lookahead = advance(); index = token.range[1]; return token; } function peek() { var pos; pos = index; lookahead = advance(); index = pos; } // Throw an exception function throwError(token, messageFormat) { var error, args = Array.prototype.slice.call(arguments, 2), msg = messageFormat.replace( /%(\d)/g, function (whole, index) { assert(index < args.length, 'Message reference must be in range'); return args[index]; } ); error = new Error(msg); error.index = index; error.description = msg; throw error; } // Throw an exception because of the token. function throwUnexpected(token) { throwError(token, Messages.UnexpectedToken, token.value); } // Expect the next token to match the specified punctuator. // If not, an exception will be thrown. function expect(value) { var token = lex(); if (token.type !== Token.Punctuator || token.value !== value) { throwUnexpected(token); } } // Return true if the next token matches the specified punctuator. function match(value) { return lookahead.type === Token.Punctuator && lookahead.value === value; } // Return true if the next token matches the specified keyword function matchKeyword(keyword) { return lookahead.type === Token.Keyword && lookahead.value === keyword; } function consumeSemicolon() { // Catch the very common case first: immediately a semicolon (char #59). if (source.charCodeAt(index) === 59) { lex(); return; } skipWhitespace(); if (match(';')) { lex(); return; } if (lookahead.type !== Token.EOF && !match('}')) { throwUnexpected(lookahead); } } // 11.1.4 Array Initialiser function parseArrayInitialiser() { var elements = []; expect('['); while (!match(']')) { if (match(',')) { lex(); elements.push(null); } else { elements.push(parseExpression()); if (!match(']')) { expect(','); } } } expect(']'); return delegate.createArrayExpression(elements); } // 11.1.5 Object Initialiser function parseObjectPropertyKey() { var token; skipWhitespace(); token = lex(); // Note: This function is called only from parseObjectProperty(), where // EOF and Punctuator tokens are already filtered out. if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) { return delegate.createLiteral(token); } return delegate.createIdentifier(token.value); } function parseObjectProperty() { var token, key; token = lookahead; skipWhitespace(); if (token.type === Token.EOF || token.type === Token.Punctuator) { throwUnexpected(token); } key = parseObjectPropertyKey(); expect(':'); return delegate.createProperty('init', key, parseExpression()); } function parseObjectInitialiser() { var properties = []; expect('{'); while (!match('}')) { properties.push(parseObjectProperty()); if (!match('}')) { expect(','); } } expect('}'); return delegate.createObjectExpression(properties); } // 11.1.6 The Grouping Operator function parseGroupExpression() { var expr; expect('('); expr = parseExpression(); expect(')'); return expr; } // 11.1 Primary Expressions function parsePrimaryExpression() { var type, token, expr; if (match('(')) { return parseGroupExpression(); } type = lookahead.type; if (type === Token.Identifier) { expr = delegate.createIdentifier(lex().value); } else if (type === Token.StringLiteral || type === Token.NumericLiteral) { expr = delegate.createLiteral(lex()); } else if (type === Token.Keyword) { if (matchKeyword('this')) { lex(); expr = delegate.createThisExpression(); } } else if (type === Token.BooleanLiteral) { token = lex(); token.value = (token.value === 'true'); expr = delegate.createLiteral(token); } else if (type === Token.NullLiteral) { token = lex(); token.value = null; expr = delegate.createLiteral(token); } else if (match('[')) { expr = parseArrayInitialiser(); } else if (match('{')) { expr = parseObjectInitialiser(); } if (expr) { return expr; } throwUnexpected(lex()); } // 11.2 Left-Hand-Side Expressions function parseArguments() { var args = []; expect('('); if (!match(')')) { while (index < length) { args.push(parseExpression()); if (match(')')) { break; } expect(','); } } expect(')'); return args; } function parseNonComputedProperty() { var token; token = lex(); if (!isIdentifierName(token)) { throwUnexpected(token); } return delegate.createIdentifier(token.value); } function parseNonComputedMember() { expect('.'); return parseNonComputedProperty(); } function parseComputedMember() { var expr; expect('['); expr = parseExpression(); expect(']'); return expr; } function parseLeftHandSideExpression() { var expr, args, property; expr = parsePrimaryExpression(); while (true) { if (match('[')) { property = parseComputedMember(); expr = delegate.createMemberExpression('[', expr, property); } else if (match('.')) { property = parseNonComputedMember(); expr = delegate.createMemberExpression('.', expr, property); } else if (match('(')) { args = parseArguments(); expr = delegate.createCallExpression(expr, args); } else { break; } } return expr; } // 11.3 Postfix Expressions var parsePostfixExpression = parseLeftHandSideExpression; // 11.4 Unary Operators function parseUnaryExpression() { var token, expr; if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) { expr = parsePostfixExpression(); } else if (match('+') || match('-') || match('!')) { token = lex(); expr = parseUnaryExpression(); expr = delegate.createUnaryExpression(token.value, expr); } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { throwError({}, Messages.UnexpectedToken); } else { expr = parsePostfixExpression(); } return expr; } function binaryPrecedence(token) { var prec = 0; if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { return 0; } switch (token.value) { case '||': prec = 1; break; case '&&': prec = 2; break; case '==': case '!=': case '===': case '!==': prec = 6; break; case '<': case '>': case '<=': case '>=': case 'instanceof': prec = 7; break; case 'in': prec = 7; break; case '+': case '-': prec = 9; break; case '*': case '/': case '%': prec = 11; break; default: break; } return prec; } // 11.5 Multiplicative Operators // 11.6 Additive Operators // 11.7 Bitwise Shift Operators // 11.8 Relational Operators // 11.9 Equality Operators // 11.10 Binary Bitwise Operators // 11.11 Binary Logical Operators function parseBinaryExpression() { var expr, token, prec, stack, right, operator, left, i; left = parseUnaryExpression(); token = lookahead; prec = binaryPrecedence(token); if (prec === 0) { return left; } token.prec = prec; lex(); right = parseUnaryExpression(); stack = [left, token, right]; while ((prec = binaryPrecedence(lookahead)) > 0) { // Reduce: make a binary expression from the three topmost entries. while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { right = stack.pop(); operator = stack.pop().value; left = stack.pop(); expr = delegate.createBinaryExpression(operator, left, right); stack.push(expr); } // Shift. token = lex(); token.prec = prec; stack.push(token); expr = parseUnaryExpression(); stack.push(expr); } // Final reduce to clean-up the stack. i = stack.length - 1; expr = stack[i]; while (i > 1) { expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr); i -= 2; } return expr; } // 11.12 Conditional Operator function parseConditionalExpression() { var expr, consequent, alternate; expr = parseBinaryExpression(); if (match('?')) { lex(); consequent = parseConditionalExpression(); expect(':'); alternate = parseConditionalExpression(); expr = delegate.createConditionalExpression(expr, consequent, alternate); } return expr; } // Simplification since we do not support AssignmentExpression. var parseExpression = parseConditionalExpression; // Polymer Syntax extensions // Filter :: // Identifier // Identifier "(" ")" // Identifier "(" FilterArguments ")" function parseFilter() { var identifier, args; identifier = lex(); if (identifier.type !== Token.Identifier) { throwUnexpected(identifier); } args = match('(') ? parseArguments() : []; return delegate.createFilter(identifier.value, args); } // Filters :: // "|" Filter // Filters "|" Filter function parseFilters() { while (match('|')) { lex(); parseFilter(); } } // TopLevel :: // LabelledExpressions // AsExpression // InExpression // FilterExpression // AsExpression :: // FilterExpression as Identifier // InExpression :: // Identifier, Identifier in FilterExpression // Identifier in FilterExpression // FilterExpression :: // Expression // Expression Filters function parseTopLevel() { skipWhitespace(); peek(); var expr = parseExpression(); if (expr) { if (lookahead.value === ',' || lookahead.value == 'in' && expr.type === Syntax.Identifier) { parseInExpression(expr); } else { parseFilters(); if (lookahead.value === 'as') { parseAsExpression(expr); } else { delegate.createTopLevel(expr); } } } if (lookahead.type !== Token.EOF) { throwUnexpected(lookahead); } } function parseAsExpression(expr) { lex(); // as var identifier = lex().value; delegate.createAsExpression(expr, identifier); } function parseInExpression(identifier) { var indexName; if (lookahead.value === ',') { lex(); if (lookahead.type !== Token.Identifier) throwUnexpected(lookahead); indexName = lex().value; } lex(); // in var expr = parseExpression(); parseFilters(); delegate.createInExpression(identifier.name, indexName, expr); } function parse(code, inDelegate) { delegate = inDelegate; source = code; index = 0; length = source.length; lookahead = null; state = { labelSet: {} }; return parseTopLevel(); } global.esprima = { parse: parse }; })(module.exports);