This is an automated email from the ASF dual-hosted git repository. davisp pushed a commit to branch gh-pages in repository https://gitbox.apache.org/repos/asf/couchdb-escodegen.git
commit 480a003098c6fc7658ddf4c04fad569e1a9a1613 Author: Constellation <[email protected]> AuthorDate: Fri Mar 16 04:29:02 2012 +0900 First commit to gh-pages --- assets/esprima.js | 3744 +++++++++++++++++++++++++++++++++++++++++++++++++++++ assets/style.css | 52 + demo/index.html | 71 + escodegen.js | 843 ++++++++++++ 4 files changed, 4710 insertions(+) diff --git a/assets/esprima.js b/assets/esprima.js new file mode 100644 index 0000000..8a11eba --- /dev/null +++ b/assets/esprima.js @@ -0,0 +1,3744 @@ +/* + Copyright (C) 2012 Ariya Hidayat <[email protected]> + Copyright (C) 2012 Mathias Bynens <[email protected]> + Copyright (C) 2012 Joost-Wim Boekesteijn <[email protected]> + Copyright (C) 2012 Kris Kowal <[email protected]> + Copyright (C) 2012 Yusuke Suzuki <[email protected]> + Copyright (C) 2012 Arpad Borsos <[email protected]> + Copyright (C) 2011 Ariya Hidayat <[email protected]> + + 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 <COPYRIGHT HOLDER> 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. +*/ + +/*jslint bitwise:true */ +/*global esprima:true, exports:true, +throwError: true, createLiteral: true, generateStatement: true, +parseAssignmentExpression: true, parseBlock: true, parseExpression: true, +parseFunctionDeclaration: true, parseFunctionExpression: true, +parseFunctionSourceElements: true, parseVariableIdentifier: true, +parseLeftHandSideExpression: true, +parseStatement: true, parseSourceElement: true */ + +(function (exports) { + 'use strict'; + + var Token, + TokenName, + Syntax, + PropertyKind, + Messages, + Regex, + source, + allowIn, + lastParenthesized, + strict, + index, + lineNumber, + lineStart, + length, + buffer, + extra, + labelSet, + inIteration, + inSwitch, + inFunctionBody; + + 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] = '<end>'; + 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 = { + AssignmentExpression: 'AssignmentExpression', + ArrayExpression: 'ArrayExpression', + BlockStatement: 'BlockStatement', + BinaryExpression: 'BinaryExpression', + BreakStatement: 'BreakStatement', + CallExpression: 'CallExpression', + CatchClause: 'CatchClause', + ConditionalExpression: 'ConditionalExpression', + ContinueStatement: 'ContinueStatement', + DoWhileStatement: 'DoWhileStatement', + DebuggerStatement: 'DebuggerStatement', + EmptyStatement: 'EmptyStatement', + ExpressionStatement: 'ExpressionStatement', + ForStatement: 'ForStatement', + ForInStatement: 'ForInStatement', + FunctionDeclaration: 'FunctionDeclaration', + FunctionExpression: 'FunctionExpression', + Identifier: 'Identifier', + IfStatement: 'IfStatement', + Literal: 'Literal', + LabeledStatement: 'LabeledStatement', + LogicalExpression: 'LogicalExpression', + MemberExpression: 'MemberExpression', + NewExpression: 'NewExpression', + ObjectExpression: 'ObjectExpression', + Program: 'Program', + Property: 'Property', + ReturnStatement: 'ReturnStatement', + SequenceExpression: 'SequenceExpression', + SwitchStatement: 'SwitchStatement', + SwitchCase: 'SwitchCase', + ThisExpression: 'ThisExpression', + ThrowStatement: 'ThrowStatement', + TryStatement: 'TryStatement', + UnaryExpression: 'UnaryExpression', + UpdateExpression: 'UpdateExpression', + VariableDeclaration: 'VariableDeclaration', + VariableDeclarator: 'VariableDeclarator', + WhileStatement: 'WhileStatement', + WithStatement: 'WithStatement' + }; + + PropertyKind = { + Data: 1, + Get: 2, + Set: 4 + }; + + // Error messages should be identical to V8. + Messages = { + UnexpectedToken: 'Unexpected token %0', + UnexpectedNumber: 'Unexpected number', + UnexpectedString: 'Unexpected string', + UnexpectedIdentifier: 'Unexpected identifier', + UnexpectedReserved: 'Unexpected reserved word', + UnexpectedEOS: 'Unexpected end of input', + NewlineAfterThrow: 'Illegal newline after throw', + InvalidRegExp: 'Invalid regular expression', + UnterminatedRegExp: 'Invalid regular expression: missing /', + InvalidLHSInAssignment: 'Invalid left-hand side in assignment', + InvalidLHSInForIn: 'Invalid left-hand side in for-in', + NoCatchOrFinally: 'Missing catch or finally after try', + UnknownLabel: 'Undefined label \'%0\'', + Redeclaration: '%0 \'%1\' has already been declared', + IllegalContinue: 'Illegal continue statement', + IllegalBreak: 'Illegal break statement', + IllegalReturn: 'Illegal return statement', + StrictModeWith: 'Strict mode code may not include a with statement', + StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode', + StrictVarName: 'Variable name may not be eval or arguments in strict mode', + StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode', + StrictParamDupe: 'Strict mode function may not have duplicate parameter names', + StrictFunctionName: 'Function name may not be eval or arguments in strict mode', + StrictOctalLiteral: 'Octal literals are not allowed in strict mode.', + StrictDelete: 'Delete of an unqualified identifier in strict mode.', + StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode', + AccessorDataProperty: 'Object literal may not have data and accessor property with the same name', + AccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name', + StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode', + StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode', + StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode', + StrictReservedWord: 'Use of future reserved word in strict mode' + }; + + // See also tools/generate-unicode-regex.py. + Regex = { + NonAsciiIdentifierStart: new RegExp('[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840- [...] + NonAsciiIdentifierPart: new RegExp('[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0300-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u0483-\u0487\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u05d0-\u05ea\u05f0-\u05f2\u0610-\u061a\u0620-\u0669\u066e-\u06d3\u06d5-\u06dc\u06df-\u06e8\u06ea-\u06fc\u06ff\u0710-\u074a\u074d-\u07b1\u07c0-\u07f5\u07fa\u08 [...] + }; + + if (typeof Object.freeze === 'function') { + Object.freeze(Token); + Object.freeze(TokenName); + Object.freeze(Syntax); + Object.freeze(PropertyKind); + Object.freeze(Messages); + Object.freeze(Regex); + } + + // 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 sliceSource(from, to) { + return source.slice(from, to); + } + + if (typeof 'esprima'[0] === 'undefined') { + sliceSource = function sliceArraySource(from, to) { + return source.slice(from, to).join(''); + }; + } + + function isDecimalDigit(ch) { + return '0123456789'.indexOf(ch) >= 0; + } + + function isHexDigit(ch) { + return '0123456789abcdefABCDEF'.indexOf(ch) >= 0; + } + + function isOctalDigit(ch) { + return '01234567'.indexOf(ch) >= 0; + } + + + // 7.2 White Space + + function isWhiteSpace(ch) { + return (ch === ' ') || (ch === '\u0009') || (ch === '\u000B') || + (ch === '\u000C') || (ch === '\u00A0') || + (ch.charCodeAt(0) >= 0x1680 && + '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(ch) >= 0); + } + + // 7.3 Line Terminators + + function isLineTerminator(ch) { + return (ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029'); + } + + // 7.6 Identifier Names and Identifiers + + function isIdentifierStart(ch) { + return (ch === '$') || (ch === '_') || (ch === '\\') || + (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + ((ch.charCodeAt(0) >= 0x80) && Regex.NonAsciiIdentifierStart.test(ch)); + } + + function isIdentifierPart(ch) { + return (ch === '$') || (ch === '_') || (ch === '\\') || + (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + ((ch >= '0') && (ch <= '9')) || + ((ch.charCodeAt(0) >= 0x80) && Regex.NonAsciiIdentifierPart.test(ch)); + } + + // 7.6.1.2 Future Reserved Words + + function isFutureReservedWord(id) { + switch (id) { + + // Future reserved words. + case 'class': + case 'enum': + case 'export': + case 'extends': + case 'import': + case 'super': + return true; + } + + return false; + } + + function isStrictModeReservedWord(id) { + switch (id) { + + // Strict Mode reserved words. + case 'implements': + case 'interface': + case 'package': + case 'private': + case 'protected': + case 'public': + case 'static': + case 'yield': + case 'let': + return true; + } + + return false; + } + + function isRestrictedWord(id) { + return id === 'eval' || id === 'arguments'; + } + + // 7.6.1.1 Keywords + + function isKeyword(id) { + var keyword = false; + switch (id.length) { + case 2: + keyword = (id === 'if') || (id === 'in') || (id === 'do'); + break; + case 3: + keyword = (id === 'var') || (id === 'for') || (id === 'new') || (id === 'try'); + break; + case 4: + keyword = (id === 'this') || (id === 'else') || (id === 'case') || (id === 'void') || (id === 'with'); + break; + case 5: + keyword = (id === 'while') || (id === 'break') || (id === 'catch') || (id === 'throw'); + break; + case 6: + keyword = (id === 'return') || (id === 'typeof') || (id === 'delete') || (id === 'switch'); + break; + case 7: + keyword = (id === 'default') || (id === 'finally'); + break; + case 8: + keyword = (id === 'function') || (id === 'continue') || (id === 'debugger'); + break; + case 10: + keyword = (id === 'instanceof'); + break; + } + + if (keyword) { + return true; + } + + switch (id) { + // Future reserved words. + // 'const' is specialized as Keyword in V8. + case 'const': + return true; + + // For compatiblity to SpiderMonkey and ES.next + case 'yield': + case 'let': + return true; + } + + if (strict && isStrictModeReservedWord(id)) { + return true; + } + + return isFutureReservedWord(id); + } + + // Return the next character and move forward. + + function nextChar() { + var ch; + if (index < length) { + ch = source[index]; + index += 1; + } + return ch; + } + + // 7.4 Comments + + function skipComment() { + var ch, blockComment, lineComment; + + blockComment = false; + lineComment = false; + + while (index < length) { + ch = source[index]; + + if (lineComment) { + ch = nextChar(); + if (isLineTerminator(ch)) { + lineComment = false; + if (ch === '\r' && source[index] === '\n') { + index += 1; + } + lineNumber += 1; + lineStart = index; + } + } else if (blockComment) { + if (isLineTerminator(ch)) { + if (ch === '\r' && source[index + 1] === '\n') { + index += 1; + } + lineNumber += 1; + index += 1; + lineStart = index; + if (index >= length) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } else { + ch = nextChar(); + if (index >= length) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + if (ch === '*') { + ch = source[index]; + if (ch === '/') { + index += 1; + blockComment = false; + } + } + } + } else if (ch === '/') { + ch = source[index + 1]; + if (ch === '/') { + index += 2; + lineComment = true; + } else if (ch === '*') { + index += 2; + blockComment = true; + if (index >= length) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } else { + break; + } + } else if (isWhiteSpace(ch)) { + index += 1; + } else if (isLineTerminator(ch)) { + index += 1; + if (ch === '\r' && source[index] === '\n') { + index += 1; + } + lineNumber += 1; + lineStart = index; + } else { + break; + } + } + } + + function scanHexEscape(prefix) { + var i, len, ch, code = 0; + + len = (prefix === 'u') ? 4 : 2; + for (i = 0; i < len; i += 1) { + if (index < length && isHexDigit(source[index])) { + ch = nextChar(); + code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase()); + } else { + return ''; + } + } + return String.fromCharCode(code); + } + + function scanIdentifier() { + var ch, start, id, restore; + + ch = source[index]; + if (!isIdentifierStart(ch)) { + return; + } + + start = index; + if (ch === '\\') { + index += 1; + if (source[index] !== 'u') { + return; + } + index += 1; + restore = index; + ch = scanHexEscape('u'); + if (ch) { + if (ch === '\\' || !isIdentifierStart(ch)) { + return; + } + id = ch; + } else { + index = restore; + id = 'u'; + } + } else { + id = nextChar(); + } + + while (index < length) { + ch = source[index]; + if (!isIdentifierPart(ch)) { + break; + } + if (ch === '\\') { + index += 1; + if (source[index] !== 'u') { + return; + } + index += 1; + restore = index; + ch = scanHexEscape('u'); + if (ch) { + if (ch === '\\' || !isIdentifierPart(ch)) { + return; + } + id += ch; + } else { + index = restore; + id += 'u'; + } + } else { + id += nextChar(); + } + } + + // There is no keyword or literal with only one character. + // Thus, it must be an identifier. + if (id.length === 1) { + return { + type: Token.Identifier, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (isKeyword(id)) { + return { + type: Token.Keyword, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 7.8.1 Null Literals + + if (id === 'null') { + return { + type: Token.NullLiteral, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 7.8.2 Boolean Literals + + if (id === 'true' || id === 'false') { + return { + type: Token.BooleanLiteral, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + return { + type: Token.Identifier, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 7.7 Punctuators + + function scanPunctuator() { + var start = index, + ch1 = source[index], + ch2, + ch3, + ch4; + + // Check for most common single-character punctuators. + + if (ch1 === ';' || ch1 === '{' || ch1 === '}') { + index += 1; + return { + type: Token.Punctuator, + value: ch1, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === ',' || ch1 === '(' || ch1 === ')') { + index += 1; + return { + type: Token.Punctuator, + value: ch1, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // Dot (.) can also start a floating-point number, hence the need + // to check the next character. + + ch2 = source[index + 1]; + if (ch1 === '.' && !isDecimalDigit(ch2)) { + return { + type: Token.Punctuator, + value: nextChar(), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // Peek more characters. + + ch3 = source[index + 2]; + ch4 = source[index + 3]; + + // 4-character punctuator: >>>= + + if (ch1 === '>' && ch2 === '>' && ch3 === '>') { + if (ch4 === '=') { + index += 4; + return { + type: Token.Punctuator, + value: '>>>=', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + } + + // 3-character punctuators: === !== >>> <<= >>= + + if (ch1 === '=' && ch2 === '=' && ch3 === '=') { + index += 3; + return { + type: Token.Punctuator, + value: '===', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '!' && ch2 === '=' && ch3 === '=') { + index += 3; + return { + type: Token.Punctuator, + value: '!==', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '>' && ch2 === '>' && ch3 === '>') { + index += 3; + return { + type: Token.Punctuator, + value: '>>>', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '<' && ch2 === '<' && ch3 === '=') { + index += 3; + return { + type: Token.Punctuator, + value: '<<=', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '>' && ch2 === '>' && ch3 === '=') { + index += 3; + return { + type: Token.Punctuator, + value: '>>=', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 2-character punctuators: <= >= == != ++ -- << >> && || + // += -= *= %= &= |= ^= /= + + if (ch2 === '=') { + if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { + index += 2; + return { + type: Token.Punctuator, + value: ch1 + ch2, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + } + + if (ch1 === ch2 && ('+-<>&|'.indexOf(ch1) >= 0)) { + if ('+-<>&|'.indexOf(ch2) >= 0) { + index += 2; + return { + type: Token.Punctuator, + value: ch1 + ch2, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + } + + // The remaining 1-character punctuators. + + if ('[]<>+-*%&|^!~?:=/'.indexOf(ch1) >= 0) { + return { + type: Token.Punctuator, + value: nextChar(), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + } + + // 7.8.3 Numeric Literals + + function scanNumericLiteral() { + var number, start, ch; + + ch = source[index]; + assert(isDecimalDigit(ch) || (ch === '.'), + 'Numeric literal must start with a decimal digit or a decimal point'); + + start = index; + number = ''; + if (ch !== '.') { + number = nextChar(); + ch = source[index]; + + // Hex number starts with '0x'. + // Octal number starts with '0'. + if (number === '0') { + if (ch === 'x' || ch === 'X') { + number += nextChar(); + while (index < length) { + ch = source[index]; + if (!isHexDigit(ch)) { + break; + } + number += nextChar(); + } + + if (number.length <= 2) { + // only 0x + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + if (index < length) { + ch = source[index]; + if (isIdentifierStart(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + return { + type: Token.NumericLiteral, + value: parseInt(number, 16), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } else if (isOctalDigit(ch)) { + number += nextChar(); + while (index < length) { + ch = source[index]; + if (!isOctalDigit(ch)) { + break; + } + number += nextChar(); + } + + if (index < length) { + ch = source[index]; + if (isIdentifierStart(ch) || isDecimalDigit(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + return { + type: Token.NumericLiteral, + value: parseInt(number, 8), + octal: true, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // decimal number starts with '0' such as '09' is illegal. + if (isDecimalDigit(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + + while (index < length) { + ch = source[index]; + if (!isDecimalDigit(ch)) { + break; + } + number += nextChar(); + } + } + + if (ch === '.') { + number += nextChar(); + while (index < length) { + ch = source[index]; + if (!isDecimalDigit(ch)) { + break; + } + number += nextChar(); + } + } + + if (ch === 'e' || ch === 'E') { + number += nextChar(); + + ch = source[index]; + if (ch === '+' || ch === '-') { + number += nextChar(); + } + + ch = source[index]; + if (isDecimalDigit(ch)) { + number += nextChar(); + while (index < length) { + ch = source[index]; + if (!isDecimalDigit(ch)) { + break; + } + number += nextChar(); + } + } else { + ch = 'character ' + ch; + if (index >= length) { + ch = '<end>'; + } + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + + if (index < length) { + ch = source[index]; + if (isIdentifierStart(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + + return { + type: Token.NumericLiteral, + value: parseFloat(number), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 7.8.4 String Literals + + function scanStringLiteral() { + var str = '', quote, start, ch, code, unescaped, restore, octal = false; + + quote = source[index]; + assert((quote === '\'' || quote === '"'), + 'String literal must starts with a quote'); + + start = index; + index += 1; + + while (index < length) { + ch = nextChar(); + + if (ch === quote) { + quote = ''; + break; + } else if (ch === '\\') { + ch = nextChar(); + if (!isLineTerminator(ch)) { + switch (ch) { + case 'n': + str += '\n'; + break; + case 'r': + str += '\r'; + break; + case 't': + str += '\t'; + break; + case 'u': + case 'x': + restore = index; + unescaped = scanHexEscape(ch); + if (unescaped) { + str += unescaped; + } else { + index = restore; + str += ch; + } + break; + case 'b': + str += '\b'; + break; + case 'f': + str += '\f'; + break; + case 'v': + str += '\v'; + break; + + default: + if (isOctalDigit(ch)) { + code = '01234567'.indexOf(ch); + + // \0 is not octal escape sequence + if (code !== 0) { + octal = true; + } + + if (index < length && isOctalDigit(source[index])) { + octal = true; + code = code * 8 + '01234567'.indexOf(nextChar()); + + // 3 digits are only allowed when string starts + // with 0, 1, 2, 3 + if ('0123'.indexOf(ch) >= 0 && + index < length && + isOctalDigit(source[index])) { + code = code * 8 + '01234567'.indexOf(nextChar()); + } + } + str += String.fromCharCode(code); + } else { + str += ch; + } + break; + } + } else { + lineNumber += 1; + if (ch === '\r' && source[index] === '\n') { + index += 1; + } + } + } else if (isLineTerminator(ch)) { + break; + } else { + str += ch; + } + } + + if (quote !== '') { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + return { + type: Token.StringLiteral, + value: str, + octal: octal, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + function scanRegExp() { + var str = '', ch, start, pattern, flags, value, classMarker = false, restore; + + buffer = null; + skipComment(); + + start = index; + ch = source[index]; + assert(ch === '/', 'Regular expression literal must start with a slash'); + str = nextChar(); + + while (index < length) { + ch = nextChar(); + str += ch; + if (classMarker) { + if (ch === ']') { + classMarker = false; + } + } else { + if (ch === '\\') { + str += nextChar(); + } + if (ch === '/') { + break; + } + if (ch === '[') { + classMarker = true; + } + if (isLineTerminator(ch)) { + throwError({}, Messages.UnterminatedRegExp); + } + } + } + + if (str.length === 1) { + throwError({}, Messages.UnterminatedRegExp); + } + + // Exclude leading and trailing slash. + pattern = str.substr(1, str.length - 2); + + flags = ''; + while (index < length) { + ch = source[index]; + if (!isIdentifierPart(ch)) { + break; + } + + index += 1; + if (ch === '\\' && index < length) { + ch = source[index]; + if (ch === 'u') { + index += 1; + restore = index; + ch = scanHexEscape('u'); + if (ch) { + flags += ch; + str += '\\u'; + for (; restore < index; restore += 1) { + str += source[restore]; + } + } else { + index = restore; + flags += 'u'; + str += '\\u'; + } + } else { + str += '\\'; + } + } else { + flags += ch; + str += ch; + } + } + + try { + value = new RegExp(pattern, flags); + } catch (e) { + throwError({}, Messages.InvalidRegExp); + } + + return { + literal: str, + value: value, + 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, token; + + skipComment(); + + if (index >= length) { + return { + type: Token.EOF, + lineNumber: lineNumber, + lineStart: lineStart, + range: [index, index] + }; + } + + token = scanPunctuator(); + if (typeof token !== 'undefined') { + return token; + } + + ch = source[index]; + + if (ch === '\'' || ch === '"') { + return scanStringLiteral(); + } + + if (ch === '.' || isDecimalDigit(ch)) { + return scanNumericLiteral(); + } + + token = scanIdentifier(); + if (typeof token !== 'undefined') { + return token; + } + + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + function lex() { + var token; + + if (buffer) { + index = buffer.range[1]; + lineNumber = buffer.lineNumber; + lineStart = buffer.lineStart; + token = buffer; + buffer = null; + return token; + } + + buffer = null; + return advance(); + } + + function lookahead() { + var pos, line, start; + + if (buffer !== null) { + return buffer; + } + + pos = index; + line = lineNumber; + start = lineStart; + buffer = advance(); + index = pos; + lineNumber = line; + lineStart = start; + + return buffer; + } + + // Return true if there is a line terminator before the next token. + + function peekLineTerminator() { + var pos, line, start, found; + + pos = index; + line = lineNumber; + start = lineStart; + skipComment(); + found = lineNumber !== line; + index = pos; + lineNumber = line; + lineStart = start; + + return found; + } + + // Throw an exception + + function throwError(token, messageFormat) { + var error, + args = Array.prototype.slice.call(arguments, 2), + msg = messageFormat.replace( + /%(\d)/g, + function (whole, index) { + return args[index] || ''; + } + ); + + if (typeof token.lineNumber === 'number') { + error = new Error('Line ' + token.lineNumber + ': ' + msg); + error.index = token.range[0]; + error.lineNumber = token.lineNumber; + error.column = token.range[0] - lineStart + 1; + } else { + error = new Error('Line ' + lineNumber + ': ' + msg); + error.index = index; + error.lineNumber = lineNumber; + error.column = index - lineStart + 1; + } + + throw error; + } + + // Throw an exception because of the token. + + function throwUnexpected(token) { + var s; + + if (token.type === Token.EOF) { + throwError(token, Messages.UnexpectedEOS); + } + + if (token.type === Token.NumericLiteral) { + throwError(token, Messages.UnexpectedNumber); + } + + if (token.type === Token.StringLiteral) { + throwError(token, Messages.UnexpectedString); + } + + if (token.type === Token.Identifier) { + throwError(token, Messages.UnexpectedIdentifier); + } + + if (token.type === Token.Keyword) { + if (isFutureReservedWord(token.value)) { + throwError(token, Messages.UnexpectedReserved); + } else if (strict && isStrictModeReservedWord(token.value)) { + throwError(token, Messages.StrictReservedWord); + } + throwError(token, Messages.UnexpectedToken, token.value); + } + + // BooleanLiteral, NullLiteral, or Punctuator. + 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); + } + } + + // Expect the next token to match the specified keyword. + // If not, an exception will be thrown. + + function expectKeyword(keyword) { + var token = lex(); + if (token.type !== Token.Keyword || token.value !== keyword) { + throwUnexpected(token); + } + } + + // Return true if the next token matches the specified punctuator. + + function match(value) { + var token = lookahead(); + return token.type === Token.Punctuator && token.value === value; + } + + // Return true if the next token matches the specified keyword + + function matchKeyword(keyword) { + var token = lookahead(); + return token.type === Token.Keyword && token.value === keyword; + } + + // Return true if the next token is an assignment operator + + function matchAssign() { + var token = lookahead(), + op = token.value; + + if (token.type !== Token.Punctuator) { + return false; + } + return op === '=' || + op === '*=' || + op === '/=' || + op === '%=' || + op === '+=' || + op === '-=' || + op === '<<=' || + op === '>>=' || + op === '>>>=' || + op === '&=' || + op === '^=' || + op === '|='; + } + + function consumeSemicolon() { + var token, line; + + // Catch the very common case first. + if (source[index] === ';') { + lex(); + return; + } + + line = lineNumber; + skipComment(); + if (lineNumber !== line) { + return; + } + + if (match(';')) { + lex(); + return; + } + + token = lookahead(); + if (token.type !== Token.EOF && !match('}')) { + throwUnexpected(token); + } + return; + } + + // Return true if provided expression is LeftHandSideExpression + + function isLeftHandSide(expr) { + switch (expr.type) { + case 'AssignmentExpression': + case 'BinaryExpression': + case 'ConditionalExpression': + case 'LogicalExpression': + case 'SequenceExpression': + case 'UnaryExpression': + case 'UpdateExpression': + return false; + } + return true; + } + + // 11.1.4 Array Initialiser + + function parseArrayInitialiser() { + var elements = [], + undef; + + expect('['); + + while (!match(']')) { + if (match(',')) { + lex(); + elements.push(undef); + } else { + elements.push(parseAssignmentExpression()); + + if (!match(']')) { + expect(','); + } + } + } + + expect(']'); + + return { + type: Syntax.ArrayExpression, + elements: elements + }; + } + + // 11.1.5 Object Initialiser + + function parsePropertyFunction(param, first) { + var previousStrict, body; + + previousStrict = strict; + body = parseFunctionSourceElements(); + if (first && strict && isRestrictedWord(param[0].name)) { + throwError(first, Messages.StrictParamName); + } + strict = previousStrict; + + return { + type: Syntax.FunctionExpression, + id: null, + params: param, + body: body + }; + } + + function parseObjectPropertyKey() { + var token = lex(), + key; + + switch (token.type) { + + case Token.StringLiteral: + case Token.NumericLiteral: + if (strict && token.octal) { + throwError(token, Messages.StrictOctalLiteral); + } + key = createLiteral(token); + break; + + case Token.Identifier: + case Token.Keyword: + case Token.BooleanLiteral: + case Token.NullLiteral: + key = { + type: Syntax.Identifier, + name: token.value + }; + break; + + default: + // Unreachable, since parseObjectProperty() will not call this + // function with any other type of token. + } + + return key; + } + + function parseObjectProperty() { + var token, property, key, id, param; + + token = lookahead(); + + switch (token.type) { + + case Token.Identifier: + id = parseObjectPropertyKey(); + + // Property Assignment: Getter and Setter. + + if (token.value === 'get' && !match(':')) { + key = parseObjectPropertyKey(); + expect('('); + expect(')'); + property = { + type: Syntax.Property, + key: key, + value: parsePropertyFunction([]), + kind: 'get' + }; + } else if (token.value === 'set' && !match(':')) { + key = parseObjectPropertyKey(); + expect('('); + token = lookahead(); + if (token.type !== Token.Identifier) { + throwUnexpected(lex()); + } + param = [ parseVariableIdentifier() ]; + expect(')'); + property = { + type: Syntax.Property, + key: key, + value: parsePropertyFunction(param, token), + kind: 'set' + }; + } else { + expect(':'); + property = { + type: Syntax.Property, + key: id, + value: parseAssignmentExpression(), + kind: 'init' + }; + } + break; + + case Token.Keyword: + case Token.BooleanLiteral: + case Token.NullLiteral: + case Token.StringLiteral: + case Token.NumericLiteral: + key = parseObjectPropertyKey(); + expect(':'); + property = { + type: Syntax.Property, + key: key, + value: parseAssignmentExpression(), + kind: 'init' + }; + break; + + default: + throwUnexpected(token); + } + + return property; + } + + function parseObjectInitialiser() { + var token, properties = [], property, name, kind, map = {}, toString = String; + + expect('{'); + + while (!match('}')) { + property = parseObjectProperty(); + + if (property.key.type === Syntax.Identifier) { + name = property.key.name; + } else { + name = toString(property.key.value); + } + kind = (property.kind === 'init') ? PropertyKind.Data : (property.kind === 'get') ? PropertyKind.Get : PropertyKind.Set; + if (Object.prototype.hasOwnProperty.call(map, name)) { + if (map[name] === PropertyKind.Data) { + if (strict && kind === PropertyKind.Data) { + throwError({}, Messages.StrictDuplicateProperty); + } else if (kind !== PropertyKind.Data) { + throwError({}, Messages.AccessorDataProperty); + } + } else { + if (kind === PropertyKind.Data) { + throwError({}, Messages.AccessorDataProperty); + } else if (map[name] & kind) { + throwError({}, Messages.AccessorGetSet); + } + } + map[name] |= kind; + } else { + map[name] = kind; + } + + properties.push(property); + + if (!match('}')) { + expect(','); + } + } + + expect('}'); + + return { + type: Syntax.ObjectExpression, + properties: properties + }; + } + + // 11.1 Primary Expressions + + function parsePrimaryExpression() { + var expr, + token = lookahead(), + type = token.type; + + if (type === Token.Identifier) { + return { + type: Syntax.Identifier, + name: lex().value + }; + } + + if (type === Token.StringLiteral || type === Token.NumericLiteral) { + if (strict && token.octal) { + throwError(token, Messages.StrictOctalLiteral); + } + return createLiteral(lex()); + } + + if (type === Token.Keyword) { + if (matchKeyword('this')) { + lex(); + return { + type: Syntax.ThisExpression + }; + } + + if (matchKeyword('function')) { + return parseFunctionExpression(); + } + } + + if (type === Token.BooleanLiteral) { + lex(); + token.value = (token.value === 'true'); + return createLiteral(token); + } + + if (type === Token.NullLiteral) { + lex(); + token.value = null; + return createLiteral(token); + } + + if (match('[')) { + return parseArrayInitialiser(); + } + + if (match('{')) { + return parseObjectInitialiser(); + } + + if (match('(')) { + lex(); + lastParenthesized = expr = parseExpression(); + expect(')'); + return expr; + } + + if (match('/') || match('/=')) { + return createLiteral(scanRegExp()); + } + + return throwUnexpected(lex()); + } + + // 11.2 Left-Hand-Side Expressions + + function parseArguments() { + var args = []; + + expect('('); + + if (!match(')')) { + while (index < length) { + args.push(parseAssignmentExpression()); + if (match(')')) { + break; + } + expect(','); + } + } + + expect(')'); + + return args; + } + + function parseNonComputedProperty() { + var token = lex(); + + if (!isIdentifierName(token)) { + throwUnexpected(token); + } + + return { + type: Syntax.Identifier, + name: token.value + }; + } + + function parseNonComputedMember(object) { + return { + type: Syntax.MemberExpression, + computed: false, + object: object, + property: parseNonComputedProperty() + }; + } + + function parseComputedMember(object) { + var property, expr; + + expect('['); + property = parseExpression(); + expr = { + type: Syntax.MemberExpression, + computed: true, + object: object, + property: property + }; + expect(']'); + return expr; + } + + function parseCallMember(object) { + return { + type: Syntax.CallExpression, + callee: object, + 'arguments': parseArguments() + }; + } + + function parseNewExpression() { + var expr; + + expectKeyword('new'); + + expr = { + type: Syntax.NewExpression, + callee: parseLeftHandSideExpression(), + 'arguments': [] + }; + + if (match('(')) { + expr['arguments'] = parseArguments(); + } + + return expr; + } + + function parseLeftHandSideExpressionAllowCall() { + var useNew, expr; + + useNew = matchKeyword('new'); + expr = useNew ? parseNewExpression() : parsePrimaryExpression(); + + while (index < length) { + if (match('.')) { + lex(); + expr = parseNonComputedMember(expr); + } else if (match('[')) { + expr = parseComputedMember(expr); + } else if (match('(')) { + expr = parseCallMember(expr); + } else { + break; + } + } + + return expr; + } + + function parseLeftHandSideExpression() { + var useNew, expr; + + useNew = matchKeyword('new'); + expr = useNew ? parseNewExpression() : parsePrimaryExpression(); + + while (index < length) { + if (match('.')) { + lex(); + expr = parseNonComputedMember(expr); + } else if (match('[')) { + expr = parseComputedMember(expr); + } else { + break; + } + } + + return expr; + } + + // 11.3 Postfix Expressions + + function parsePostfixExpression() { + var expr = parseLeftHandSideExpressionAllowCall(); + + if ((match('++') || match('--')) && !peekLineTerminator()) { + // 11.3.1, 11.3.2 + if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) { + throwError({}, Messages.StrictLHSPostfix); + } + expr = { + type: Syntax.UpdateExpression, + operator: lex().value, + argument: expr, + prefix: false + }; + } + + return expr; + } + + // 11.4 Unary Operators + + function parseUnaryExpression() { + var token, expr; + + if (match('++') || match('--')) { + token = lex(); + expr = parseUnaryExpression(); + // 11.4.4, 11.4.5 + if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) { + throwError({}, Messages.StrictLHSPrefix); + } + expr = { + type: Syntax.UpdateExpression, + operator: token.value, + argument: expr, + prefix: true + }; + return expr; + } + + if (match('+') || match('-') || match('~') || match('!')) { + expr = { + type: Syntax.UnaryExpression, + operator: lex().value, + argument: parseUnaryExpression() + }; + return expr; + } + + if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { + expr = { + type: Syntax.UnaryExpression, + operator: lex().value, + argument: parseUnaryExpression() + }; + if (strict && expr.operator === 'delete' && expr.argument.type === Syntax.Identifier) { + throwError({}, Messages.StrictDelete); + } + return expr; + } + + return parsePostfixExpression(); + } + + // 11.5 Multiplicative Operators + + function parseMultiplicativeExpression() { + var expr = parseUnaryExpression(); + + while (match('*') || match('/') || match('%')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseUnaryExpression() + }; + } + + return expr; + } + + // 11.6 Additive Operators + + function parseAdditiveExpression() { + var expr = parseMultiplicativeExpression(); + + while (match('+') || match('-')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseMultiplicativeExpression() + }; + } + + return expr; + } + + // 11.7 Bitwise Shift Operators + + function parseShiftExpression() { + var expr = parseAdditiveExpression(); + + while (match('<<') || match('>>') || match('>>>')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseAdditiveExpression() + }; + } + + return expr; + } + // 11.8 Relational Operators + + function parseRelationalExpression() { + var expr, previousAllowIn; + + previousAllowIn = allowIn; + allowIn = true; + expr = parseShiftExpression(); + allowIn = previousAllowIn; + + if (match('<') || match('>') || match('<=') || match('>=')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseRelationalExpression() + }; + } else if (allowIn && matchKeyword('in')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: 'in', + left: expr, + right: parseRelationalExpression() + }; + } else if (matchKeyword('instanceof')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: 'instanceof', + left: expr, + right: parseRelationalExpression() + }; + } + + return expr; + } + + // 11.9 Equality Operators + + function parseEqualityExpression() { + var expr = parseRelationalExpression(); + + while (match('==') || match('!=') || match('===') || match('!==')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseRelationalExpression() + }; + } + + return expr; + } + + // 11.10 Binary Bitwise Operators + + function parseBitwiseANDExpression() { + var expr = parseEqualityExpression(); + + while (match('&')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: '&', + left: expr, + right: parseEqualityExpression() + }; + } + + return expr; + } + + function parseBitwiseORExpression() { + var expr = parseBitwiseANDExpression(); + + while (match('|')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: '|', + left: expr, + right: parseBitwiseANDExpression() + }; + } + + return expr; + } + + function parseBitwiseXORExpression() { + var expr = parseBitwiseORExpression(); + + while (match('^')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: '^', + left: expr, + right: parseBitwiseORExpression() + }; + } + + return expr; + } + + // 11.11 Binary Logical Operators + + function parseLogicalANDExpression() { + var expr = parseBitwiseXORExpression(); + + while (match('&&')) { + lex(); + expr = { + type: Syntax.LogicalExpression, + operator: '&&', + left: expr, + right: parseBitwiseXORExpression() + }; + } + + return expr; + } + + function parseLogicalORExpression() { + var expr = parseLogicalANDExpression(); + + while (match('||')) { + lex(); + expr = { + type: Syntax.LogicalExpression, + operator: '||', + left: expr, + right: parseLogicalANDExpression() + }; + } + + return expr; + } + + // 11.12 Conditional Operator + + function parseConditionalExpression() { + var expr, previousAllowIn, consequent; + + expr = parseLogicalORExpression(); + + if (match('?')) { + lex(); + previousAllowIn = allowIn; + allowIn = true; + consequent = parseAssignmentExpression(); + allowIn = previousAllowIn; + expect(':'); + + expr = { + type: Syntax.ConditionalExpression, + test: expr, + consequent: consequent, + alternate: parseAssignmentExpression() + }; + } + + return expr; + } + + // 11.13 Assignment Operators + + function parseAssignmentExpression() { + var expr; + + expr = parseConditionalExpression(); + + if (matchAssign()) { + // LeftHandSideExpression + if (lastParenthesized !== expr && !isLeftHandSide(expr)) { + throwError({}, Messages.InvalidLHSInAssignment); + } + + // 11.13.1 + if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) { + throwError({}, Messages.StrictLHSAssignment); + } + + expr = { + type: Syntax.AssignmentExpression, + operator: lex().value, + left: expr, + right: parseAssignmentExpression() + }; + } + + return expr; + } + + // 11.14 Comma Operator + + function parseExpression() { + var expr = parseAssignmentExpression(); + + if (match(',')) { + expr = { + type: Syntax.SequenceExpression, + expressions: [ expr ] + }; + + while (index < length) { + if (!match(',')) { + break; + } + lex(); + expr.expressions.push(parseAssignmentExpression()); + } + + } + return expr; + } + + // 12.1 Block + + function parseStatementList() { + var list = [], + statement; + + while (index < length) { + if (match('}')) { + break; + } + statement = parseSourceElement(); + if (typeof statement === 'undefined') { + break; + } + list.push(statement); + } + + return list; + } + + function parseBlock() { + var block; + + expect('{'); + + block = parseStatementList(); + + expect('}'); + + return { + type: Syntax.BlockStatement, + body: block + }; + } + + // 12.2 Variable Statement + + function parseVariableIdentifier() { + var token = lex(); + + if (token.type !== Token.Identifier) { + throwUnexpected(token); + } + + return { + type: Syntax.Identifier, + name: token.value + }; + } + + function parseVariableDeclaration(kind) { + var id = parseVariableIdentifier(), + init = null; + + // 12.2.1 + if (strict && isRestrictedWord(id.name)) { + throwError({}, Messages.StrictVarName); + } + + if (kind === 'const') { + expect('='); + init = parseAssignmentExpression(); + } else if (match('=')) { + lex(); + init = parseAssignmentExpression(); + } + + return { + type: Syntax.VariableDeclarator, + id: id, + init: init + }; + } + + function parseVariableDeclarationList(kind) { + var list = []; + + while (index < length) { + list.push(parseVariableDeclaration(kind)); + if (!match(',')) { + break; + } + lex(); + } + + return list; + } + + function parseVariableStatement() { + var declarations; + + expectKeyword('var'); + + declarations = parseVariableDeclarationList(); + + consumeSemicolon(); + + return { + type: Syntax.VariableDeclaration, + declarations: declarations, + kind: 'var' + }; + } + + // kind may be `const` or `let` + // Both are experimental and not in the specification yet. + // see http://wiki.ecmascript.org/doku.php?id=harmony:const + // and http://wiki.ecmascript.org/doku.php?id=harmony:let + function parseConstLetDeclaration(kind) { + var declarations; + + expectKeyword(kind); + + declarations = parseVariableDeclarationList(kind); + + consumeSemicolon(); + + return { + type: Syntax.VariableDeclaration, + declarations: declarations, + kind: kind + }; + } + + // 12.3 Empty Statement + + function parseEmptyStatement() { + expect(';'); + + return { + type: Syntax.EmptyStatement + }; + } + + // 12.4 Expression Statement + + function parseExpressionStatement() { + var expr = parseExpression(); + + consumeSemicolon(); + + return { + type: Syntax.ExpressionStatement, + expression: expr + }; + } + + // 12.5 If statement + + function parseIfStatement() { + var test, consequent, alternate; + + expectKeyword('if'); + + expect('('); + + test = parseExpression(); + + expect(')'); + + consequent = parseStatement(); + + if (matchKeyword('else')) { + lex(); + alternate = parseStatement(); + } else { + alternate = null; + } + + return { + type: Syntax.IfStatement, + test: test, + consequent: consequent, + alternate: alternate + }; + } + + // 12.6 Iteration Statements + + function parseDoWhileStatement() { + var body, test, oldInIteration; + + expectKeyword('do'); + + oldInIteration = inIteration; + inIteration = true; + + body = parseStatement(); + + inIteration = oldInIteration; + + expectKeyword('while'); + + expect('('); + + test = parseExpression(); + + expect(')'); + + if (match(';')) { + lex(); + } + + return { + type: Syntax.DoWhileStatement, + body: body, + test: test + }; + } + + function parseWhileStatement() { + var test, body, oldInIteration; + + expectKeyword('while'); + + expect('('); + + test = parseExpression(); + + expect(')'); + + oldInIteration = inIteration; + inIteration = true; + + body = parseStatement(); + + inIteration = oldInIteration; + + return { + type: Syntax.WhileStatement, + test: test, + body: body + }; + } + + function parseForVariableDeclaration() { + var token = lex(); + + return { + type: Syntax.VariableDeclaration, + declarations: parseVariableDeclarationList(), + kind: token.value + }; + } + + function parseForStatement() { + var init, test, update, left, right, body, oldInIteration; + + init = test = update = null; + + expectKeyword('for'); + + expect('('); + + if (match(';')) { + lex(); + } else { + if (matchKeyword('var') || matchKeyword('let')) { + allowIn = false; + init = parseForVariableDeclaration(); + allowIn = true; + + if (init.declarations.length === 1 && matchKeyword('in')) { + lex(); + left = init; + right = parseExpression(); + init = null; + } + } else { + allowIn = false; + init = parseExpression(); + allowIn = true; + + if (matchKeyword('in')) { + // LeftHandSideExpression + if (matchKeyword('in') && (lastParenthesized !== init && !isLeftHandSide(init))) { + throwError({}, Messages.InvalidLHSInForIn); + } + lex(); + left = init; + right = parseExpression(); + init = null; + } + } + + if (typeof left === 'undefined') { + expect(';'); + } + } + + if (typeof left === 'undefined') { + + if (!match(';')) { + test = parseExpression(); + } + expect(';'); + + if (!match(')')) { + update = parseExpression(); + } + } + + expect(')'); + + oldInIteration = inIteration; + inIteration = true; + + body = parseStatement(); + + inIteration = oldInIteration; + + if (typeof left === 'undefined') { + return { + type: Syntax.ForStatement, + init: init, + test: test, + update: update, + body: body + }; + } + + return { + type: Syntax.ForInStatement, + left: left, + right: right, + body: body, + each: false + }; + } + + // 12.7 The continue statement + + function parseContinueStatement() { + var token, label = null; + + expectKeyword('continue'); + + // Optimize the most common form: 'continue;'. + if (source[index] === ';') { + lex(); + + if (!inIteration) { + throwError({}, Messages.IllegalContinue); + } + + return { + type: Syntax.ContinueStatement, + label: null + }; + } + + if (peekLineTerminator()) { + if (!inIteration) { + throwError({}, Messages.IllegalContinue); + } + + return { + type: Syntax.ContinueStatement, + label: null + }; + } + + token = lookahead(); + if (token.type === Token.Identifier) { + label = parseVariableIdentifier(); + + if (!Object.prototype.hasOwnProperty.call(labelSet, label.name)) { + throwError({}, Messages.UnknownLabel, label.name); + } + } + + consumeSemicolon(); + + if (label === null && !inIteration) { + throwError({}, Messages.IllegalContinue); + } + + return { + type: Syntax.ContinueStatement, + label: label + }; + } + + // 12.8 The break statement + + function parseBreakStatement() { + var token, label = null; + + expectKeyword('break'); + + // Optimize the most common form: 'break;'. + if (source[index] === ';') { + lex(); + + if (!(inIteration || inSwitch)) { + throwError({}, Messages.IllegalBreak); + } + + return { + type: Syntax.BreakStatement, + label: null + }; + } + + if (peekLineTerminator()) { + if (!(inIteration || inSwitch)) { + throwError({}, Messages.IllegalBreak); + } + + return { + type: Syntax.BreakStatement, + label: null + }; + } + + token = lookahead(); + if (token.type === Token.Identifier) { + label = parseVariableIdentifier(); + + if (!Object.prototype.hasOwnProperty.call(labelSet, label.name)) { + throwError({}, Messages.UnknownLabel, label.name); + } + } + + consumeSemicolon(); + + if (label === null && !(inIteration || inSwitch)) { + throwError({}, Messages.IllegalBreak); + } + + return { + type: Syntax.BreakStatement, + label: label + }; + } + + // 12.9 The return statement + + function parseReturnStatement() { + var token, argument = null; + + expectKeyword('return'); + + if (!inFunctionBody) { + throwError({}, Messages.IllegalReturn); + } + + // 'return' followed by a space and an identifier is very common. + if (source[index] === ' ') { + if (isIdentifierStart(source[index + 1])) { + argument = parseExpression(); + consumeSemicolon(); + return { + type: Syntax.ReturnStatement, + argument: argument + }; + } + } + + if (peekLineTerminator()) { + return { + type: Syntax.ReturnStatement, + argument: null + }; + } + + if (!match(';')) { + token = lookahead(); + if (!match('}') && token.type !== Token.EOF) { + argument = parseExpression(); + } + } + + consumeSemicolon(); + + return { + type: Syntax.ReturnStatement, + argument: argument + }; + } + + // 12.10 The with statement + + function parseWithStatement() { + var object, body; + + if (strict) { + throwError({}, Messages.StrictModeWith); + } + + expectKeyword('with'); + + expect('('); + + object = parseExpression(); + + expect(')'); + + body = parseStatement(); + + return { + type: Syntax.WithStatement, + object: object, + body: body + }; + } + + // 12.10 The swith statement + + function parseSwitchCase(test) { + var consequent = [], + statement; + + while (index < length) { + if (match('}') || matchKeyword('default') || matchKeyword('case')) { + break; + } + statement = parseStatement(); + if (typeof statement === 'undefined') { + break; + } + consequent.push(statement); + } + + return { + type: Syntax.SwitchCase, + test: test, + consequent: consequent + }; + } + + function parseSwitchStatement() { + var discriminant, cases, test, oldInSwitch; + + expectKeyword('switch'); + + expect('('); + + discriminant = parseExpression(); + + expect(')'); + + expect('{'); + + if (match('}')) { + lex(); + return { + type: Syntax.SwitchStatement, + discriminant: discriminant + }; + } + + cases = []; + + oldInSwitch = inSwitch; + inSwitch = true; + + while (index < length) { + if (match('}')) { + break; + } + + if (matchKeyword('default')) { + lex(); + test = null; + } else { + expectKeyword('case'); + test = parseExpression(); + } + expect(':'); + + cases.push(parseSwitchCase(test)); + } + + inSwitch = oldInSwitch; + + expect('}'); + + return { + type: Syntax.SwitchStatement, + discriminant: discriminant, + cases: cases + }; + } + + // 12.13 The throw statement + + function parseThrowStatement() { + var argument; + + expectKeyword('throw'); + + if (peekLineTerminator()) { + throwError({}, Messages.NewlineAfterThrow); + } + + argument = parseExpression(); + + consumeSemicolon(); + + return { + type: Syntax.ThrowStatement, + argument: argument + }; + } + + // 12.14 The try statement + + function parseCatchClause() { + var param; + + expectKeyword('catch'); + + expect('('); + if (!match(')')) { + param = parseExpression(); + // 12.14.1 + if (strict && param.type === Syntax.Identifier && isRestrictedWord(param.name)) { + throwError({}, Messages.StrictCatchVariable); + } + } + expect(')'); + + return { + type: Syntax.CatchClause, + param: param, + guard: null, + body: parseBlock() + }; + } + + function parseTryStatement() { + var block, handlers = [], finalizer = null; + + expectKeyword('try'); + + block = parseBlock(); + + if (matchKeyword('catch')) { + handlers.push(parseCatchClause()); + } + + if (matchKeyword('finally')) { + lex(); + finalizer = parseBlock(); + } + + if (handlers.length === 0 && !finalizer) { + throwError({}, Messages.NoCatchOrFinally); + } + + return { + type: Syntax.TryStatement, + block: block, + handlers: handlers, + finalizer: finalizer + }; + } + + // 12.15 The debugger statement + + function parseDebuggerStatement() { + expectKeyword('debugger'); + + consumeSemicolon(); + + return { + type: Syntax.DebuggerStatement + }; + } + + // 12 Statements + + function parseStatement() { + var token = lookahead(), + expr, + labeledBody; + + if (token.type === Token.EOF) { + throwUnexpected(token); + } + + if (token.type === Token.Punctuator) { + switch (token.value) { + case ';': + return parseEmptyStatement(); + case '{': + return parseBlock(); + case '(': + return parseExpressionStatement(); + default: + break; + } + } + + if (token.type === Token.Keyword) { + switch (token.value) { + case 'break': + return parseBreakStatement(); + case 'continue': + return parseContinueStatement(); + case 'debugger': + return parseDebuggerStatement(); + case 'do': + return parseDoWhileStatement(); + case 'for': + return parseForStatement(); + case 'function': + return parseFunctionDeclaration(); + case 'if': + return parseIfStatement(); + case 'return': + return parseReturnStatement(); + case 'switch': + return parseSwitchStatement(); + case 'throw': + return parseThrowStatement(); + case 'try': + return parseTryStatement(); + case 'var': + return parseVariableStatement(); + case 'while': + return parseWhileStatement(); + case 'with': + return parseWithStatement(); + default: + break; + } + } + + expr = parseExpression(); + + // 12.12 Labelled Statements + if ((expr.type === Syntax.Identifier) && match(':')) { + lex(); + + if (Object.prototype.hasOwnProperty.call(labelSet, expr.name)) { + throwError({}, Messages.Redeclaration, 'Label', expr.name); + } + + labelSet[expr.name] = true; + labeledBody = parseStatement(); + delete labelSet[expr.name]; + + return { + type: Syntax.LabeledStatement, + label: expr, + body: labeledBody + }; + } + + consumeSemicolon(); + + return { + type: Syntax.ExpressionStatement, + expression: expr + }; + } + + // 13 Function Definition + + function parseFunctionSourceElements() { + var sourceElement, sourceElements = [], token, directive, firstRestricted, oldLabelSet, oldInIteration, oldInSwitch, oldInFunctionBody; + + expect('{'); + + while (index < length) { + token = lookahead(); + if (token.type !== Token.StringLiteral) { + break; + } + + sourceElement = parseSourceElement(); + sourceElements.push(sourceElement); + if (sourceElement.expression.type !== Syntax.Literal) { + // this is not directive + break; + } + directive = sliceSource(token.range[0] + 1, token.range[1] - 1); + if (directive === 'use strict') { + strict = true; + if (firstRestricted) { + throwError(firstRestricted, Messages.StrictOctalLiteral); + } + } else { + if (!firstRestricted && token.octal) { + firstRestricted = token; + } + } + } + + oldLabelSet = labelSet; + oldInIteration = inIteration; + oldInSwitch = inSwitch; + oldInFunctionBody = inFunctionBody; + + labelSet = {}; + inIteration = false; + inSwitch = false; + inFunctionBody = true; + + while (index < length) { + if (match('}')) { + break; + } + sourceElement = parseSourceElement(); + if (typeof sourceElement === 'undefined') { + break; + } + sourceElements.push(sourceElement); + } + + expect('}'); + + labelSet = oldLabelSet; + inIteration = oldInIteration; + inSwitch = oldInSwitch; + inFunctionBody = oldInFunctionBody; + + return { + type: Syntax.BlockStatement, + body: sourceElements + }; + } + + function parseFunctionDeclaration() { + var id, param, params = [], body, token, firstRestricted, message, previousStrict, paramSet; + + expectKeyword('function'); + token = lookahead(); + id = parseVariableIdentifier(); + if (strict) { + if (isRestrictedWord(token.value)) { + throwError(token, Messages.StrictFunctionName); + } + } else { + if (isRestrictedWord(token.value)) { + firstRestricted = token; + message = Messages.StrictFunctionName; + } else if (isStrictModeReservedWord(token.value)) { + firstRestricted = token; + message = Messages.StrictReservedWord; + } + } + + expect('('); + + if (!match(')')) { + paramSet = {}; + while (index < length) { + token = lookahead(); + param = parseVariableIdentifier(); + if (strict) { + if (isRestrictedWord(token.value)) { + throwError(token, Messages.StrictParamName); + } + if (Object.prototype.hasOwnProperty.call(paramSet, token.value)) { + throwError(token, Messages.StrictParamDupe); + } + } else if (!firstRestricted) { + if (isRestrictedWord(token.value)) { + firstRestricted = token; + message = Messages.StrictParamName; + } else if (isStrictModeReservedWord(token.value)) { + firstRestricted = token; + message = Messages.StrictReservedWord; + } else if (Object.prototype.hasOwnProperty.call(paramSet, token.value)) { + firstRestricted = token; + message = Messages.StrictParamDupe; + } + } + params.push(param); + paramSet[param.name] = true; + if (match(')')) { + break; + } + expect(','); + } + } + + expect(')'); + + previousStrict = strict; + body = parseFunctionSourceElements(); + if (strict && firstRestricted) { + throwError(firstRestricted, message); + } + strict = previousStrict; + + return { + type: Syntax.FunctionDeclaration, + id: id, + params: params, + body: body + }; + } + + function parseFunctionExpression() { + var token, id = null, firstRestricted, message, param, params = [], body, previousStrict, paramSet; + + expectKeyword('function'); + + if (!match('(')) { + token = lookahead(); + id = parseVariableIdentifier(); + if (strict) { + if (isRestrictedWord(token.value)) { + throwError(token, Messages.StrictFunctionName); + } + } else { + if (isRestrictedWord(token.value)) { + firstRestricted = token; + message = Messages.StrictFunctionName; + } else if (isStrictModeReservedWord(token.value)) { + firstRestricted = token; + message = Messages.StrictReservedWord; + } + } + } + + expect('('); + + if (!match(')')) { + paramSet = {}; + while (index < length) { + token = lookahead(); + param = parseVariableIdentifier(); + if (strict) { + if (isRestrictedWord(token.value)) { + throwError(token, Messages.StrictParamName); + } + if (Object.prototype.hasOwnProperty.call(paramSet, token.value)) { + throwError(token, Messages.StrictParamDupe); + } + } else if (!firstRestricted) { + if (isRestrictedWord(token.value)) { + firstRestricted = token; + message = Messages.StrictParamName; + } else if (isStrictModeReservedWord(token.value)) { + firstRestricted = token; + message = Messages.StrictReservedWord; + } else if (Object.prototype.hasOwnProperty.call(paramSet, token.value)) { + firstRestricted = token; + message = Messages.StrictParamDupe; + } + } + params.push(param); + paramSet[param.name] = true; + if (match(')')) { + break; + } + expect(','); + } + } + + expect(')'); + + previousStrict = strict; + body = parseFunctionSourceElements(); + if (strict && firstRestricted) { + throwError(firstRestricted, message); + } + strict = previousStrict; + + return { + type: Syntax.FunctionExpression, + id: id, + params: params, + body: body + }; + } + + // 14 Program + + function parseSourceElement() { + var token = lookahead(); + + if (token.type === Token.Keyword) { + switch (token.value) { + case 'const': + case 'let': + return parseConstLetDeclaration(token.value); + case 'function': + return parseFunctionDeclaration(); + default: + return parseStatement(); + } + } + + if (token.type !== Token.EOF) { + return parseStatement(); + } + } + + function parseSourceElements() { + var sourceElement, sourceElements = [], token, directive, firstRestricted; + + while (index < length) { + token = lookahead(); + if (token.type !== Token.StringLiteral) { + break; + } + + sourceElement = parseSourceElement(); + sourceElements.push(sourceElement); + if (sourceElement.expression.type !== Syntax.Literal) { + // this is not directive + break; + } + directive = sliceSource(token.range[0] + 1, token.range[1] - 1); + if (directive === 'use strict') { + strict = true; + if (firstRestricted) { + throwError(firstRestricted, Messages.StrictOctalLiteral); + } + } else { + if (!firstRestricted && token.octal) { + firstRestricted = token; + } + } + } + + while (index < length) { + sourceElement = parseSourceElement(); + if (typeof sourceElement === 'undefined') { + break; + } + sourceElements.push(sourceElement); + } + return sourceElements; + } + + function parseProgram() { + var program; + strict = false; + program = { + type: Syntax.Program, + body: parseSourceElements() + }; + return program; + } + + // The following functions are needed only when the option to preserve + // the comments is active. + + function addComment(start, end, type, value) { + if (typeof start !== 'number') { + return; + } + + // Because the way the actual token is scanned, often the comments + // (if any) are skipped twice during the lexical analysis. + // Thus, we need to skip adding a comment if the comment array already + // handled it. + if (extra.comments.length > 0) { + if (extra.comments[extra.comments.length - 1].range[1] > start) { + return; + } + } + + extra.comments.push({ + range: [start, end], + type: type, + value: value + }); + } + + function scanComment() { + var comment, ch, start, blockComment, lineComment; + + comment = ''; + blockComment = false; + lineComment = false; + + while (index < length) { + ch = source[index]; + + if (lineComment) { + ch = nextChar(); + if (isLineTerminator(ch)) { + lineComment = false; + addComment(start, index - 1, 'Line', comment); + if (ch === '\r' && source[index] === '\n') { + index += 1; + } + lineNumber += 1; + lineStart = index; + comment = ''; + } else { + comment += ch; + } + } else if (blockComment) { + if (isLineTerminator(ch)) { + if (ch === '\r' && source[index + 1] === '\n') { + index += 1; + } + lineNumber += 1; + index += 1; + lineStart = index; + if (index >= length) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } else { + ch = nextChar(); + if (index >= length) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + comment += ch; + if (ch === '*') { + ch = source[index]; + if (ch === '/') { + comment = comment.substr(0, comment.length - 1); + blockComment = false; + index += 1; + addComment(start, index - 1, 'Block', comment); + comment = ''; + } + } + } + } else if (ch === '/') { + ch = source[index + 1]; + if (ch === '/') { + start = index; + index += 2; + lineComment = true; + } else if (ch === '*') { + start = index; + index += 2; + blockComment = true; + if (index >= length) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } else { + break; + } + } else if (isWhiteSpace(ch)) { + index += 1; + } else if (isLineTerminator(ch)) { + index += 1; + if (ch === '\r' && source[index] === '\n') { + index += 1; + } + lineNumber += 1; + lineStart = index; + } else { + break; + } + } + + addComment(start, index, (blockComment) ? 'Block' : 'Line', comment); + } + + function collectToken() { + var token = extra.advance(), + range, + value; + + if (token.type !== Token.EOF) { + range = [token.range[0], token.range[1] - 1]; + value = sliceSource(token.range[0], token.range[1]); + extra.tokens.push({ + type: TokenName[token.type], + value: value, + range: range + }); + } + + return token; + } + + function collectRegex() { + var pos, regex, token; + + skipComment(); + + pos = index; + regex = extra.scanRegExp(); + + // Pop the previous token, which is likely '/' or '/=' + if (extra.tokens.length > 0) { + token = extra.tokens[extra.tokens.length - 1]; + if (token.range[0] === pos && token.type === 'Punctuator') { + if (token.value === '/' || token.value === '/=') { + extra.tokens.pop(); + } + } + } + + extra.tokens.push({ + type: 'RegularExpression', + value: regex.literal, + range: [pos, index - 1] + }); + + return regex; + } + + function createLiteral(token) { + return { + type: Syntax.Literal, + value: token.value + }; + } + + function createRawLiteral(token) { + return { + type: Syntax.Literal, + value: token.value, + raw: sliceSource(token.range[0], token.range[1]) + }; + } + + function wrapTrackingFunction(range, loc) { + + return function (parseFunction) { + + function isBinary(node) { + return node.type === Syntax.LogicalExpression || + node.type === Syntax.BinaryExpression; + } + + function visit(node) { + if (range) { + if (isBinary(node.left) && (typeof node.left.range === 'undefined')) { + visit(node.left); + } + if (isBinary(node.right) && (typeof node.right.range === 'undefined')) { + visit(node.right); + } + + // Expression enclosed in brackets () already has the correct range. + if (typeof node.range === 'undefined') { + node.range = [node.left.range[0], node.right.range[1]]; + } + } + + if (loc) { + if (isBinary(node.left) && (typeof node.left.loc === 'undefined')) { + visit(node.left); + } + if (isBinary(node.right) && (typeof node.right.loc === 'undefined')) { + visit(node.right); + } + + if (typeof node.loc === 'undefined') { + node.loc = { + start: node.left.loc.start, + end: node.right.loc.end + }; + } + } + } + + return function () { + var node, rangeInfo, locInfo; + + skipComment(); + rangeInfo = [index, 0]; + locInfo = { + start: { + line: lineNumber, + column: index - lineStart + } + }; + + node = parseFunction.apply(null, arguments); + if (typeof node !== 'undefined') { + + if (range) { + rangeInfo[1] = index - 1; + node.range = rangeInfo; + } + + if (loc) { + locInfo.end = { + line: lineNumber, + column: index - lineStart + }; + node.loc = locInfo; + } + + if (isBinary(node)) { + visit(node); + } + + if (node.type === Syntax.MemberExpression) { + if (typeof node.object.range !== 'undefined') { + node.range[0] = node.object.range[0]; + } + if (typeof node.object.loc !== 'undefined') { + node.loc.start = node.object.loc.start; + } + } + return node; + } + }; + + }; + } + + function patch() { + + var wrapTracking; + + if (extra.comments) { + extra.skipComment = skipComment; + skipComment = scanComment; + } + + if (extra.raw) { + extra.createLiteral = createLiteral; + createLiteral = createRawLiteral; + } + + if (extra.range || extra.loc) { + + wrapTracking = wrapTrackingFunction(extra.range, extra.loc); + + extra.parseAdditiveExpression = parseAdditiveExpression; + extra.parseAssignmentExpression = parseAssignmentExpression; + extra.parseBitwiseANDExpression = parseBitwiseANDExpression; + extra.parseBitwiseORExpression = parseBitwiseORExpression; + extra.parseBitwiseXORExpression = parseBitwiseXORExpression; + extra.parseBlock = parseBlock; + extra.parseFunctionSourceElements = parseFunctionSourceElements; + extra.parseCallMember = parseCallMember; + extra.parseCatchClause = parseCatchClause; + extra.parseComputedMember = parseComputedMember; + extra.parseConditionalExpression = parseConditionalExpression; + extra.parseConstLetDeclaration = parseConstLetDeclaration; + extra.parseEqualityExpression = parseEqualityExpression; + extra.parseExpression = parseExpression; + extra.parseForVariableDeclaration = parseForVariableDeclaration; + extra.parseFunctionDeclaration = parseFunctionDeclaration; + extra.parseFunctionExpression = parseFunctionExpression; + extra.parseLogicalANDExpression = parseLogicalANDExpression; + extra.parseLogicalORExpression = parseLogicalORExpression; + extra.parseMultiplicativeExpression = parseMultiplicativeExpression; + extra.parseNewExpression = parseNewExpression; + extra.parseNonComputedMember = parseNonComputedMember; + extra.parseNonComputedProperty = parseNonComputedProperty; + extra.parseObjectProperty = parseObjectProperty; + extra.parseObjectPropertyKey = parseObjectPropertyKey; + extra.parsePostfixExpression = parsePostfixExpression; + extra.parsePrimaryExpression = parsePrimaryExpression; + extra.parseProgram = parseProgram; + extra.parsePropertyFunction = parsePropertyFunction; + extra.parseRelationalExpression = parseRelationalExpression; + extra.parseStatement = parseStatement; + extra.parseShiftExpression = parseShiftExpression; + extra.parseSwitchCase = parseSwitchCase; + extra.parseUnaryExpression = parseUnaryExpression; + extra.parseVariableDeclaration = parseVariableDeclaration; + extra.parseVariableIdentifier = parseVariableIdentifier; + + parseAdditiveExpression = wrapTracking(extra.parseAdditiveExpression); + parseAssignmentExpression = wrapTracking(extra.parseAssignmentExpression); + parseBitwiseANDExpression = wrapTracking(extra.parseBitwiseANDExpression); + parseBitwiseORExpression = wrapTracking(extra.parseBitwiseORExpression); + parseBitwiseXORExpression = wrapTracking(extra.parseBitwiseXORExpression); + parseBlock = wrapTracking(extra.parseBlock); + parseFunctionSourceElements = wrapTracking(extra.parseFunctionSourceElements); + parseCallMember = wrapTracking(extra.parseCallMember); + parseCatchClause = wrapTracking(extra.parseCatchClause); + parseComputedMember = wrapTracking(extra.parseComputedMember); + parseConditionalExpression = wrapTracking(extra.parseConditionalExpression); + parseConstLetDeclaration = wrapTracking(extra.parseConstLetDeclaration); + parseEqualityExpression = wrapTracking(extra.parseEqualityExpression); + parseExpression = wrapTracking(extra.parseExpression); + parseForVariableDeclaration = wrapTracking(extra.parseForVariableDeclaration); + parseFunctionDeclaration = wrapTracking(extra.parseFunctionDeclaration); + parseFunctionExpression = wrapTracking(extra.parseFunctionExpression); + parseLogicalANDExpression = wrapTracking(extra.parseLogicalANDExpression); + parseLogicalORExpression = wrapTracking(extra.parseLogicalORExpression); + parseMultiplicativeExpression = wrapTracking(extra.parseMultiplicativeExpression); + parseNewExpression = wrapTracking(extra.parseNewExpression); + parseNonComputedMember = wrapTracking(extra.parseNonComputedMember); + parseNonComputedProperty = wrapTracking(extra.parseNonComputedProperty); + parseObjectProperty = wrapTracking(extra.parseObjectProperty); + parseObjectPropertyKey = wrapTracking(extra.parseObjectPropertyKey); + parsePostfixExpression = wrapTracking(extra.parsePostfixExpression); + parsePrimaryExpression = wrapTracking(extra.parsePrimaryExpression); + parseProgram = wrapTracking(extra.parseProgram); + parsePropertyFunction = wrapTracking(extra.parsePropertyFunction); + parseRelationalExpression = wrapTracking(extra.parseRelationalExpression); + parseStatement = wrapTracking(extra.parseStatement); + parseShiftExpression = wrapTracking(extra.parseShiftExpression); + parseSwitchCase = wrapTracking(extra.parseSwitchCase); + parseUnaryExpression = wrapTracking(extra.parseUnaryExpression); + parseVariableDeclaration = wrapTracking(extra.parseVariableDeclaration); + parseVariableIdentifier = wrapTracking(extra.parseVariableIdentifier); + } + + if (typeof extra.tokens !== 'undefined') { + extra.advance = advance; + extra.scanRegExp = scanRegExp; + + advance = collectToken; + scanRegExp = collectRegex; + } + } + + function unpatch() { + if (typeof extra.skipComment === 'function') { + skipComment = extra.skipComment; + } + + if (extra.raw) { + createLiteral = extra.createLiteral; + } + + if (extra.range || extra.loc) { + parseAdditiveExpression = extra.parseAdditiveExpression; + parseAssignmentExpression = extra.parseAssignmentExpression; + parseBitwiseANDExpression = extra.parseBitwiseANDExpression; + parseBitwiseORExpression = extra.parseBitwiseORExpression; + parseBitwiseXORExpression = extra.parseBitwiseXORExpression; + parseBlock = extra.parseBlock; + parseFunctionSourceElements = extra.parseFunctionSourceElements; + parseCallMember = extra.parseCallMember; + parseCatchClause = extra.parseCatchClause; + parseComputedMember = extra.parseComputedMember; + parseConditionalExpression = extra.parseConditionalExpression; + parseConstLetDeclaration = extra.parseConstLetDeclaration; + parseEqualityExpression = extra.parseEqualityExpression; + parseExpression = extra.parseExpression; + parseForVariableDeclaration = extra.parseForVariableDeclaration; + parseFunctionDeclaration = extra.parseFunctionDeclaration; + parseFunctionExpression = extra.parseFunctionExpression; + parseLogicalANDExpression = extra.parseLogicalANDExpression; + parseLogicalORExpression = extra.parseLogicalORExpression; + parseMultiplicativeExpression = extra.parseMultiplicativeExpression; + parseNewExpression = extra.parseNewExpression; + parseNonComputedMember = extra.parseNonComputedMember; + parseNonComputedProperty = extra.parseNonComputedProperty; + parseObjectProperty = extra.parseObjectProperty; + parseObjectPropertyKey = extra.parseObjectPropertyKey; + parsePrimaryExpression = extra.parsePrimaryExpression; + parsePostfixExpression = extra.parsePostfixExpression; + parseProgram = extra.parseProgram; + parsePropertyFunction = extra.parsePropertyFunction; + parseRelationalExpression = extra.parseRelationalExpression; + parseStatement = extra.parseStatement; + parseShiftExpression = extra.parseShiftExpression; + parseSwitchCase = extra.parseSwitchCase; + parseUnaryExpression = extra.parseUnaryExpression; + parseVariableDeclaration = extra.parseVariableDeclaration; + parseVariableIdentifier = extra.parseVariableIdentifier; + } + + if (typeof extra.scanRegExp === 'function') { + advance = extra.advance; + scanRegExp = extra.scanRegExp; + } + } + + function stringToArray(str) { + var length = str.length, + result = [], + i; + for (i = 0; i < length; i += 1) { + result[i] = str.charAt(i); + } + return result; + } + + function parse(code, options) { + var program, toString; + + toString = String; + if (typeof code !== 'string' && !(code instanceof String)) { + code = toString(code); + } + + source = code; + index = 0; + lineNumber = (source.length > 0) ? 1 : 0; + lineStart = 0; + length = source.length; + buffer = null; + allowIn = true; + labelSet = {}; + inSwitch = false; + inIteration = false; + lastParenthesized = null; + inFunctionBody = false; + + extra = {}; + if (typeof options !== 'undefined') { + extra.range = (typeof options.range === 'boolean') && options.range; + extra.loc = (typeof options.loc === 'boolean') && options.loc; + extra.raw = (typeof options.raw === 'boolean') && options.raw; + if (typeof options.tokens === 'boolean' && options.tokens) { + extra.tokens = []; + } + if (typeof options.comment === 'boolean' && options.comment) { + extra.comments = []; + } + } + + if (length > 0) { + if (typeof source[0] === 'undefined') { + // Try first to convert to a string. This is good as fast path + // for old IE which understands string indexing for string + // literals only and not for string object. + if (code instanceof String) { + source = code.valueOf(); + } + + // Force accessing the characters via an array. + if (typeof source[0] === 'undefined') { + source = stringToArray(code); + } + } + } + + patch(); + try { + program = parseProgram(); + if (typeof extra.comments !== 'undefined') { + program.comments = extra.comments; + } + if (typeof extra.tokens !== 'undefined') { + program.tokens = extra.tokens; + } + } catch (e) { + throw e; + } finally { + unpatch(); + extra = {}; + } + + return program; + } + + // Executes visitor on the object and its children (recursively). + + function traverse(object, visitor, master) { + var key, child, parent, path; + + parent = (typeof master === 'undefined') ? [] : master; + + if (visitor.call(null, object, parent) === false) { + return; + } + for (key in object) { + if (object.hasOwnProperty(key)) { + child = object[key]; + path = [ object ]; + path.push(parent); + if (typeof child === 'object' && child !== null) { + traverse(child, visitor, path); + } + } + } + } + + // Insert a prolog in the body of every function. + // It will be in the form of a function call: + // + // traceName(object); + // + // where the object contains the following properties: + // + // 'name' holds the name of the function + // 'lineNumber' holds the starting line number of the function block + // 'range' contains the index-based range of the function + // + // The name of the function represents the associated reference for + // the function (deduced on a best-effort basis if it is not + // a function declaration). + // + // If traceName is a function instead of a string, it will be invoked and + // the result will be used as the entire prolog. The arguments for the + // invocation are the function name, range, and location info. + + function traceFunctionEntrance(traceName) { + + return function (code) { + var tree, + functionList, + param, + signature, + pos, + i; + + + tree = parse(code, { range: true, loc: true }); + + functionList = []; + traverse(tree, function (node, path) { + var parent; + if (node.type === Syntax.FunctionDeclaration) { + functionList.push({ + name: node.id.name, + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } else if (node.type === Syntax.FunctionExpression) { + parent = path[0]; + if (parent.type === Syntax.AssignmentExpression) { + if (typeof parent.left.range !== 'undefined') { + functionList.push({ + name: code.slice(parent.left.range[0], + parent.left.range[1] + 1), + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } + } else if (parent.type === Syntax.VariableDeclarator) { + functionList.push({ + name: parent.id.name, + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } else if (parent.type === Syntax.CallExpression) { + functionList.push({ + name: parent.id ? parent.id.name : '[Anonymous]', + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } else if (typeof parent.length === 'number') { + functionList.push({ + name: parent.id ? parent.id.name : '[Anonymous]', + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } else if (typeof parent.key !== 'undefined') { + if (parent.key.type === 'Identifier') { + if (parent.value === node && parent.key.name) { + functionList.push({ + name: parent.key.name, + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } + } + } + } + }); + + // Insert the instrumentation code from the last entry. + // This is to ensure that the range for each entry remains valid) + // (it won't shift due to some new inserting string before the range). + for (i = functionList.length - 1; i >= 0; i -= 1) { + param = { + name: functionList[i].name, + range: functionList[i].range, + loc: functionList[i].loc + }; + if (typeof traceName === 'function') { + signature = traceName.call(null, param); + } else { + signature = traceName + '({ '; + signature += 'name: \'' + functionList[i].name + '\', '; + if (typeof functionList[i].loc !== 'undefined') { + signature += 'lineNumber: ' + functionList[i].loc.start.line + ', '; + } + signature += 'range: [' + functionList[i].range[0] + ', ' + + functionList[i].range[1] + '] '; + signature += '});'; + } + pos = functionList[i].blockStart + 1; + code = code.slice(0, pos) + '\n' + signature + code.slice(pos, code.length); + } + + return code; + }; + } + + function modify(code, modifiers) { + var i; + + if (Object.prototype.toString.call(modifiers) === '[object Array]') { + for (i = 0; i < modifiers.length; i += 1) { + code = modifiers[i].call(null, code); + } + } else if (typeof modifiers === 'function') { + code = modifiers.call(null, code); + } else { + throw new Error('Wrong use of esprima.modify() function'); + } + + return code; + } + + // Sync with package.json. + exports.version = '0.9.9-dev'; + + exports.parse = parse; + + exports.modify = modify; + + exports.Tracer = { + FunctionEntrance: traceFunctionEntrance + }; + +}(typeof exports === 'undefined' ? (esprima = {}) : exports)); +/* vim: set sw=4 ts=4 et tw=80 : */ diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..86efaf0 --- /dev/null +++ b/assets/style.css @@ -0,0 +1,52 @@ +* { + font-size 12px; + font-family:'Trebuchet MS', Arial, Helvetica, sans-serif; +} + +pre { + font-family:'Bitstream Vera Sans Mono', Courier, monospace; + font-size: 12px; + background-color: #EEE; + padding:10px; + overflow: auto; +} + +#console { + -webkit-border-radius: 5px; + -moz-border-radius : 5px; + -o-border-radius : 5px; + border-radius : 5px; + border : 1px solid #C6C6C6; + outline: none; + padding:10px; + display: block; + width : 80%; + margin: 10px auto 10px auto; +} + +.invalid { + border-color: #fbc2c4 !important; +} + +#output { + -webkit-border-radius: 5px; + -moz-border-radius : 5px; + -o-border-radius : 5px; + border-radius : 5px; + border : 1px solid #C6C6C6; + width : 80%; + margin : 10px auto 10px auto; +} + +h1, h2, h3, h4, h5, p { + text-align: center; +} + +p { + color: #849EAA; +} +a { + text-decoration: none; + color: #4183C4; +} + diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..e25c485 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,71 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>JS code generator demo: JS → AST → JS</title> + <link rel="stylesheet" href="../assets/style.css" type="text/css" /> + <script src='../assets/esprima.js'></script> + <script src='../escodegen.js'></script> + <script type="text/javascript"> +function $D(elm) { + var range = document.createRange(); + range.selectNodeContents(elm); + range.deleteContents(); + range.detach(); +} +function $T(mes) { + return document.createTextNode(mes); +} +function hasClass (e, name){ + name = name.toLowerCase(); + var cn = e.className; + if (!cn) return false; + var cnlist = cn.toLowerCase().split(/\s+/); + for (var i=0,l=cnlist.length;i<l;i++) + if(cnlist[i] == name) return true; + return false; +} +function addClass (e, name){ + var cn = e.className || ''; + if(hasClass(e, name)) return; + e.className = cn+' '+name; +} +function removeClass(e, name){ + if(!hasClass(e, name)) return; + var cn = e.className || ''; + name = name.toLowerCase(); + var cnlist = cn.toLowerCase().split(/\s+/); + cnlist.splice(cnlist.indexOf(name), 1); + e.className = cnlist.join(' '); +} +function toggleClass(e, name){ + (hasClass(e, name))? removeClass(e, name) : addClass(e, name); +} +document.addEventListener('DOMContentLoaded', function(ev) { + var textarea = document.getElementById("console"); + var pre = document.getElementById("output"); + textarea.value = "for(var i=1;i<=100;++i){if(i%15===0){console.log('FizzBuzz')}else if(i%3===0){console.log('Fizz')}else if(i%5===0){console.log('Buzz')}else{console.log(i)}}"; + function show() { + $D(pre); + try { + removeClass(textarea, "invalid"); + var AST = esprima.parse(textarea.value); + pre.appendChild($T(escodegen.generate(AST))); + } catch (e) { + addClass(textarea, "invalid") + pre.appendChild($T("Invalid")); + } + } + show(); + textarea.addEventListener("input", show, false); +}, false); + </script> +</head> +<body> +<h1>JS code generator demo: JS → AST → JS</h1> +<p>using <a href="http://www.esprima.org/">esprima</a> as parser</p> +<p>and using <a href="https://github.com/Constellation/escodegen">escodegen</a> as code generator</p> +<textarea id="console" rows="10" cols="30" placeholder="write your code"></textarea> +<pre id="output"></pre> +</body> +</html> diff --git a/escodegen.js b/escodegen.js new file mode 100644 index 0000000..b4ea9ad --- /dev/null +++ b/escodegen.js @@ -0,0 +1,843 @@ +/* + Copyright (C) 2012 Ariya Hidayat <[email protected]> + Copyright (C) 2012 Mathias Bynens <[email protected]> + Copyright (C) 2012 Joost-Wim Boekesteijn <[email protected]> + Copyright (C) 2012 Kris Kowal <[email protected]> + Copyright (C) 2012 Yusuke Suzuki <[email protected]> + Copyright (C) 2012 Arpad Borsos <[email protected]> + Copyright (C) 2011 Ariya Hidayat <[email protected]> + + 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 <COPYRIGHT HOLDER> 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. +*/ + +/*jslint bitwise:true */ +/*global escodegen:true, exports:true, generateStatement: true*/ + +(function (exports) { + 'use strict'; + + var Syntax, + Precedence, + BinaryPrecedence, + base, + indent, + extra, + parse; + + Syntax = { + AssignmentExpression: 'AssignmentExpression', + ArrayExpression: 'ArrayExpression', + BlockStatement: 'BlockStatement', + BinaryExpression: 'BinaryExpression', + BreakStatement: 'BreakStatement', + CallExpression: 'CallExpression', + CatchClause: 'CatchClause', + ConditionalExpression: 'ConditionalExpression', + ContinueStatement: 'ContinueStatement', + DoWhileStatement: 'DoWhileStatement', + DebuggerStatement: 'DebuggerStatement', + EmptyStatement: 'EmptyStatement', + ExpressionStatement: 'ExpressionStatement', + ForStatement: 'ForStatement', + ForInStatement: 'ForInStatement', + FunctionDeclaration: 'FunctionDeclaration', + FunctionExpression: 'FunctionExpression', + Identifier: 'Identifier', + IfStatement: 'IfStatement', + Literal: 'Literal', + LabeledStatement: 'LabeledStatement', + LogicalExpression: 'LogicalExpression', + MemberExpression: 'MemberExpression', + NewExpression: 'NewExpression', + ObjectExpression: 'ObjectExpression', + Program: 'Program', + Property: 'Property', + ReturnStatement: 'ReturnStatement', + SequenceExpression: 'SequenceExpression', + SwitchStatement: 'SwitchStatement', + SwitchCase: 'SwitchCase', + ThisExpression: 'ThisExpression', + ThrowStatement: 'ThrowStatement', + TryStatement: 'TryStatement', + UnaryExpression: 'UnaryExpression', + UpdateExpression: 'UpdateExpression', + VariableDeclaration: 'VariableDeclaration', + VariableDeclarator: 'VariableDeclarator', + WhileStatement: 'WhileStatement', + WithStatement: 'WithStatement' + }; + + Precedence = { + Sequence: 0, + Assignment: 1, + Conditional: 2, + LogicalOR: 3, + LogicalAND: 4, + LogicalXOR: 5, + BitwiseOR: 6, + BitwiseAND: 7, + Equality: 8, + Relational: 9, + BitwiseSHIFT: 10, + Additive: 11, + Multiplicative: 12, + Unary: 13, + Postfix: 14, + Call: 15, + New: 16, + Member: 17, + Primary: 18 + }; + + BinaryPrecedence = { + '||': Precedence.LogicalOR, + '&&': Precedence.LogicalAND, + '^': Precedence.LogicalXOR, + '|': Precedence.BitwiseOR, + '&': Precedence.BitwiseAND, + '==': Precedence.Equality, + '!=': Precedence.Equality, + '===': Precedence.Equality, + '!==': Precedence.Equality, + '<': Precedence.Relational, + '>': Precedence.Relational, + '<=': Precedence.Relational, + '>=': Precedence.Relational, + 'in': Precedence.Relational, + 'instanceof': Precedence.Relational, + '<<': Precedence.BitwiseSHIFT, + '>>': Precedence.BitwiseSHIFT, + '>>>': Precedence.BitwiseSHIFT, + '+': Precedence.Additive, + '-': Precedence.Additive, + '*': Precedence.Multiplicative, + '%': Precedence.Multiplicative, + '/': Precedence.Multiplicative + }; + + if (typeof Object.freeze === 'function') { + Object.freeze(Syntax); + Object.freeze(Precedence); + Object.freeze(BinaryPrecedence); + } + + function unicodeEscape(ch) { + var result, i; + result = ch.charCodeAt(0).toString(16); + for (i = result.length; i < 4; i += 1) { + result = '0' + result; + } + return '\\u' + result; + } + + function stringToArray(str) { + var length = str.length, + result = [], + i; + for (i = 0; i < length; i += 1) { + result[i] = str.charAt(i); + } + return result; + } + + function escapeString(str) { + var result = '', i, len, ch; + + if (typeof str[0] === 'undefined') { + str = stringToArray(str); + } + + for (i = 0, len = str.length; i < len; i += 1) { + ch = str[i]; + if ('\'\\\b\f\n\r\t'.indexOf(ch) >= 0) { + result += '\\'; + switch (ch) { + case '\'': + result += '\''; + break; + case '\\': + result += '\\'; + break; + case '\b': + result += 'b'; + break; + case '\f': + result += 'f'; + break; + case '\n': + result += 'n'; + break; + case '\r': + result += 'r'; + break; + case '\t': + result += 't'; + break; + } + } else if (ch < ' ' || ch.charCodeAt(0) >= 0x80) { + result += unicodeEscape(ch); + } else { + result += ch; + } + } + + return '\'' + result + '\''; + } + + function addIndent(stmt) { + return base + stmt; + } + + function parenthesize(text, current, should) { + return (current < should) ? '(' + text + ')' : text; + } + + function maybeBlock(stmt, suffix) { + var previousBase, result; + + if (stmt.type === Syntax.BlockStatement) { + result = ' ' + generateStatement(stmt); + if (suffix) { + return result + ' '; + } + return result; + } + + if (stmt.type === Syntax.EmptyStatement) { + result = ';'; + } else { + previousBase = base; + base += indent; + result = '\n' + addIndent(generateStatement(stmt)); + base = previousBase; + } + + if (suffix) { + return result + '\n' + addIndent(''); + } + return result; + } + + function generateFunctionBody(node) { + var result, i, len; + result = '('; + for (i = 0, len = node.params.length; i < len; i += 1) { + result += node.params[i].name; + if ((i + 1) < len) { + result += ', '; + } + } + return result + ')' + maybeBlock(node.body); + } + + function generateExpression(expr, precedence) { + var result, currentPrecedence, previousBase, i, len, raw; + + if (!precedence) { + precedence = Precedence.Sequence; + } + + switch (expr.type) { + case Syntax.SequenceExpression: + result = ''; + for (i = 0, len = expr.expressions.length; i < len; i += 1) { + result += generateExpression(expr.expressions[i], Precedence.Assignment); + if ((i + 1) < len) { + result += ', '; + } + } + result = parenthesize(result, Precedence.Sequence, precedence); + break; + + case Syntax.AssignmentExpression: + result = parenthesize( + generateExpression(expr.left, Precedence.Call) + ' ' + expr.operator + ' ' + + generateExpression(expr.right, Precedence.Assignment), + Precedence.Assignment, + precedence + ); + break; + + case Syntax.ConditionalExpression: + result = parenthesize( + generateExpression(expr.test, Precedence.LogicalOR) + ' ? ' + + generateExpression(expr.consequent, Precedence.Assignment) + ' : ' + + generateExpression(expr.alternate, Precedence.Assignment), + Precedence.Conditional, + precedence + ); + break; + + case Syntax.LogicalExpression: + case Syntax.BinaryExpression: + currentPrecedence = BinaryPrecedence[expr.operator]; + + result = generateExpression(expr.left, currentPrecedence) + + ' ' + expr.operator + ' ' + + generateExpression(expr.right, currentPrecedence + 1); + if (expr.operator === 'in') { + // TODO parenthesize only in allowIn = false case + result = '(' + result + ')'; + } else { + result = parenthesize(result, currentPrecedence, precedence); + } + break; + + case Syntax.CallExpression: + result = ''; + for (i = 0, len = expr['arguments'].length; i < len; i += 1) { + result += generateExpression(expr['arguments'][i], Precedence.Assignment); + if ((i + 1) < len) { + result += ', '; + } + } + result = parenthesize( + generateExpression(expr.callee, Precedence.Call) + '(' + result + ')', + Precedence.Call, + precedence + ); + break; + + case Syntax.NewExpression: + result = ''; + for (i = 0, len = expr['arguments'].length; i < len; i += 1) { + result += generateExpression(expr['arguments'][i], Precedence.Assignment); + if ((i + 1) < len) { + result += ', '; + } + } + result = parenthesize( + 'new ' + generateExpression(expr.callee, Precedence.New) + '(' + result + ')', + Precedence.New, + precedence + ); + break; + + case Syntax.MemberExpression: + result = generateExpression(expr.object, Precedence.Call); + if (expr.computed) { + result += '[' + generateExpression(expr.property) + ']'; + } else { + if (expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') { + if (result.indexOf('.') < 0) { + if (!/[eExX]/.test(result) && !(result.length >= 2 && result[0] === '0')) { + result += '.'; + } + } + } + result += '.' + expr.property.name; + } + result = parenthesize(result, Precedence.Member, precedence); + break; + + case Syntax.UnaryExpression: + result = expr.operator; + if (result.length > 2) { + result += ' '; + } + result = parenthesize( + result + generateExpression(expr.argument, Precedence.Unary + + ( + expr.argument.type === Syntax.UnaryExpression && + expr.operator.length < 3 && + expr.argument.operator === expr.operator ? 1 : 0 + ) + ), + Precedence.Unary, + precedence + ); + break; + + case Syntax.UpdateExpression: + if (expr.prefix) { + result = parenthesize( + expr.operator + + generateExpression(expr.argument, Precedence.Unary), + Precedence.Unary, + precedence + ); + } else { + result = parenthesize( + generateExpression(expr.argument, Precedence.Postfix) + + expr.operator, + Precedence.Postfix, + precedence + ); + } + break; + + case Syntax.FunctionExpression: + result = 'function '; + if (expr.id) { + result += expr.id.name; + } + result += generateFunctionBody(expr); + break; + + case Syntax.ArrayExpression: + if (!expr.elements.length) { + result = '[]'; + break; + } + result = '[\n'; + previousBase = base; + base += indent; + for (i = 0, len = expr.elements.length; i < len; i += 1) { + if (!expr.elements[i]) { + result += addIndent(''); + if ((i + 1) === len) { + result += ','; + } + } else { + result += addIndent(generateExpression(expr.elements[i], Precedence.Assignment)); + } + if ((i + 1) < len) { + result += ',\n'; + } + } + base = previousBase; + result += '\n' + addIndent(']'); + break; + + case Syntax.Property: + if (expr.kind === 'get' || expr.kind === 'set') { + result = expr.kind + ' ' + generateExpression(expr.key) + + generateFunctionBody(expr.value); + } else { + result = generateExpression(expr.key) + ': ' + + generateExpression(expr.value, Precedence.Assignment); + } + break; + + case Syntax.ObjectExpression: + if (!expr.properties.length) { + result = '{}'; + break; + } + result = '{\n'; + previousBase = base; + base += indent; + for (i = 0, len = expr.properties.length; i < len; i += 1) { + result += addIndent(generateExpression(expr.properties[i])); + if ((i + 1) < len) { + result += ',\n'; + } + } + base = previousBase; + result += '\n' + addIndent('}'); + break; + + case Syntax.ThisExpression: + result = 'this'; + break; + + case Syntax.Identifier: + result = expr.name; + break; + + case Syntax.Literal: + if (expr.hasOwnProperty('raw') && parse) { + try { + raw = parse(expr.raw).body[0].expression; + if (raw.type === Syntax.Literal) { + if (raw.value === expr.value) { + result = expr.raw; + break; + } + } + } catch (e) { + // not use raw property + } + } + + if (expr.value === null) { + result = 'null'; + break; + } + + if (typeof expr.value === 'string') { + result = escapeString(expr.value); + break; + } + + if (typeof expr.value === 'number' && expr.value === Infinity) { + // Infinity is variable + result = '1e+1000'; + break; + } + + result = expr.value.toString(); + break; + + default: + break; + } + + if (result === undefined) { + throw new Error('Unknown expression type: ' + expr.type); + } + return result; + } + + function generateStatement(stmt) { + var i, len, result, previousBase; + + switch (stmt.type) { + case Syntax.BlockStatement: + result = '{\n'; + + previousBase = base; + base += indent; + for (i = 0, len = stmt.body.length; i < len; i += 1) { + result += addIndent(generateStatement(stmt.body[i])) + '\n'; + } + base = previousBase; + + result += addIndent('}'); + break; + + case Syntax.BreakStatement: + if (stmt.label) { + result = 'break ' + stmt.label.name + ';'; + } else { + result = 'break;'; + } + break; + + case Syntax.ContinueStatement: + if (stmt.label) { + result = 'continue ' + stmt.label.name + ';'; + } else { + result = 'continue;'; + } + break; + + case Syntax.DoWhileStatement: + result = 'do' + maybeBlock(stmt.body, true) + 'while (' + generateExpression(stmt.test) + ');'; + break; + + case Syntax.CatchClause: + previousBase = base; + base += indent; + result = ' catch (' + generateExpression(stmt.param) + ')'; + base = previousBase; + result += maybeBlock(stmt.body); + break; + + case Syntax.DebuggerStatement: + result = 'debugger;'; + break; + + case Syntax.EmptyStatement: + result = ';'; + break; + + case Syntax.ExpressionStatement: + result = generateExpression(stmt.expression); + // 12.4 '{', 'function' is not allowed in this position. + // wrap espression with parentheses + if (result[0] === '{' || result.indexOf('function ') === 0) { + result = '(' + result + ');'; + } else { + result += ';'; + } + break; + + case Syntax.VariableDeclarator: + if (stmt.init) { + result = stmt.id.name + ' = ' + generateExpression(stmt.init, Precedence.Assignment); + } else { + result = stmt.id.name; + } + break; + + case Syntax.VariableDeclaration: + result = stmt.kind + ' '; + // special path for + // var x = function () { + // }; + if (stmt.declarations.length === 1 && stmt.declarations[0].init && + stmt.declarations[0].init.type === Syntax.FunctionExpression) { + result += generateStatement(stmt.declarations[0]); + } else { + previousBase = base; + base += indent; + for (i = 0, len = stmt.declarations.length; i < len; i += 1) { + result += generateStatement(stmt.declarations[i]); + if ((i + 1) < len) { + result += ', '; + } + } + base = previousBase; + } + result += ';'; + break; + + case Syntax.ThrowStatement: + result = 'throw ' + generateExpression(stmt.argument) + ';'; + break; + + case Syntax.TryStatement: + result = 'try' + maybeBlock(stmt.block); + for (i = 0, len = stmt.handlers.length; i < len; i += 1) { + result += generateStatement(stmt.handlers[i]); + } + if (stmt.finalizer) { + result += ' finally' + maybeBlock(stmt.finalizer); + } + break; + + case Syntax.SwitchStatement: + previousBase = base; + base += indent; + result = 'switch (' + generateExpression(stmt.discriminant) + ') {\n'; + base = previousBase; + if (stmt.cases) { + for (i = 0, len = stmt.cases.length; i < len; i += 1) { + result += addIndent(generateStatement(stmt.cases[i])) + '\n'; + } + } + result += addIndent('}'); + break; + + case Syntax.SwitchCase: + previousBase = base; + base += indent; + if (stmt.test) { + result = 'case ' + generateExpression(stmt.test) + ':'; + } else { + result = 'default:'; + } + + i = 0; + len = stmt.consequent.length; + if (len && stmt.consequent[0].type === Syntax.BlockStatement) { + result += maybeBlock(stmt.consequent[0]); + i = 1; + } + + for (; i < len; i += 1) { + result += '\n' + addIndent(generateStatement(stmt.consequent[i])); + } + + base = previousBase; + break; + + case Syntax.IfStatement: + if (stmt.alternate) { + if (stmt.alternate.type === Syntax.IfStatement) { + previousBase = base; + base += indent; + result = 'if (' + generateExpression(stmt.test) + ')'; + base = previousBase; + result += maybeBlock(stmt.consequent, true) + 'else ' + generateStatement(stmt.alternate); + } else { + previousBase = base; + base += indent; + result = 'if (' + generateExpression(stmt.test) + ')'; + base = previousBase; + result += maybeBlock(stmt.consequent, true) + 'else' + maybeBlock(stmt.alternate); + } + } else { + previousBase = base; + base += indent; + result = 'if (' + generateExpression(stmt.test) + ')'; + base = previousBase; + result += maybeBlock(stmt.consequent); + } + break; + + case Syntax.ForStatement: + previousBase = base; + base += indent; + result = 'for ('; + if (stmt.init) { + if (stmt.init.type === Syntax.VariableDeclaration) { + result += generateStatement(stmt.init); + } else { + result += generateExpression(stmt.init) + ';'; + } + } else { + result += ';'; + } + + if (stmt.test) { + result += ' ' + generateExpression(stmt.test) + ';'; + } else { + result += ';'; + } + + if (stmt.update) { + result += ' ' + generateExpression(stmt.update) + ')'; + } else { + result += ')'; + } + base = previousBase; + + result += maybeBlock(stmt.body); + break; + + case Syntax.ForInStatement: + result = 'for ('; + if (stmt.left.type === Syntax.VariableDeclaration) { + previousBase = base; + base += indent + indent; + result += stmt.left.kind + ' ' + generateStatement(stmt.left.declarations[0]); + base = previousBase; + } else { + previousBase = base; + base += indent; + result += generateExpression(stmt.left, Precedence.Call); + base = previousBase; + } + + previousBase = base; + base += indent; + result += ' in ' + generateExpression(stmt.right) + ')'; + base = previousBase; + result += maybeBlock(stmt.body); + break; + + case Syntax.LabeledStatement: + result = stmt.label.name + ':' + maybeBlock(stmt.body); + break; + + case Syntax.Program: + result = ''; + for (i = 0, len = stmt.body.length; i < len; i += 1) { + result += generateStatement(stmt.body[i]); + if ((i + 1) < len) { + result += '\n'; + } + } + break; + + case Syntax.FunctionDeclaration: + result = 'function '; + if (stmt.id) { + result += stmt.id.name; + } + result += generateFunctionBody(stmt); + break; + + case Syntax.ReturnStatement: + if (stmt.argument) { + result = 'return ' + generateExpression(stmt.argument) + ';'; + } else { + result = 'return;'; + } + break; + + case Syntax.WhileStatement: + previousBase = base; + base += indent; + result = 'while (' + generateExpression(stmt.test) + ')'; + base = previousBase; + result += maybeBlock(stmt.body); + break; + + case Syntax.WithStatement: + previousBase = base; + base += indent; + result = 'with (' + generateExpression(stmt.object) + ')'; + base = previousBase; + result += maybeBlock(stmt.body); + break; + + default: + break; + } + + if (result === undefined) { + throw new Error('Unknown statement type: ' + stmt.type); + } + return result; + } + + function generate(node, options) { + if (typeof options !== 'undefined') { + base = options.base || ''; + indent = options.indent || ' '; + parse = options.parse; + } else { + base = ''; + indent = ' '; + parse = null; + } + + switch (node.type) { + case Syntax.BlockStatement: + case Syntax.BreakStatement: + case Syntax.CatchClause: + case Syntax.ContinueStatement: + case Syntax.DoWhileStatement: + case Syntax.DebuggerStatement: + case Syntax.EmptyStatement: + case Syntax.ExpressionStatement: + case Syntax.ForStatement: + case Syntax.ForInStatement: + case Syntax.FunctionDeclaration: + case Syntax.IfStatement: + case Syntax.LabeledStatement: + case Syntax.Program: + case Syntax.ReturnStatement: + case Syntax.SwitchStatement: + case Syntax.SwitchCase: + case Syntax.ThrowStatement: + case Syntax.TryStatement: + case Syntax.VariableDeclaration: + case Syntax.VariableDeclarator: + case Syntax.WhileStatement: + case Syntax.WithStatement: + return generateStatement(node); + + case Syntax.AssignmentExpression: + case Syntax.ArrayExpression: + case Syntax.BinaryExpression: + case Syntax.CallExpression: + case Syntax.ConditionalExpression: + case Syntax.FunctionExpression: + case Syntax.Identifier: + case Syntax.Literal: + case Syntax.LogicalExpression: + case Syntax.MemberExpression: + case Syntax.NewExpression: + case Syntax.ObjectExpression: + case Syntax.Property: + case Syntax.SequenceExpression: + case Syntax.ThisExpression: + case Syntax.UnaryExpression: + case Syntax.UpdateExpression: + return generateExpression(node); + + default: + break; + } + throw new Error('Unknown node type: ' + node.type); + } + + // Sync with package.json. + exports.version = '0.0.3-dev'; + + exports.generate = generate; + +}(typeof exports === 'undefined' ? (escodegen = {}) : exports)); +/* vim: set sw=4 ts=4 et tw=80 : */
