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 590f4f89abadf2a3f091b9a783bbfcaf5f7baea7 Author: Constellation <[email protected]> AuthorDate: Thu Jun 7 15:57:29 2012 +0900 script upgrade --- assets/esprima.js | 1364 ++++++++++++++++++++++++++++++++++++----------------- escodegen.js | 781 ++++++++++++++++++++++++++---- 2 files changed, 1636 insertions(+), 509 deletions(-) diff --git a/assets/esprima.js b/assets/esprima.js index 8a11eba..5fee256 100644 --- a/assets/esprima.js +++ b/assets/esprima.js @@ -28,14 +28,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -/*jslint bitwise:true */ +/*jslint bitwise:true plusplus: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, +parseImportSpecifier: true, parseLeftHandSideExpression: true, -parseStatement: true, parseSourceElement: true */ +parseStatement: true, parseSourceElement: true, parseModuleBlock: true */ (function (exports) { 'use strict'; @@ -47,19 +48,14 @@ parseStatement: true, parseSourceElement: true */ Messages, Regex, source, - allowIn, - lastParenthesized, strict, index, lineNumber, lineStart, length, buffer, - extra, - labelSet, - inIteration, - inSwitch, - inFunctionBody; + state, + extra; Token = { BooleanLiteral: 1, @@ -85,6 +81,7 @@ parseStatement: true, parseSourceElement: true */ Syntax = { AssignmentExpression: 'AssignmentExpression', ArrayExpression: 'ArrayExpression', + ArrayPattern: 'ArrayPattern', BlockStatement: 'BlockStatement', BinaryExpression: 'BinaryExpression', BreakStatement: 'BreakStatement', @@ -95,21 +92,32 @@ parseStatement: true, parseSourceElement: true */ DoWhileStatement: 'DoWhileStatement', DebuggerStatement: 'DebuggerStatement', EmptyStatement: 'EmptyStatement', + ExportSpecifier: 'ExportSpecifier', + ExportSpecifierSet: 'ExportSpecifierSet', + ExportDeclaration: 'ExportDeclaration', ExpressionStatement: 'ExpressionStatement', ForStatement: 'ForStatement', ForInStatement: 'ForInStatement', + ForOfStatement: 'ForOfStatement', FunctionDeclaration: 'FunctionDeclaration', FunctionExpression: 'FunctionExpression', + Glob: 'Glob', Identifier: 'Identifier', IfStatement: 'IfStatement', + ImportDeclaration: 'ImportDeclaration', + ImportSpecifier: 'ImportSpecifier', Literal: 'Literal', LabeledStatement: 'LabeledStatement', LogicalExpression: 'LogicalExpression', MemberExpression: 'MemberExpression', + ModuleDeclaration: 'ModuleDeclaration', NewExpression: 'NewExpression', ObjectExpression: 'ObjectExpression', + ObjectPattern: 'ObjectPattern', + Path: 'Path', Program: 'Program', Property: 'Property', + ProtoExpression: 'ProtoExpression', ReturnStatement: 'ReturnStatement', SequenceExpression: 'SequenceExpression', SwitchStatement: 'SwitchStatement', @@ -140,6 +148,7 @@ parseStatement: true, parseSourceElement: true */ UnexpectedReserved: 'Unexpected reserved word', UnexpectedEOS: 'Unexpected end of input', NewlineAfterThrow: 'Illegal newline after throw', + NewlineAfterModule: 'Illegal newline after module', InvalidRegExp: 'Invalid regular expression', UnterminatedRegExp: 'Invalid regular expression: missing /', InvalidLHSInAssignment: 'Invalid left-hand side in assignment', @@ -164,7 +173,8 @@ parseStatement: true, parseSourceElement: true */ 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' + StrictReservedWord: 'Use of future reserved word in strict mode', + NoFromAfterImport: 'Missing from after import' }; // See also tools/generate-unicode-regex.py. @@ -173,15 +183,6 @@ parseStatement: true, parseSourceElement: true */ 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. @@ -338,18 +339,18 @@ parseStatement: true, parseSourceElement: true */ return true; } + // Harmony + if (id === 'module') { + 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; + return source[index++]; } // 7.4 Comments @@ -368,18 +369,18 @@ parseStatement: true, parseSourceElement: true */ if (isLineTerminator(ch)) { lineComment = false; if (ch === '\r' && source[index] === '\n') { - index += 1; + ++index; } - lineNumber += 1; + ++lineNumber; lineStart = index; } } else if (blockComment) { if (isLineTerminator(ch)) { if (ch === '\r' && source[index + 1] === '\n') { - index += 1; + ++index; } - lineNumber += 1; - index += 1; + ++lineNumber; + ++index; lineStart = index; if (index >= length) { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); @@ -392,7 +393,7 @@ parseStatement: true, parseSourceElement: true */ if (ch === '*') { ch = source[index]; if (ch === '/') { - index += 1; + ++index; blockComment = false; } } @@ -412,13 +413,13 @@ parseStatement: true, parseSourceElement: true */ break; } } else if (isWhiteSpace(ch)) { - index += 1; + ++index; } else if (isLineTerminator(ch)) { - index += 1; + ++index; if (ch === '\r' && source[index] === '\n') { - index += 1; + ++index; } - lineNumber += 1; + ++lineNumber; lineStart = index; } else { break; @@ -430,7 +431,7 @@ parseStatement: true, parseSourceElement: true */ var i, len, ch, code = 0; len = (prefix === 'u') ? 4 : 2; - for (i = 0; i < len; i += 1) { + for (i = 0; i < len; ++i) { if (index < length && isHexDigit(source[index])) { ch = nextChar(); code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase()); @@ -451,11 +452,11 @@ parseStatement: true, parseSourceElement: true */ start = index; if (ch === '\\') { - index += 1; + ++index; if (source[index] !== 'u') { return; } - index += 1; + ++index; restore = index; ch = scanHexEscape('u'); if (ch) { @@ -477,11 +478,11 @@ parseStatement: true, parseSourceElement: true */ break; } if (ch === '\\') { - index += 1; + ++index; if (source[index] !== 'u') { return; } - index += 1; + ++index; restore = index; ch = scanHexEscape('u'); if (ch) { @@ -565,7 +566,7 @@ parseStatement: true, parseSourceElement: true */ // Check for most common single-character punctuators. if (ch1 === ';' || ch1 === '{' || ch1 === '}') { - index += 1; + ++index; return { type: Token.Punctuator, value: ch1, @@ -576,7 +577,7 @@ parseStatement: true, parseSourceElement: true */ } if (ch1 === ',' || ch1 === '(' || ch1 === ')') { - index += 1; + ++index; return { type: Token.Punctuator, value: ch1, @@ -706,9 +707,31 @@ parseStatement: true, parseSourceElement: true */ } } + if (ch1 === '<' && ch2 === '|') { + index += 2; + return { + type: Token.Punctuator, + value: '<|', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '=' && ch2 === '>') { + index += 2; + return { + type: Token.Punctuator, + value: '=>', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + // The remaining 1-character punctuators. - if ('[]<>+-*%&|^!~?:=/'.indexOf(ch1) >= 0) { + if ('[]<>+-*%&|^!~?:=#/'.indexOf(ch1) >= 0) { return { type: Token.Punctuator, value: nextChar(), @@ -722,7 +745,7 @@ parseStatement: true, parseSourceElement: true */ // 7.8.3 Numeric Literals function scanNumericLiteral() { - var number, start, ch; + var number, start, ch, octal; ch = source[index]; assert(isDecimalDigit(ch) || (ch === '.'), @@ -736,6 +759,8 @@ parseStatement: true, parseSourceElement: true */ // Hex number starts with '0x'. // Octal number starts with '0'. + // Octal number in ES6 starts with '0o'. + // Binary number in ES6 starts with '0b'. if (number === '0') { if (ch === 'x' || ch === 'X') { number += nextChar(); @@ -765,8 +790,46 @@ parseStatement: true, parseSourceElement: true */ lineStart: lineStart, range: [start, index] }; - } else if (isOctalDigit(ch)) { - number += nextChar(); + } else if (ch === 'b' || ch === 'B') { + nextChar(); + number = ''; + + while (index < length) { + ch = source[index]; + if (ch !== '0' && ch !== '1') { + break; + } + number += nextChar(); + } + + if (number.length === 0) { + // only 0b or 0B + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + if (index < length) { + ch = source[index]; + if (isIdentifierStart(ch) || isDecimalDigit(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + return { + type: Token.NumericLiteral, + value: parseInt(number, 2), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } else if (ch === 'o' || ch === 'O' || isOctalDigit(ch)) { + if (isOctalDigit(ch)) { + octal = true; + number = nextChar(); + } else { + octal = false; + nextChar(); + number = ''; + } + while (index < length) { ch = source[index]; if (!isOctalDigit(ch)) { @@ -775,16 +838,22 @@ parseStatement: true, parseSourceElement: true */ number += nextChar(); } + if (number.length === 0) { + // only 0o or 0O + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + 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, + octal: octal, lineNumber: lineNumber, lineStart: lineStart, range: [start, index] @@ -870,7 +939,7 @@ parseStatement: true, parseSourceElement: true */ 'String literal must starts with a quote'); start = index; - index += 1; + ++index; while (index < length) { ch = nextChar(); @@ -940,9 +1009,9 @@ parseStatement: true, parseSourceElement: true */ break; } } else { - lineNumber += 1; + ++lineNumber; if (ch === '\r' && source[index] === '\n') { - index += 1; + ++index; } } } else if (isLineTerminator(ch)) { @@ -986,15 +1055,17 @@ parseStatement: true, parseSourceElement: true */ } } else { if (ch === '\\') { - str += nextChar(); - } - if (ch === '/') { + ch = nextChar(); + // ECMA-262 7.8.5 + if (isLineTerminator(ch)) { + throwError({}, Messages.UnterminatedRegExp); + } + str += ch; + } else if (ch === '/') { break; - } - if (ch === '[') { + } else if (ch === '[') { classMarker = true; - } - if (isLineTerminator(ch)) { + } else if (isLineTerminator(ch)) { throwError({}, Messages.UnterminatedRegExp); } } @@ -1014,17 +1085,17 @@ parseStatement: true, parseSourceElement: true */ break; } - index += 1; + ++index; if (ch === '\\' && index < length) { ch = source[index]; if (ch === 'u') { - index += 1; + ++index; restore = index; ch = scanHexEscape('u'); if (ch) { flags += ch; str += '\\u'; - for (; restore < index; restore += 1) { + for (; restore < index; ++restore) { str += source[restore]; } } else { @@ -1176,6 +1247,20 @@ parseStatement: true, parseSourceElement: true */ throw error; } + function throwErrorTolerant() { + var error; + try { + throwError.apply(null, arguments); + } catch (e) { + if (extra.errors) { + extra.errors.push(e); + } else { + throw e; + } + } + } + + // Throw an exception because of the token. function throwUnexpected(token) { @@ -1244,6 +1329,14 @@ parseStatement: true, parseSourceElement: true */ return token.type === Token.Keyword && token.value === keyword; } + + // Return true if the next token matches the specified contextual keyword + + function matchContextualKeyword(keyword) { + var token = lookahead(); + return token.type === Token.Identifier && token.value === keyword; + } + // Return true if the next token is an assignment operator function matchAssign() { @@ -1339,6 +1432,12 @@ parseStatement: true, parseSourceElement: true */ }; } + function parseSealedArrayInitialiser() { + var result = parseArrayInitialiser(); + result.sealed = true; + return result; + } + // 11.1.5 Object Initialiser function parsePropertyFunction(param, first) { @@ -1359,61 +1458,85 @@ parseStatement: true, parseSourceElement: true */ }; } + function parsePropertyMethodFunction() { + var token, previousStrict, param, params, paramSet, method; + + previousStrict = strict; + strict = true; + params = []; + + expect('('); + + if (!match(')')) { + paramSet = {}; + while (index < length) { + token = lookahead(); + param = parseVariableIdentifier(); + if (isRestrictedWord(token.value)) { + throwError(token, Messages.StrictParamName); + } + if (Object.prototype.hasOwnProperty.call(paramSet, token.value)) { + throwError(token, Messages.StrictParamDupe); + } + params.push(param); + paramSet[param.name] = true; + if (match(')')) { + break; + } + expect(','); + } + } + + expect(')'); + + method = parsePropertyFunction(params); + + strict = previousStrict; + + return method; + } + function parseObjectPropertyKey() { - var token = lex(), - key; + var token = lex(); - switch (token.type) { + // Note: This function is called only from parseObjectProperty(), where + // EOF and Punctuator tokens are already filtered out. - case Token.StringLiteral: - case Token.NumericLiteral: + if (token.type === Token.StringLiteral || token.type === 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 createLiteral(token); } - return key; + return { + type: Syntax.Identifier, + name: token.value + }; } function parseObjectProperty() { - var token, property, key, id, param; + var token, key, id, param; token = lookahead(); - switch (token.type) { + if (token.type === Token.Identifier) { - case Token.Identifier: id = parseObjectPropertyKey(); // Property Assignment: Getter and Setter. - if (token.value === 'get' && !match(':')) { + if (token.value === 'get' && !(match(':') || match('('))) { key = parseObjectPropertyKey(); expect('('); expect(')'); - property = { + return { type: Syntax.Property, key: key, value: parsePropertyFunction([]), kind: 'get' }; - } else if (token.value === 'set' && !match(':')) { + } else if (token.value === 'set' && !(match(':') || match('('))) { key = parseObjectPropertyKey(); expect('('); token = lookahead(); @@ -1422,43 +1545,69 @@ parseStatement: true, parseSourceElement: true */ } param = [ parseVariableIdentifier() ]; expect(')'); - property = { + return { type: Syntax.Property, key: key, value: parsePropertyFunction(param, token), kind: 'set' }; } else { - expect(':'); - property = { + if (match(':')) { + lex(); + return { + type: Syntax.Property, + key: id, + value: parseAssignmentExpression(), + kind: 'init' + }; + } else if (match('(')) { + return { + type: Syntax.Property, + key: id, + value: parsePropertyMethodFunction(), + kind: 'init', + method: true + }; + } else { + return { + type: Syntax.Property, + key: id, + value: id, + kind: 'init', + shorthand: true + }; + } + } + } else if (token.type === Token.EOF || token.type === Token.Punctuator) { + throwUnexpected(token); + } else { + key = parseObjectPropertyKey(); + if (match(':')) { + lex(); + return { type: Syntax.Property, - key: id, + key: key, value: parseAssignmentExpression(), kind: 'init' }; + } else if (match('(')) { + return { + type: Syntax.Property, + key: key, + value: parsePropertyMethodFunction(), + kind: 'init', + method: true + }; + } else { + return { + type: Syntax.Property, + key: key, + value: key, + kind: 'init', + shorthand: true + }; } - 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() { @@ -1478,7 +1627,7 @@ parseStatement: true, parseSourceElement: true */ if (Object.prototype.hasOwnProperty.call(map, name)) { if (map[name] === PropertyKind.Data) { if (strict && kind === PropertyKind.Data) { - throwError({}, Messages.StrictDuplicateProperty); + throwErrorTolerant({}, Messages.StrictDuplicateProperty); } else if (kind !== PropertyKind.Data) { throwError({}, Messages.AccessorDataProperty); } @@ -1509,6 +1658,12 @@ parseStatement: true, parseSourceElement: true */ }; } + function parseSealedObjectInitialiser() { + var result = parseObjectInitialiser(); + result.sealed = true; + return result; + } + // 11.1 Primary Expressions function parsePrimaryExpression() { @@ -1525,7 +1680,7 @@ parseStatement: true, parseSourceElement: true */ if (type === Token.StringLiteral || type === Token.NumericLiteral) { if (strict && token.octal) { - throwError(token, Messages.StrictOctalLiteral); + throwErrorTolerant(token, Messages.StrictOctalLiteral); } return createLiteral(lex()); } @@ -1565,7 +1720,7 @@ parseStatement: true, parseSourceElement: true */ if (match('(')) { lex(); - lastParenthesized = expr = parseExpression(); + state.lastParenthesized = expr = parseExpression(); expect(')'); return expr; } @@ -1574,6 +1729,17 @@ parseStatement: true, parseSourceElement: true */ return createLiteral(scanRegExp()); } + if (match('#')) { + lex(); + if (match('[')) { + return parseSealedArrayInitialiser(); + } + + if (match('{')) { + return parseSealedObjectInitialiser(); + } + } + return throwUnexpected(lex()); } @@ -1662,6 +1828,57 @@ parseStatement: true, parseSourceElement: true */ return expr; } + function parseTriangleLiteral() { + var expr, + token = lookahead(), + type = token.type; + + if (type === Token.StringLiteral || type === Token.NumericLiteral) { + if (strict && token.octal) { + throwErrorTolerant(token, Messages.StrictOctalLiteral); + } + return createLiteral(lex()); + } + + if (type === Token.Keyword) { + if (matchKeyword('function')) { + return parseFunctionExpression(); + } + } + + if (type === Token.BooleanLiteral) { + lex(); + token.value = (token.value === 'true'); + return createLiteral(token); + } + + if (match('[')) { + return parseArrayInitialiser(); + } + + if (match('{')) { + return parseObjectInitialiser(); + } + + if (match('/') || match('/=')) { + return createLiteral(scanRegExp()); + } + + // ArrowFunctionExpression + expr = parseExpression(); + + return parseArrowFunctionExpression(expr); + } + + function parseProtoExpression(proto) { + expect('<|'); + return { + type: Syntax.ProtoExpression, + proto: proto, + literal: parseTriangleLiteral() + }; + } + function parseLeftHandSideExpressionAllowCall() { var useNew, expr; @@ -1674,6 +1891,8 @@ parseStatement: true, parseSourceElement: true */ expr = parseNonComputedMember(expr); } else if (match('[')) { expr = parseComputedMember(expr); + } else if (match('<|')) { + expr = parseProtoExpression(expr); } else if (match('(')) { expr = parseCallMember(expr); } else { @@ -1696,6 +1915,8 @@ parseStatement: true, parseSourceElement: true */ expr = parseNonComputedMember(expr); } else if (match('[')) { expr = parseComputedMember(expr); + } else if (match('<|')) { + expr = parseProtoExpression(expr); } else { break; } @@ -1762,7 +1983,7 @@ parseStatement: true, parseSourceElement: true */ argument: parseUnaryExpression() }; if (strict && expr.operator === 'delete' && expr.argument.type === Syntax.Identifier) { - throwError({}, Messages.StrictDelete); + throwErrorTolerant({}, Messages.StrictDelete); } return expr; } @@ -1825,10 +2046,10 @@ parseStatement: true, parseSourceElement: true */ function parseRelationalExpression() { var expr, previousAllowIn; - previousAllowIn = allowIn; - allowIn = true; + previousAllowIn = state.allowIn; + state.allowIn = true; expr = parseShiftExpression(); - allowIn = previousAllowIn; + state.allowIn = previousAllowIn; if (match('<') || match('>') || match('<=') || match('>=')) { expr = { @@ -1837,7 +2058,7 @@ parseStatement: true, parseSourceElement: true */ left: expr, right: parseRelationalExpression() }; - } else if (allowIn && matchKeyword('in')) { + } else if (state.allowIn && matchKeyword('in')) { lex(); expr = { type: Syntax.BinaryExpression, @@ -1863,7 +2084,7 @@ parseStatement: true, parseSourceElement: true */ function parseEqualityExpression() { var expr = parseRelationalExpression(); - while (match('==') || match('!=') || match('===') || match('!==')) { + while ((!peekLineTerminator() && (matchContextualKeyword('is') || matchContextualKeyword('isnt'))) || match('==') || match('!=') || match('===') || match('!==')) { expr = { type: Syntax.BinaryExpression, operator: lex().value, @@ -1968,10 +2189,10 @@ parseStatement: true, parseSourceElement: true */ if (match('?')) { lex(); - previousAllowIn = allowIn; - allowIn = true; + previousAllowIn = state.allowIn; + state.allowIn = true; consequent = parseAssignmentExpression(); - allowIn = previousAllowIn; + state.allowIn = previousAllowIn; expect(':'); expr = { @@ -1987,6 +2208,81 @@ parseStatement: true, parseSourceElement: true */ // 11.13 Assignment Operators + function reinterpretAsAssignmentBindingPattern(expr) { + var i, len, property, element; + + if (expr.sealed) { + throwError({}, Messages.InvalidLHSInAssignment); + } + + if (expr.type === Syntax.ObjectExpression) { + expr.type = Syntax.ObjectPattern; + for (i = 0, len = expr.properties.length; i < len; i += 1) { + property = expr.properties[i]; + if (property.kind !== 'init') { + throwError({}, Messages.InvalidLHSInAssignment); + } + reinterpretAsAssignmentBindingPattern(property.value); + } + } else if (expr.type === Syntax.ArrayExpression) { + expr.type = Syntax.ArrayPattern; + for (i = 0, len = expr.elements.length; i < len; i += 1) { + element = expr.elements[i]; + if (element) { + reinterpretAsAssignmentBindingPattern(element); + } + } + } else if (expr.type === Syntax.Identifier) { + if (isRestrictedWord(expr.name)) { + throwError({}, Messages.InvalidLHSInAssignment); + } + } else { + if (expr.type !== Syntax.MemberExpression && expr.type !== Syntax.CallExpression && expr.type !== Syntax.NewExpression) { + throwError({}, Messages.InvalidLHSInAssignment); + } + } + } + + function reinterpretAsCoverFormalsList(expr) { + if (state.lastParenthesized !== expr) { + if (expr.type === Syntax.Identifier) { + // e => ConciseBody style + return [ expr ]; + } + throwError({}, Messages.InvalidCoverFormalsList); + } + + if (expr.type === Syntax.Identifier) { + // (e) => ConciseBody style + return [ expr ]; + } + + if (expr.type !== Syntax.SequenceExpression) { + throwError({}, Messages.InvalidCoverFormalsList); + } + + return expr; + } + + function parseArrowFunctionExpression(expr) { + var params, previousStrict; + + params = reinterpretAsCoverFormalsList(expr); + + expect('=>'); + + previousStrict = strict; + strict = true; + expr = { + type: Syntax.ArrowFunctionExpression, + params: params, + body: parseConciseBody() + }; + strict = previousStrict; + + return expr; + } + function parseAssignmentExpression() { var expr; @@ -1994,7 +2290,7 @@ parseStatement: true, parseSourceElement: true */ if (matchAssign()) { // LeftHandSideExpression - if (lastParenthesized !== expr && !isLeftHandSide(expr)) { + if (state.lastParenthesized !== expr && !isLeftHandSide(expr)) { throwError({}, Messages.InvalidLHSInAssignment); } @@ -2003,12 +2299,20 @@ parseStatement: true, parseSourceElement: true */ throwError({}, Messages.StrictLHSAssignment); } + // ES.next draf 11.13 Runtime Semantics step 1 + if (expr.type === Syntax.ObjectExpression || expr.type === Syntax.ArrayExpression) { + reinterpretAsAssignmentBindingPattern(expr); + } + expr = { type: Syntax.AssignmentExpression, operator: lex().value, left: expr, right: parseAssignmentExpression() }; + } else if (match('=>')) { + lex(); + return parseArrowFunctionExpression(expr); } return expr; @@ -2093,7 +2397,7 @@ parseStatement: true, parseSourceElement: true */ // 12.2.1 if (strict && isRestrictedWord(id.name)) { - throwError({}, Messages.StrictVarName); + throwErrorTolerant({}, Messages.StrictVarName); } if (kind === 'const') { @@ -2161,17 +2465,255 @@ parseStatement: true, parseSourceElement: true */ }; } - // 12.3 Empty Statement + // http://wiki.ecmascript.org/doku.php?id=harmony:modules - function parseEmptyStatement() { - expect(';'); + function parsePath() { + var result, id; - return { - type: Syntax.EmptyStatement + result = { + type: Syntax.Path, + body: [] }; - } - // 12.4 Expression Statement + while (true) { + id = parseVariableIdentifier(); + result.body.push(id); + if (!match('.')) { + break; + } + lex(); + } + + return result; + } + + function parseGlob() { + expect('*'); + return { + type: Syntax.Glob + }; + } + + function parseModuleDeclaration() { + var id, token, declaration; + + expectKeyword('module'); + + if (peekLineTerminator()) { + throwError({}, Messages.NewlineAfterModule); + } + + id = parseVariableIdentifier(); + + if (match('{')) { + return { + type: Syntax.ModuleDeclaration, + id: id, + body: parseModuleBlock() + }; + } + + expect('='); + + token = lookahead(); + if (token.type === Token.StringLiteral) { + declaration = { + type: Syntax.ModuleDeclaration, + id: id, + from: parsePrimaryExpression() + }; + } else { + declaration = { + type: Syntax.ModuleDeclaration, + id: id, + from: parsePath() + }; + } + + consumeSemicolon(); + + return declaration; + } + + function parseExportSpecifierSetProperty() { + var specifier; + + specifier = { + type: Syntax.ExportSpecifier, + id: parseVariableIdentifier(), + from: null + }; + + if (match(':')) { + lex(); + specifier.from = parsePath(); + } + + return specifier; + } + + function parseExportSpecifier() { + var specifier, specifiers; + + if (match('{')) { + lex(); + specifiers = []; + + do { + specifiers.push(parseExportSpecifierSetProperty()); + } while (match(',') && lex()); + + expect('}'); + + return { + type: Syntax.ExportSpecifierSet, + specifiers: specifiers + }; + } + + if (match('*')) { + specifier = { + type: Syntax.ExportSpecifier, + id: parseGlob(), + from: null + }; + + if (matchContextualKeyword('from')) { + lex(); + specifier.from = parsePath(); + } + } else { + specifier = { + type: Syntax.ExportSpecifier, + id: parseVariableIdentifier(), + from: null + }; + } + return specifier; + } + + function parseExportDeclaration() { + var id, token, declaration, specifiers; + + expectKeyword('export'); + + token = lookahead(); + + if (token.type === Token.Keyword) { + switch (token.value) { + case 'function': + return { + type: Syntax.ExportDeclaration, + declaration: parseFunctionDeclaration() + }; + case 'module': + return { + type: Syntax.ExportDeclaration, + declaration: parseModuleDeclaration() + }; + case 'let': + case 'const': + return { + type: Syntax.ExportDeclaration, + declaration: parseConstLetDeclaration(token.value) + }; + case 'var': + return { + type: Syntax.ExportDeclaration, + declaration: parseStatement() + }; + } + throwUnexpected(lex()); + } + + specifiers = [ parseExportSpecifier() ]; + if (match(',')) { + while (index < length) { + if (!match(',')) { + break; + } + lex(); + specifiers.push(parseExportSpecifier()); + } + } + + consumeSemicolon(); + + return { + type: Syntax.ExportDeclaration, + specifiers: specifiers + }; + } + + function parseImportDeclaration() { + var specifiers, from; + + expectKeyword('import'); + + if (match('*')) { + specifiers = [parseGlob()]; + } else if (match('{')) { + lex(); + specifiers = []; + + do { + specifiers.push(parseImportSpecifier()); + } while (match(',') && lex()); + + expect('}'); + } else { + specifiers = [parseVariableIdentifier()]; + } + + if (!matchContextualKeyword('from')) { + throwError({}, Messages.NoFromAfterImport); + } + + lex(); + + if (lookahead().type === Token.StringLiteral) { + from = parsePrimaryExpression(); + } else { + from = parsePath(); + } + + consumeSemicolon(); + + return { + type: Syntax.ImportDeclaration, + specifiers: specifiers, + from: from + }; + } + + function parseImportSpecifier() { + var specifier; + + specifier = { + type: Syntax.ImportSpecifier, + id: parseVariableIdentifier(), + from: null + }; + + if (match(':')) { + lex(); + specifier.from = parsePath(); + } + + return specifier; + } + + // 12.3 Empty Statement + + function parseEmptyStatement() { + expect(';'); + + return { + type: Syntax.EmptyStatement + }; + } + + // 12.4 Expression Statement function parseExpressionStatement() { var expr = parseExpression(); @@ -2221,12 +2763,12 @@ parseStatement: true, parseSourceElement: true */ expectKeyword('do'); - oldInIteration = inIteration; - inIteration = true; + oldInIteration = state.inIteration; + state.inIteration = true; body = parseStatement(); - inIteration = oldInIteration; + state.inIteration = oldInIteration; expectKeyword('while'); @@ -2258,12 +2800,12 @@ parseStatement: true, parseSourceElement: true */ expect(')'); - oldInIteration = inIteration; - inIteration = true; + oldInIteration = state.inIteration; + state.inIteration = true; body = parseStatement(); - inIteration = oldInIteration; + state.inIteration = oldInIteration; return { type: Syntax.WhileStatement, @@ -2283,7 +2825,7 @@ parseStatement: true, parseSourceElement: true */ } function parseForStatement() { - var init, test, update, left, right, body, oldInIteration; + var init, test, update, left, right, body, operator, oldInIteration, i, len; init = test = update = null; @@ -2294,28 +2836,38 @@ parseStatement: true, parseSourceElement: true */ if (match(';')) { lex(); } else { - if (matchKeyword('var') || matchKeyword('let')) { - allowIn = false; + if (matchKeyword('var') || matchKeyword('let') || matchKeyword('const')) { + state.allowIn = false; init = parseForVariableDeclaration(); - allowIn = true; - - if (init.declarations.length === 1 && matchKeyword('in')) { - lex(); - left = init; - right = parseExpression(); - init = null; + state.allowIn = true; + + if (init.declarations.length === 1) { + if (matchKeyword('in') || matchContextualKeyword('of')) { + operator = lookahead(); + if (!((operator.value === 'in' || init.kind !== 'var') && init.declarations[0].init)) { + lex(); + left = init; + right = parseExpression(); + init = null; + } + } } } else { - allowIn = false; + state.allowIn = false; init = parseExpression(); - allowIn = true; + state.allowIn = true; - if (matchKeyword('in')) { + if (matchContextualKeyword('of')) { + operator = lex(); + left = init; + right = parseExpression(); + init = null; + } else if (matchKeyword('in')) { // LeftHandSideExpression - if (matchKeyword('in') && (lastParenthesized !== init && !isLeftHandSide(init))) { + if (matchKeyword('in') && (state.lastParenthesized !== init && !isLeftHandSide(init))) { throwError({}, Messages.InvalidLHSInForIn); } - lex(); + operator = lex(); left = init; right = parseExpression(); init = null; @@ -2341,12 +2893,12 @@ parseStatement: true, parseSourceElement: true */ expect(')'); - oldInIteration = inIteration; - inIteration = true; + oldInIteration = state.inIteration; + state.inIteration = true; body = parseStatement(); - inIteration = oldInIteration; + state.inIteration = oldInIteration; if (typeof left === 'undefined') { return { @@ -2358,13 +2910,23 @@ parseStatement: true, parseSourceElement: true */ }; } - return { - type: Syntax.ForInStatement, - left: left, - right: right, - body: body, - each: false - }; + if (operator.value === 'in') { + return { + type: Syntax.ForInStatement, + left: left, + right: right, + body: body, + each: false + }; + } else { + return { + type: Syntax.ForOfStatement, + left: left, + right: right, + body: body, + each: false + }; + } } // 12.7 The continue statement @@ -2377,8 +2939,8 @@ parseStatement: true, parseSourceElement: true */ // Optimize the most common form: 'continue;'. if (source[index] === ';') { lex(); - - if (!inIteration) { + + if (!state.inIteration) { throwError({}, Messages.IllegalContinue); } @@ -2389,7 +2951,7 @@ parseStatement: true, parseSourceElement: true */ } if (peekLineTerminator()) { - if (!inIteration) { + if (!state.inIteration) { throwError({}, Messages.IllegalContinue); } @@ -2403,14 +2965,14 @@ parseStatement: true, parseSourceElement: true */ if (token.type === Token.Identifier) { label = parseVariableIdentifier(); - if (!Object.prototype.hasOwnProperty.call(labelSet, label.name)) { + if (!Object.prototype.hasOwnProperty.call(state.labelSet, label.name)) { throwError({}, Messages.UnknownLabel, label.name); } } consumeSemicolon(); - if (label === null && !inIteration) { + if (label === null && !state.inIteration) { throwError({}, Messages.IllegalContinue); } @@ -2430,8 +2992,8 @@ parseStatement: true, parseSourceElement: true */ // Optimize the most common form: 'break;'. if (source[index] === ';') { lex(); - - if (!(inIteration || inSwitch)) { + + if (!(state.inIteration || state.inSwitch)) { throwError({}, Messages.IllegalBreak); } @@ -2442,7 +3004,7 @@ parseStatement: true, parseSourceElement: true */ } if (peekLineTerminator()) { - if (!(inIteration || inSwitch)) { + if (!(state.inIteration || state.inSwitch)) { throwError({}, Messages.IllegalBreak); } @@ -2456,14 +3018,14 @@ parseStatement: true, parseSourceElement: true */ if (token.type === Token.Identifier) { label = parseVariableIdentifier(); - if (!Object.prototype.hasOwnProperty.call(labelSet, label.name)) { + if (!Object.prototype.hasOwnProperty.call(state.labelSet, label.name)) { throwError({}, Messages.UnknownLabel, label.name); } } consumeSemicolon(); - if (label === null && !(inIteration || inSwitch)) { + if (label === null && !(state.inIteration || state.inSwitch)) { throwError({}, Messages.IllegalBreak); } @@ -2480,8 +3042,8 @@ parseStatement: true, parseSourceElement: true */ expectKeyword('return'); - if (!inFunctionBody) { - throwError({}, Messages.IllegalReturn); + if (!state.inFunctionBody) { + throwErrorTolerant({}, Messages.IllegalReturn); } // 'return' followed by a space and an identifier is very common. @@ -2524,7 +3086,7 @@ parseStatement: true, parseSourceElement: true */ var object, body; if (strict) { - throwError({}, Messages.StrictModeWith); + throwErrorTolerant({}, Messages.StrictModeWith); } expectKeyword('with'); @@ -2546,10 +3108,20 @@ parseStatement: true, parseSourceElement: true */ // 12.10 The swith statement - function parseSwitchCase(test) { - var consequent = [], + function parseSwitchCase() { + var test, + consequent = [], statement; + if (matchKeyword('default')) { + lex(); + test = null; + } else { + expectKeyword('case'); + test = parseExpression(); + } + expect(':'); + while (index < length) { if (match('}') || matchKeyword('default') || matchKeyword('case')) { break; @@ -2569,7 +3141,7 @@ parseStatement: true, parseSourceElement: true */ } function parseSwitchStatement() { - var discriminant, cases, test, oldInSwitch; + var discriminant, cases, oldInSwitch; expectKeyword('switch'); @@ -2591,27 +3163,17 @@ parseStatement: true, parseSourceElement: true */ cases = []; - oldInSwitch = inSwitch; - inSwitch = true; + oldInSwitch = state.inSwitch; + state.inSwitch = true; while (index < length) { if (match('}')) { break; } - - if (matchKeyword('default')) { - lex(); - test = null; - } else { - expectKeyword('case'); - test = parseExpression(); - } - expect(':'); - - cases.push(parseSwitchCase(test)); + cases.push(parseSwitchCase()); } - inSwitch = oldInSwitch; + state.inSwitch = oldInSwitch; expect('}'); @@ -2655,7 +3217,7 @@ parseStatement: true, parseSourceElement: true */ param = parseExpression(); // 12.14.1 if (strict && param.type === Syntax.Identifier && isRestrictedWord(param.name)) { - throwError({}, Messages.StrictCatchVariable); + throwErrorTolerant({}, Messages.StrictCatchVariable); } } expect(')'); @@ -2773,13 +3335,13 @@ parseStatement: true, parseSourceElement: true */ if ((expr.type === Syntax.Identifier) && match(':')) { lex(); - if (Object.prototype.hasOwnProperty.call(labelSet, expr.name)) { + if (Object.prototype.hasOwnProperty.call(state.labelSet, expr.name)) { throwError({}, Messages.Redeclaration, 'Label', expr.name); } - labelSet[expr.name] = true; + state.labelSet[expr.name] = true; labeledBody = parseStatement(); - delete labelSet[expr.name]; + delete state.labelSet[expr.name]; return { type: Syntax.LabeledStatement, @@ -2799,7 +3361,8 @@ parseStatement: true, parseSourceElement: true */ // 13 Function Definition function parseFunctionSourceElements() { - var sourceElement, sourceElements = [], token, directive, firstRestricted, oldLabelSet, oldInIteration, oldInSwitch, oldInFunctionBody; + var sourceElement, sourceElements = [], token, directive, firstRestricted, + oldLabelSet, oldInIteration, oldInSwitch, oldInFunctionBody; expect('{'); @@ -2828,15 +3391,15 @@ parseStatement: true, parseSourceElement: true */ } } - oldLabelSet = labelSet; - oldInIteration = inIteration; - oldInSwitch = inSwitch; - oldInFunctionBody = inFunctionBody; + oldLabelSet = state.labelSet; + oldInIteration = state.inIteration; + oldInSwitch = state.inSwitch; + oldInFunctionBody = state.inFunctionBody; - labelSet = {}; - inIteration = false; - inSwitch = false; - inFunctionBody = true; + state.labelSet = {}; + state.inIteration = false; + state.inSwitch = false; + state.inFunctionBody = true; while (index < length) { if (match('}')) { @@ -2851,10 +3414,10 @@ parseStatement: true, parseSourceElement: true */ expect('}'); - labelSet = oldLabelSet; - inIteration = oldInIteration; - inSwitch = oldInSwitch; - inFunctionBody = oldInFunctionBody; + state.labelSet = oldLabelSet; + state.inIteration = oldInIteration; + state.inSwitch = oldInSwitch; + state.inFunctionBody = oldInFunctionBody; return { type: Syntax.BlockStatement, @@ -3031,7 +3594,24 @@ parseStatement: true, parseSourceElement: true */ } } - function parseSourceElements() { + function parseProgramElement() { + var token = lookahead(); + + if (token.type === Token.Keyword) { + switch (token.value) { + case 'module': + return parseModuleDeclaration(token.value); + case 'export': + return parseExportDeclaration(); + case 'import': + return parseImportDeclaration(); + } + } + + return parseSourceElement(); + } + + function parseProgramElements() { var sourceElement, sourceElements = [], token, directive, firstRestricted; while (index < length) { @@ -3040,7 +3620,7 @@ parseStatement: true, parseSourceElement: true */ break; } - sourceElement = parseSourceElement(); + sourceElement = parseProgramElement(); sourceElements.push(sourceElement); if (sourceElement.expression.type !== Syntax.Literal) { // this is not directive @@ -3060,7 +3640,7 @@ parseStatement: true, parseSourceElement: true */ } while (index < length) { - sourceElement = parseSourceElement(); + sourceElement = parseProgramElement(); if (typeof sourceElement === 'undefined') { break; } @@ -3069,12 +3649,49 @@ parseStatement: true, parseSourceElement: true */ return sourceElements; } + function parseModuleElement() { + return parseProgramElement(); + } + + function parseModuleElements() { + var list = [], + statement; + + while (index < length) { + if (match('}')) { + break; + } + statement = parseModuleElement(); + if (typeof statement === 'undefined') { + break; + } + list.push(statement); + } + + return list; + } + + function parseModuleBlock() { + var block; + + expect('{'); + + block = parseModuleElements(); + + expect('}'); + + return { + type: Syntax.BlockStatement, + body: block + }; + } + function parseProgram() { var program; strict = false; program = { type: Syntax.Program, - body: parseSourceElements() + body: parseProgramElements() }; return program; } @@ -3083,9 +3700,7 @@ parseStatement: true, parseSourceElement: true */ // the comments is active. function addComment(start, end, type, value) { - if (typeof start !== 'number') { - return; - } + assert(typeof start === 'number', 'Comment must have valid position'); // Because the way the actual token is scanned, often the comments // (if any) are skipped twice during the lexical analysis. @@ -3116,13 +3731,17 @@ parseStatement: true, parseSourceElement: true */ if (lineComment) { ch = nextChar(); - if (isLineTerminator(ch)) { + if (index >= length) { + lineComment = false; + comment += ch; + addComment(start, index, 'Line', comment); + } else if (isLineTerminator(ch)) { lineComment = false; - addComment(start, index - 1, 'Line', comment); + addComment(start, index, 'Line', comment); if (ch === '\r' && source[index] === '\n') { - index += 1; + ++index; } - lineNumber += 1; + ++lineNumber; lineStart = index; comment = ''; } else { @@ -3131,10 +3750,13 @@ parseStatement: true, parseSourceElement: true */ } else if (blockComment) { if (isLineTerminator(ch)) { if (ch === '\r' && source[index + 1] === '\n') { - index += 1; + ++index; + comment += '\r\n'; + } else { + comment += ch; } - lineNumber += 1; - index += 1; + ++lineNumber; + ++index; lineStart = index; if (index >= length) { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); @@ -3150,8 +3772,8 @@ parseStatement: true, parseSourceElement: true */ if (ch === '/') { comment = comment.substr(0, comment.length - 1); blockComment = false; - index += 1; - addComment(start, index - 1, 'Block', comment); + ++index; + addComment(start, index, 'Block', comment); comment = ''; } } @@ -3173,20 +3795,18 @@ parseStatement: true, parseSourceElement: true */ break; } } else if (isWhiteSpace(ch)) { - index += 1; + ++index; } else if (isLineTerminator(ch)) { - index += 1; + ++index; if (ch === '\r' && source[index] === '\n') { - index += 1; + ++index; } - lineNumber += 1; + ++lineNumber; lineStart = index; } else { break; } } - - addComment(start, index, (blockComment) ? 'Block' : 'Line', comment); } function collectToken() { @@ -3195,7 +3815,7 @@ parseStatement: true, parseSourceElement: true */ value; if (token.type !== Token.EOF) { - range = [token.range[0], token.range[1] - 1]; + range = [token.range[0], token.range[1]]; value = sliceSource(token.range[0], token.range[1]); extra.tokens.push({ type: TokenName[token.type], @@ -3228,7 +3848,7 @@ parseStatement: true, parseSourceElement: true */ extra.tokens.push({ type: 'RegularExpression', value: regex.literal, - range: [pos, index - 1] + range: [pos, index] }); return regex; @@ -3259,34 +3879,21 @@ parseStatement: true, parseSourceElement: true */ } 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 (isBinary(node.left)) { + visit(node.left); + } + if (isBinary(node.right)) { + visit(node.right); } - 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 - }; - } + if (range && typeof node.range === 'undefined') { + node.range = [node.left.range[0], node.right.range[1]]; + } + if (loc && typeof node.loc === 'undefined') { + node.loc = { + start: node.left.loc.start, + end: node.right.loc.end + }; } } @@ -3306,7 +3913,7 @@ parseStatement: true, parseSourceElement: true */ if (typeof node !== 'undefined') { if (range) { - rangeInfo[1] = index - 1; + rangeInfo[1] = index; node.range = rangeInfo; } @@ -3368,26 +3975,37 @@ parseStatement: true, parseSourceElement: true */ extra.parseConditionalExpression = parseConditionalExpression; extra.parseConstLetDeclaration = parseConstLetDeclaration; extra.parseEqualityExpression = parseEqualityExpression; + extra.parseExportDeclaration = parseExportDeclaration; + extra.parseExportSpecifier = parseExportSpecifier; + extra.parseExportSpecifierSetProperty = parseExportSpecifierSetProperty; extra.parseExpression = parseExpression; extra.parseForVariableDeclaration = parseForVariableDeclaration; extra.parseFunctionDeclaration = parseFunctionDeclaration; extra.parseFunctionExpression = parseFunctionExpression; + extra.parseGlob = parseGlob; + extra.parseImportDeclaration = parseImportDeclaration; + extra.parseImportSpecifier = parseImportSpecifier; extra.parseLogicalANDExpression = parseLogicalANDExpression; extra.parseLogicalORExpression = parseLogicalORExpression; extra.parseMultiplicativeExpression = parseMultiplicativeExpression; + extra.parseModuleDeclaration = parseModuleDeclaration; + extra.parseModuleBlock = parseModuleBlock; extra.parseNewExpression = parseNewExpression; extra.parseNonComputedMember = parseNonComputedMember; extra.parseNonComputedProperty = parseNonComputedProperty; extra.parseObjectProperty = parseObjectProperty; extra.parseObjectPropertyKey = parseObjectPropertyKey; + extra.parsePath = parsePath; extra.parsePostfixExpression = parsePostfixExpression; extra.parsePrimaryExpression = parsePrimaryExpression; extra.parseProgram = parseProgram; extra.parsePropertyFunction = parsePropertyFunction; + extra.parseProtoExpression = parseProtoExpression; extra.parseRelationalExpression = parseRelationalExpression; extra.parseStatement = parseStatement; extra.parseShiftExpression = parseShiftExpression; extra.parseSwitchCase = parseSwitchCase; + extra.parseTriangleLiteral = parseTriangleLiteral; extra.parseUnaryExpression = parseUnaryExpression; extra.parseVariableDeclaration = parseVariableDeclaration; extra.parseVariableIdentifier = parseVariableIdentifier; @@ -3404,27 +4022,38 @@ parseStatement: true, parseSourceElement: true */ parseComputedMember = wrapTracking(extra.parseComputedMember); parseConditionalExpression = wrapTracking(extra.parseConditionalExpression); parseConstLetDeclaration = wrapTracking(extra.parseConstLetDeclaration); + parseExportDeclaration = wrapTracking(parseExportDeclaration); + parseExportSpecifier = wrapTracking(parseExportSpecifier); + parseExportSpecifierSetProperty = wrapTracking(parseExportSpecifierSetProperty); parseEqualityExpression = wrapTracking(extra.parseEqualityExpression); parseExpression = wrapTracking(extra.parseExpression); parseForVariableDeclaration = wrapTracking(extra.parseForVariableDeclaration); parseFunctionDeclaration = wrapTracking(extra.parseFunctionDeclaration); parseFunctionExpression = wrapTracking(extra.parseFunctionExpression); + parseGlob = wrapTracking(extra.parseGlob); + parseImportDeclaration = wrapTracking(extra.parseImportDeclaration); + parseImportSpecifier = wrapTracking(extra.parseImportSpecifier); parseLogicalANDExpression = wrapTracking(extra.parseLogicalANDExpression); parseLogicalORExpression = wrapTracking(extra.parseLogicalORExpression); parseMultiplicativeExpression = wrapTracking(extra.parseMultiplicativeExpression); + parseModuleDeclaration = wrapTracking(extra.parseModuleDeclaration); + parseModuleBlock = wrapTracking(extra.parseModuleBlock); parseNewExpression = wrapTracking(extra.parseNewExpression); parseNonComputedMember = wrapTracking(extra.parseNonComputedMember); parseNonComputedProperty = wrapTracking(extra.parseNonComputedProperty); parseObjectProperty = wrapTracking(extra.parseObjectProperty); parseObjectPropertyKey = wrapTracking(extra.parseObjectPropertyKey); + parsePath = wrapTracking(extra.parsePath); parsePostfixExpression = wrapTracking(extra.parsePostfixExpression); parsePrimaryExpression = wrapTracking(extra.parsePrimaryExpression); parseProgram = wrapTracking(extra.parseProgram); parsePropertyFunction = wrapTracking(extra.parsePropertyFunction); + parseProtoExpression = wrapTracking(parseProtoExpression); parseRelationalExpression = wrapTracking(extra.parseRelationalExpression); parseStatement = wrapTracking(extra.parseStatement); parseShiftExpression = wrapTracking(extra.parseShiftExpression); parseSwitchCase = wrapTracking(extra.parseSwitchCase); + parseTriangleLiteral = wrapTracking(extra.parseTriangleLiteral); parseUnaryExpression = wrapTracking(extra.parseUnaryExpression); parseVariableDeclaration = wrapTracking(extra.parseVariableDeclaration); parseVariableIdentifier = wrapTracking(extra.parseVariableIdentifier); @@ -3462,26 +4091,37 @@ parseStatement: true, parseSourceElement: true */ parseConditionalExpression = extra.parseConditionalExpression; parseConstLetDeclaration = extra.parseConstLetDeclaration; parseEqualityExpression = extra.parseEqualityExpression; + parseExportDeclaration = extra.parseExportDeclaration; + parseExportSpecifier = extra.parseExportSpecifier; + parseExportSpecifierSetProperty = extra.parseExportSpecifierSetProperty; parseExpression = extra.parseExpression; parseForVariableDeclaration = extra.parseForVariableDeclaration; parseFunctionDeclaration = extra.parseFunctionDeclaration; parseFunctionExpression = extra.parseFunctionExpression; + parseGlob = extra.parseGlob; + parseImportDeclaration = extra.parseImportDeclaration; + parseImportSpecifier = extra.parseImportSpecifier; parseLogicalANDExpression = extra.parseLogicalANDExpression; parseLogicalORExpression = extra.parseLogicalORExpression; parseMultiplicativeExpression = extra.parseMultiplicativeExpression; + parseModuleDeclaration = extra.parseModuleDeclaration; + parseModuleBlock = extra.parseModuleBlock; parseNewExpression = extra.parseNewExpression; parseNonComputedMember = extra.parseNonComputedMember; parseNonComputedProperty = extra.parseNonComputedProperty; parseObjectProperty = extra.parseObjectProperty; parseObjectPropertyKey = extra.parseObjectPropertyKey; - parsePrimaryExpression = extra.parsePrimaryExpression; + parsePath = extra.parsePath; parsePostfixExpression = extra.parsePostfixExpression; + parsePrimaryExpression = extra.parsePrimaryExpression; parseProgram = extra.parseProgram; parsePropertyFunction = extra.parsePropertyFunction; + parseProtoExpression = extra.parseProtoExpression; parseRelationalExpression = extra.parseRelationalExpression; parseStatement = extra.parseStatement; parseShiftExpression = extra.parseShiftExpression; parseSwitchCase = extra.parseSwitchCase; + parseTriangleLiteral = extra.parseTriangleLiteral; parseUnaryExpression = extra.parseUnaryExpression; parseVariableDeclaration = extra.parseVariableDeclaration; parseVariableIdentifier = extra.parseVariableIdentifier; @@ -3497,7 +4137,7 @@ parseStatement: true, parseSourceElement: true */ var length = str.length, result = [], i; - for (i = 0; i < length; i += 1) { + for (i = 0; i < length; ++i) { result[i] = str.charAt(i); } return result; @@ -3517,12 +4157,14 @@ parseStatement: true, parseSourceElement: true */ lineStart = 0; length = source.length; buffer = null; - allowIn = true; - labelSet = {}; - inSwitch = false; - inIteration = false; - lastParenthesized = null; - inFunctionBody = false; + state = { + allowIn: true, + labelSet: {}, + lastParenthesized: null, + inFunctionBody: false, + inIteration: false, + inSwitch: false + }; extra = {}; if (typeof options !== 'undefined') { @@ -3535,6 +4177,9 @@ parseStatement: true, parseSourceElement: true */ if (typeof options.comment === 'boolean' && options.comment) { extra.comments = []; } + if (typeof options.tolerant === 'boolean' && options.tolerant) { + extra.errors = []; + } } if (length > 0) { @@ -3562,6 +4207,9 @@ parseStatement: true, parseSourceElement: true */ if (typeof extra.tokens !== 'undefined') { program.tokens = extra.tokens; } + if (typeof extra.errors !== 'undefined') { + program.errors = extra.errors; + } } catch (e) { throw e; } finally { @@ -3572,173 +4220,31 @@ parseStatement: true, parseSourceElement: true */ return program; } - // Executes visitor on the object and its children (recursively). + // Sync with package.json. + exports.version = '1.0.0-dev'; - function traverse(object, visitor, master) { - var key, child, parent, path; + exports.parse = parse; - parent = (typeof master === 'undefined') ? [] : master; + // Deep copy. + exports.Syntax = (function () { + var name, types = {}; - if (visitor.call(null, object, parent) === false) { - return; + if (typeof Object.create === 'function') { + types = Object.create(null); } - 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); + for (name in Syntax) { + if (Syntax.hasOwnProperty(name)) { + types[name] = Syntax[name]; } - } 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; + if (typeof Object.freeze === 'function') { + Object.freeze(types); + } - exports.Tracer = { - FunctionEntrance: traceFunctionEntrance - }; + return types; + }()); }(typeof exports === 'undefined' ? (esprima = {}) : exports)); /* vim: set sw=4 ts=4 et tw=80 : */ diff --git a/escodegen.js b/escodegen.js index 0590190..59087f6 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1,4 +1,5 @@ /* + Copyright (C) 2012 John Freeman <[email protected]> Copyright (C) 2012 Ariya Hidayat <[email protected]> Copyright (C) 2012 Mathias Bynens <[email protected]> Copyright (C) 2012 Joost-Wim Boekesteijn <[email protected]> @@ -37,6 +38,9 @@ var Syntax, Precedence, BinaryPrecedence, + VisitorKeys, + VisitorOption, + isArray, base, indent, extra, @@ -139,10 +143,12 @@ indent: null, base: null, parse: null, + comment: false, format: { indent: { style: ' ', - base: 0 + base: 0, + adjustMultilineComment: false } } }; @@ -179,6 +185,42 @@ return result; } + isArray = Array.isArray; + if (!isArray) { + isArray = function isArray(array) { + return Object.prototype.toString.call(array) === '[object Array]'; + }; + } + + function endsWithLineTerminator(value) { + return (/(?:\r\n|[\n\r])$/).test(value); + } + + function shallowCopy(obj) { + var ret = {}, key; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + ret[key] = obj[key]; + } + } + return ret; + } + + function deepCopy(obj) { + var ret = {}, key, val; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + val = obj[key]; + if (typeof val === 'object' && val !== null) { + ret[key] = deepCopy(val); + } else { + ret[key] = val; + } + } + } + return ret; + } + function updateDeeply(target, override) { var key, val; @@ -247,18 +289,71 @@ return '\'' + result + '\''; } + 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); + } + function addIndent(stmt) { return base + stmt; } + function adjustMultilineComment(value) { + var array, i, len, line, j, ch, spaces; + + spaces = Number.MAX_VALUE; + array = value.split(/\r\n|[\r\n]/); + + // first line doesn't have indentation + for (i = 1, len = array.length; i < len; i += 1) { + line = array[i]; + j = 0; + while (j < line.length && isWhiteSpace(line[j])) { + j += 1; + } + if (spaces > j) { + spaces = j; + } + } + + if (spaces % 2 === 1) { + // /* + // * + // */ + // If spaces are odd number, above pattern is considered. + // We waste 1 space. + spaces -= 1; + } + for (i = 1, len = array.length; i < len; i += 1) { + array[i] = addIndent(array[i].slice(spaces)); + } + return array.join('\n'); + } + + function generateComment(comment) { + if (comment.type === 'Line') { + // Esprima always produce last line comment with LineTerminator + return '//' + comment.value; + } + if (extra.format.indent.adjustMultilineComment && /[\n\r]/.test(comment.value)) { + return adjustMultilineComment('/*' + comment.value + '*/'); + } + return '/*' + comment.value + '*/'; + } + function parenthesize(text, current, should) { - return (current < should) ? '(' + text + ')' : text; + if (current < should) { + return '(' + text + ')'; + } + return text; } function maybeBlock(stmt, suffix) { var previousBase, result; - if (stmt.type === Syntax.BlockStatement) { + if (stmt.type === Syntax.BlockStatement && (!extra.comment || !stmt.leadingComments)) { result = ' ' + generateStatement(stmt); if (suffix) { return result + ' '; @@ -266,7 +361,7 @@ return result; } - if (stmt.type === Syntax.EmptyStatement) { + if (stmt.type === Syntax.EmptyStatement && (!extra.comment || !stmt.leadingComments)) { result = ';'; } else { previousBase = base; @@ -293,18 +388,23 @@ return result + ')' + maybeBlock(node.body); } - function generateExpression(expr, precedence) { - var result, currentPrecedence, previousBase, i, len, raw; + function generateExpression(expr, option) { + var result, precedence, currentPrecedence, previousBase, i, len, raw, allowIn, allowCall; - if (!precedence) { - precedence = Precedence.Sequence; - } + precedence = option.precedence; + allowIn = option.allowIn; + allowCall = option.allowCall; switch (expr.type) { case Syntax.SequenceExpression: result = ''; + allowIn |= (Precedence.Sequence < precedence); for (i = 0, len = expr.expressions.length; i < len; i += 1) { - result += generateExpression(expr.expressions[i], Precedence.Assignment); + result += generateExpression(expr.expressions[i], { + precedence: Precedence.Assignment, + allowIn: allowIn, + allowCall: true + }); if ((i + 1) < len) { result += ', '; } @@ -313,19 +413,41 @@ break; case Syntax.AssignmentExpression: + allowIn |= (Precedence.Assignment < precedence); result = parenthesize( - generateExpression(expr.left, Precedence.Call) + ' ' + expr.operator + ' ' + - generateExpression(expr.right, Precedence.Assignment), + generateExpression(expr.left, { + precedence: Precedence.Call, + allowIn: allowIn, + allowCall: true + }) + ' ' + expr.operator + ' ' + + generateExpression(expr.right, { + precedence: Precedence.Assignment, + allowIn: allowIn, + allowCall: true + }), Precedence.Assignment, precedence ); break; case Syntax.ConditionalExpression: + allowIn |= (Precedence.Conditional < precedence); result = parenthesize( - generateExpression(expr.test, Precedence.LogicalOR) + ' ? ' + - generateExpression(expr.consequent, Precedence.Assignment) + ' : ' + - generateExpression(expr.alternate, Precedence.Assignment), + generateExpression(expr.test, { + precedence: Precedence.LogicalOR, + allowIn: allowIn, + allowCall: true + }) + ' ? ' + + generateExpression(expr.consequent, { + precedence: Precedence.Assignment, + allowIn: allowIn, + allowCall: true + }) + ' : ' + + generateExpression(expr.alternate, { + precedence: Precedence.Assignment, + allowIn: allowIn, + allowCall: true + }), Precedence.Conditional, precedence ); @@ -335,51 +457,91 @@ 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 + allowIn |= (currentPrecedence < precedence); + + result = + generateExpression(expr.left, { + precedence: currentPrecedence, + allowIn: allowIn, + allowCall: true + }) + ' ' + expr.operator + ' ' + + generateExpression(expr.right, { + precedence: currentPrecedence + 1, + allowIn: allowIn, + allowCall: true + }); + + if (expr.operator === 'in' && !allowIn) { result = '(' + result + ')'; } else { result = parenthesize(result, currentPrecedence, precedence); } + break; case Syntax.CallExpression: - result = ''; + result = generateExpression(expr.callee, { + precedence: Precedence.Call, + allowIn: true, + allowCall: true + }); + + result += '('; for (i = 0, len = expr['arguments'].length; i < len; i += 1) { - result += generateExpression(expr['arguments'][i], Precedence.Assignment); + result += generateExpression(expr['arguments'][i], { + precedence: Precedence.Assignment, + allowIn: true, + allowCall: true + }); if ((i + 1) < len) { result += ', '; } } - result = parenthesize( - generateExpression(expr.callee, Precedence.Call) + '(' + result + ')', - Precedence.Call, - precedence - ); + result += ')'; + + if (!allowCall) { + result = '(' + result + ')'; + } else { + result = parenthesize(result, Precedence.Call, precedence); + } break; case Syntax.NewExpression: - result = ''; + result = 'new ' + generateExpression(expr.callee, { + precedence: Precedence.New, + allowIn: true, + allowCall: false + }); + + result += '('; for (i = 0, len = expr['arguments'].length; i < len; i += 1) { - result += generateExpression(expr['arguments'][i], Precedence.Assignment); + result += generateExpression(expr['arguments'][i], { + precedence: Precedence.Assignment, + allowIn: true, + allowCall: true + }); if ((i + 1) < len) { result += ', '; } } - result = parenthesize( - 'new ' + generateExpression(expr.callee, Precedence.New) + '(' + result + ')', - Precedence.New, - precedence - ); + result += ')'; + + result = parenthesize(result, Precedence.New, precedence); break; case Syntax.MemberExpression: - result = generateExpression(expr.object, Precedence.Call); + result = generateExpression(expr.object, { + precedence: Precedence.Call, + allowIn: true, + allowCall: allowCall + }); + if (expr.computed) { - result += '[' + generateExpression(expr.property) + ']'; + result += '[' + generateExpression(expr.property, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: allowCall + }) + ']'; } else { if (expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') { if (result.indexOf('.') < 0) { @@ -390,6 +552,7 @@ } result += '.' + expr.property.name; } + result = parenthesize(result, Precedence.Member, precedence); break; @@ -399,13 +562,15 @@ result += ' '; } result = parenthesize( - result + generateExpression(expr.argument, Precedence.Unary + - ( + result + generateExpression(expr.argument, { + precedence: Precedence.Unary + ( expr.argument.type === Syntax.UnaryExpression && - expr.operator.length < 3 && - expr.argument.operator === expr.operator ? 1 : 0 - ) + expr.operator.length < 3 && + expr.argument.operator === expr.operator ? 1 : 0 ), + allowIn: true, + allowCall: true + }), Precedence.Unary, precedence ); @@ -415,14 +580,21 @@ if (expr.prefix) { result = parenthesize( expr.operator + - generateExpression(expr.argument, Precedence.Unary), + generateExpression(expr.argument, { + precedence: Precedence.Unary, + allowIn: true, + allowCall: true + }), Precedence.Unary, precedence ); } else { result = parenthesize( - generateExpression(expr.argument, Precedence.Postfix) + - expr.operator, + generateExpression(expr.argument, { + precedence: Precedence.Postfix, + allowIn: true, + allowCall: true + }) + expr.operator, Precedence.Postfix, precedence ); @@ -452,7 +624,11 @@ result += ','; } } else { - result += addIndent(generateExpression(expr.elements[i], Precedence.Assignment)); + result += addIndent(generateExpression(expr.elements[i], { + precedence: Precedence.Assignment, + allowIn: true, + allowCall: true + })); } if ((i + 1) < len) { result += ',\n'; @@ -464,11 +640,22 @@ case Syntax.Property: if (expr.kind === 'get' || expr.kind === 'set') { - result = expr.kind + ' ' + generateExpression(expr.key) + - generateFunctionBody(expr.value); + result = expr.kind + ' ' + generateExpression(expr.key, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + generateFunctionBody(expr.value); } else { - result = generateExpression(expr.key) + ': ' + - generateExpression(expr.value, Precedence.Assignment); + result = + generateExpression(expr.key, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ': ' + generateExpression(expr.value, { + precedence: Precedence.Assignment, + allowIn: true, + allowCall: true + }); } break; @@ -481,7 +668,11 @@ previousBase = base; base += indent; for (i = 0, len = expr.properties.length; i < len; i += 1) { - result += addIndent(generateExpression(expr.properties[i])); + result += addIndent(generateExpression(expr.properties[i], { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + })); if ((i + 1) < len) { result += ',\n'; } @@ -542,8 +733,13 @@ return result; } - function generateStatement(stmt) { - var i, len, result, previousBase; + function generateStatement(stmt, option) { + var i, len, result, previousBase, comment, save, ret, node, allowIn; + + allowIn = true; + if (option) { + allowIn = option.allowIn; + } switch (stmt.type) { case Syntax.BlockStatement: @@ -576,13 +772,21 @@ break; case Syntax.DoWhileStatement: - result = 'do' + maybeBlock(stmt.body, true) + 'while (' + generateExpression(stmt.test) + ');'; + result = 'do' + maybeBlock(stmt.body, true) + 'while (' + generateExpression(stmt.test, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ');'; break; case Syntax.CatchClause: previousBase = base; base += indent; - result = ' catch (' + generateExpression(stmt.param) + ')'; + result = ' catch (' + generateExpression(stmt.param, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ')'; base = previousBase; result += maybeBlock(stmt.body); break; @@ -596,7 +800,11 @@ break; case Syntax.ExpressionStatement: - result = generateExpression(stmt.expression); + result = generateExpression(stmt.expression, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }); // 12.4 '{', 'function' is not allowed in this position. // wrap espression with parentheses if (result[0] === '{' || result.indexOf('function ') === 0) { @@ -608,27 +816,54 @@ case Syntax.VariableDeclarator: if (stmt.init) { - result = stmt.id.name + ' = ' + generateExpression(stmt.init, Precedence.Assignment); + result = stmt.id.name + ' = ' + generateExpression(stmt.init, { + precedence: Precedence.Assignment, + allowIn: allowIn, + allowCall: true + }); } else { result = stmt.id.name; } break; case Syntax.VariableDeclaration: - result = stmt.kind + ' '; + 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]); + result += ' ' + generateStatement(stmt.declarations[0], { + allowIn: allowIn + }); } else { + // VariableDeclarator is typed as Statement, + // but joined with comma (not LineTerminator). + // So if comment is attached to target node, we should specialize. 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 += ', '; + + node = stmt.declarations[0]; + if (extra.comment && node.leadingComments) { + result += '\n' + addIndent(generateStatement(node, { + allowIn: allowIn + })); + } else { + result += ' ' + generateStatement(node, { + allowIn: allowIn + }); + } + + for (i = 1, len = stmt.declarations.length; i < len; i += 1) { + node = stmt.declarations[i]; + if (extra.comment && node.leadingComments) { + result += ',\n' + addIndent(generateStatement(node, { + allowIn: allowIn + })); + } else { + result += ', ' + generateStatement(node, { + allowIn: allowIn + }); } } base = previousBase; @@ -637,7 +872,11 @@ break; case Syntax.ThrowStatement: - result = 'throw ' + generateExpression(stmt.argument) + ';'; + result = 'throw ' + generateExpression(stmt.argument, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ';'; break; case Syntax.TryStatement: @@ -653,7 +892,11 @@ case Syntax.SwitchStatement: previousBase = base; base += indent; - result = 'switch (' + generateExpression(stmt.discriminant) + ') {\n'; + result = 'switch (' + generateExpression(stmt.discriminant, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ') {\n'; base = previousBase; if (stmt.cases) { for (i = 0, len = stmt.cases.length; i < len; i += 1) { @@ -667,7 +910,11 @@ previousBase = base; base += indent; if (stmt.test) { - result = 'case ' + generateExpression(stmt.test) + ':'; + result = 'case ' + generateExpression(stmt.test, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ':'; } else { result = 'default:'; } @@ -691,20 +938,32 @@ if (stmt.alternate.type === Syntax.IfStatement) { previousBase = base; base += indent; - result = 'if (' + generateExpression(stmt.test) + ')'; + result = 'if (' + generateExpression(stmt.test, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ')'; base = previousBase; result += maybeBlock(stmt.consequent, true) + 'else ' + generateStatement(stmt.alternate); } else { previousBase = base; base += indent; - result = 'if (' + generateExpression(stmt.test) + ')'; + result = 'if (' + generateExpression(stmt.test, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ')'; base = previousBase; result += maybeBlock(stmt.consequent, true) + 'else' + maybeBlock(stmt.alternate); } } else { previousBase = base; base += indent; - result = 'if (' + generateExpression(stmt.test) + ')'; + result = 'if (' + generateExpression(stmt.test, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ')'; base = previousBase; result += maybeBlock(stmt.consequent); } @@ -716,22 +975,36 @@ result = 'for ('; if (stmt.init) { if (stmt.init.type === Syntax.VariableDeclaration) { - result += generateStatement(stmt.init); + result += generateStatement(stmt.init, { + allowIn: false + }); } else { - result += generateExpression(stmt.init) + ';'; + result += generateExpression(stmt.init, { + precedence: Precedence.Sequence, + allowIn: false, + allowCall: true + }) + ';'; } } else { result += ';'; } if (stmt.test) { - result += ' ' + generateExpression(stmt.test) + ';'; + result += ' ' + generateExpression(stmt.test, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ';'; } else { result += ';'; } if (stmt.update) { - result += ' ' + generateExpression(stmt.update) + ')'; + result += ' ' + generateExpression(stmt.update, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ')'; } else { result += ')'; } @@ -745,18 +1018,28 @@ if (stmt.left.type === Syntax.VariableDeclaration) { previousBase = base; base += indent + indent; - result += stmt.left.kind + ' ' + generateStatement(stmt.left.declarations[0]); + result += stmt.left.kind + ' ' + generateStatement(stmt.left.declarations[0], { + allowIn: false + }); base = previousBase; } else { previousBase = base; base += indent; - result += generateExpression(stmt.left, Precedence.Call); + result += generateExpression(stmt.left, { + precedence: Precedence.Call, + allowIn: true, + allowCall: true + }); base = previousBase; } previousBase = base; base += indent; - result += ' in ' + generateExpression(stmt.right) + ')'; + result += ' in ' + generateExpression(stmt.right, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ')'; base = previousBase; result += maybeBlock(stmt.body); break; @@ -785,7 +1068,11 @@ case Syntax.ReturnStatement: if (stmt.argument) { - result = 'return ' + generateExpression(stmt.argument) + ';'; + result = 'return ' + generateExpression(stmt.argument, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ';'; } else { result = 'return;'; } @@ -794,7 +1081,11 @@ case Syntax.WhileStatement: previousBase = base; base += indent; - result = 'while (' + generateExpression(stmt.test) + ')'; + result = 'while (' + generateExpression(stmt.test, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ')'; base = previousBase; result += maybeBlock(stmt.body); break; @@ -802,7 +1093,11 @@ case Syntax.WithStatement: previousBase = base; base += indent; - result = 'with (' + generateExpression(stmt.object) + ')'; + result = 'with (' + generateExpression(stmt.object, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }) + ')'; base = previousBase; result += maybeBlock(stmt.body); break; @@ -814,6 +1109,42 @@ if (result === undefined) { throw new Error('Unknown statement type: ' + stmt.type); } + + // Attach comments + + if (extra.comment) { + if (stmt.leadingComments) { + save = result; + + comment = stmt.leadingComments[0]; + result = generateComment(comment); + if (!endsWithLineTerminator(result)) { + result += '\n'; + } + + for (i = 1, len = stmt.leadingComments.length; i < len; i += 1) { + comment = stmt.leadingComments[i]; + ret = generateComment(comment); + if (!endsWithLineTerminator(ret)) { + ret += '\n'; + } + result += addIndent(ret); + } + + result += addIndent(save); + } + + if (stmt.trailingComments) { + for (i = 0, len = stmt.trailingComments.length; i < len; i += 1) { + comment = stmt.trailingComments[i]; + if (!endsWithLineTerminator(result)) { + result += '\n'; + } + result += addIndent(generateComment(comment)); + } + } + } + return result; } @@ -845,6 +1176,7 @@ base = stringRepeat(indent, options.format.indent.base); parse = options.parse; } + extra = options; switch (node.type) { case Syntax.BlockStatement: @@ -889,7 +1221,11 @@ case Syntax.ThisExpression: case Syntax.UnaryExpression: case Syntax.UpdateExpression: - return generateExpression(node); + return generateExpression(node, { + precedence: Precedence.Sequence, + allowIn: true, + allowCall: true + }); default: break; @@ -897,10 +1233,295 @@ throw new Error('Unknown node type: ' + node.type); } + // simple visitor implementation + + VisitorKeys = { + AssignmentExpression: ['left', 'right'], + ArrayExpression: ['elements'], + BlockStatement: ['body'], + BinaryExpression: ['left', 'right'], + BreakStatement: ['label'], + CallExpression: ['callee', 'arguments'], + CatchClause: ['param', 'body'], + ConditionalExpression: ['test', 'consequent', 'alternate'], + ContinueStatement: ['label'], + DoWhileStatement: ['body', 'test'], + DebuggerStatement: [], + EmptyStatement: [], + ExpressionStatement: ['expression'], + ForStatement: ['init', 'test', 'update', 'body'], + ForInStatement: ['left', 'right', 'body'], + FunctionDeclaration: ['id', 'params', 'body'], + FunctionExpression: ['id', 'params', 'body'], + Identifier: [], + IfStatement: ['test', 'consequent', 'alternate'], + Literal: [], + LabeledStatement: ['label', 'body'], + LogicalExpression: ['left', 'right'], + MemberExpression: ['object', 'property'], + NewExpression: ['callee', 'arguments'], + ObjectExpression: ['properties'], + Program: ['body'], + Property: ['key', 'value'], + ReturnStatement: ['argument'], + SequenceExpression: ['expressions'], + SwitchStatement: ['descriminant', 'cases'], + SwitchCase: ['test', 'consequent'], + ThisExpression: [], + ThrowStatement: ['argument'], + TryStatement: ['block', 'handlers', 'finalizer'], + UnaryExpression: ['argument'], + UpdateExpression: ['argument'], + VariableDeclaration: ['declarations'], + VariableDeclarator: ['id', 'init'], + WhileStatement: ['test', 'body'], + WithStatement: ['object', 'body'] + }; + + VisitorOption = { + Break: 1, + Skip: 2 + }; + + function traverse(top, visitor) { + var worklist, leavelist, node, ret, current, current2, candidates, candidate; + + worklist = [ top ]; + leavelist = []; + + while (worklist.length) { + node = worklist.pop(); + + if (node) { + if (visitor.enter) { + ret = visitor.enter(node); + } else { + ret = undefined; + } + + if (ret === VisitorOption.Break) { + return; + } + + worklist.push(null); + leavelist.push(node); + + if (ret !== VisitorOption.Skip) { + candidates = VisitorKeys[node.type]; + current = candidates.length; + while ((current -= 1) >= 0) { + candidate = node[candidates[current]]; + if (candidate) { + if (isArray(candidate)) { + current2 = candidate.length; + while ((current2 -= 1) >= 0) { + if (candidate[current2]) { + worklist.push(candidate[current2]); + } + } + } else { + worklist.push(candidate); + } + } + } + } + } else { + node = leavelist.pop(); + if (visitor.leave) { + ret = visitor.leave(node); + } else { + ret = undefined; + } + if (ret === VisitorOption.Break) { + return; + } + } + } + } + + + // based on LLVM libc++ upper_bound / lower_bound + // MIT License + + function upperBound(array, func) { + var diff, len, i, current; + + len = array.length; + i = 0; + + while (len) { + diff = len >>> 1; + current = i + diff; + if (func(array[current])) { + len = diff; + } else { + i = current + 1; + len -= diff + 1; + } + } + return i; + } + + function lowerBound(array, func) { + var diff, len, i, current; + + len = array.length; + i = 0; + + while (len) { + diff = len >>> 1; + current = i + diff; + if (func(array[current])) { + i = current + 1; + len -= diff + 1; + } else { + len = diff; + } + } + return i; + } + + function extendCommentRange(comment, tokens) { + var target, token; + + target = upperBound(tokens, function search(token) { + return token.range[0] > comment.range[0]; + }); + + comment.extendedRange = [comment.range[0], comment.range[1]]; + + if (target !== tokens.length) { + comment.extendedRange[1] = tokens[target].range[0]; + } + + target -= 1; + if (target >= 0) { + if (target < tokens.length) { + comment.extendedRange[0] = tokens[target].range[1]; + } else if (token.length) { + comment.extendedRange[1] = tokens[tokens.length - 1].range[0]; + } + } + + return comment; + } + + function attachComments(tree, providedComments, tokens) { + // At first, we should calculate extended comment ranges. + var comments = [], len, i; + + if (!tree.range) { + throw new Error('attachComments needs range information'); + } + + for (i = 0, len = providedComments.length; i < len; i += 1) { + comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens)); + } + + // + // For example, + // + // var i1 = 10, // test + // i2 = 20; + // + // Above comment is probably trailing to i1 = 10. + // So we specialize comma token. + // + // And second, comma first style example, + // + // var i = [ + // 10 // testing + // , 20 // testing 2 + // ]; + // + // 'testing' comment is trailing to 10 is proper. + // + // So we add some specialize path. + // + // 1. check comment is'nt containing LineTerminator + // 2. check LineTerminator is not found between previous token and comment + // 3. check LineTerminator is found between comment and next token + // + // If above conditions are all true, + // we assume this comment should be attached to previous token as trailing comment. + // + + // This traverse attacher is based on John Freeman's implementation. + traverse(tree, { + cursor: 0, + enter: function (node) { + var comment; + + while (this.cursor < comments.length) { + comment = comments[this.cursor]; + if (comment.extendedRange[1] > node.range[0]) { + break; + } + + if (comment.extendedRange[1] === node.range[0]) { + if (!node.leadingComments) { + node.leadingComments = []; + } + node.leadingComments.push(comment); + comments.splice(this.cursor, 1); + } else { + this.cursor += 1; + } + } + + // already out of owned node + if (this.cursor === comments.length) { + return VisitorOption.Break; + } + + if (comments[this.cursor].extendedRange[0] > node.range[1]) { + return VisitorOption.Skip; + } + } + }); + + traverse(tree, { + cursor: 0, + leave: function (node) { + var comment; + + while (this.cursor < comments.length) { + comment = comments[this.cursor]; + if (node.range[1] < comment.extendedRange[0]) { + break; + } + + if (node.range[1] === comment.extendedRange[0]) { + if (!node.trailingComments) { + node.trailingComments = []; + } + node.trailingComments.push(comment); + comments.splice(this.cursor, 1); + } else { + this.cursor += 1; + } + } + + // already out of owned node + if (this.cursor === comments.length) { + return VisitorOption.Break; + } + + if (comments[this.cursor].extendedRange[0] > node.range[1]) { + return VisitorOption.Skip; + } + } + }); + + return tree; + } + // Sync with package.json. - exports.version = '0.0.4-dev'; + exports.version = '0.0.4'; exports.generate = generate; + exports.traverse = traverse; + exports.attachComments = attachComments; }(typeof exports === 'undefined' ? (escodegen = {}) : exports)); /* vim: set sw=4 ts=4 et tw=80 : */
