Partial parser comments

This commit is contained in:
Marijn Haverbeke 2012-10-02 09:29:52 +02:00
parent 237bfbfb9b
commit 32e62f1cfc
2 changed files with 413 additions and 239 deletions

646
acorn.js
View File

@ -748,12 +748,37 @@
// ## Parser // ## Parser
// A recursive descent parser operates by defining functions for all
// syntactic elements, and recursively calling those, each function
// advancing the input stream and returning an AST node. Precedence
// of constructs (for example, the fact that `!x[1]` means `!(x[1])`
// instead of `(!x)[1]` is handled by the fact that the parser
// function that parses unary prefix operators is called first, and
// in turn calls the function that parses `[]` subscripts — that
// way, it'll receive the node for `x[1]` already parsed, and wraps
// *that* in the unary operator node.
//
// Acorn uses an [operator precedence parser][opp] to handle binary
// operator precedence, because it is much more compact than using
// the technique outlined above, which uses different, nesting
// functions to specify precedence, for all of the ten binary
// precedence levels that JavaScript defines.
//
// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
// ### Parser utilities
// Continue to the next token.
function next() { function next() {
lastStart = tokStart; lastStart = tokStart;
lastEnd = tokEnd; lastEnd = tokEnd;
readToken(); readToken();
} }
// Enter strict mode. Re-reads the next token to please pedantic
// tests ("use strict"; 010; -- should fail).
function setStrict(strct) { function setStrict(strct) {
strict = strct; strict = strct;
tokPos = options.linePositions ? lastEnd.offset : lastEnd; tokPos = options.linePositions ? lastEnd.offset : lastEnd;
@ -761,8 +786,11 @@
readToken(); readToken();
} }
// Start an AST node, attaching a start offset and optionally a
// `commentsBefore` property to it.
function startNode() { function startNode() {
var node = {type: null, start: tokStart}; var node = {type: null, start: tokStart, end: null};
if (options.trackComments && tokCommentsBefore) { if (options.trackComments && tokCommentsBefore) {
node.commentsBefore = tokCommentsBefore; node.commentsBefore = tokCommentsBefore;
tokCommentsBefore = null; tokCommentsBefore = null;
@ -770,6 +798,11 @@
return node; return node;
} }
// Start a node whose start offset/comments information should be
// based on the start of another node. For example, a binary
// operator node is only started after its left-hand side has
// already been parsed.
function startNodeFrom(other) { function startNodeFrom(other) {
var node = {type: null, start: other.start}; var node = {type: null, start: other.start};
if (other.commentsBefore) { if (other.commentsBefore) {
@ -779,14 +812,94 @@
return node; return node;
} }
// Finish an AST node, adding `type`, `end`, and `commentsAfter`
// properties.
//
// We keep track of the last node that we finished, in order
// 'bubble' `commentsAfter` properties up to the biggest node. I.e.
// in '`1 + 1 // foo', the comment should be attached to the binary
// operator node, not the second literal node.
var lastFinishedNode;
function finishNode(node, type) { function finishNode(node, type) {
if (type != null) node.type = type; node.type = type;
node.end = lastEnd; node.end = lastEnd;
if (options.trackComments && tokCommentsAfter) if (options.trackComments) {
node.commentsAfter = tokCommentsAfter; if (tokCommentsAfter) {
node.commentsAfter = tokCommentsAfter;
tokCommentsAfter = null;
} else if (lastFinishedNode && lastFinishedNode.end === lastEnd) {
node.commentsAfter = lastFinishedNode.commentsAfter;
lastFinishedNode.commentsAfter = null;
}
lastFinishedNode = node;
}
return node; return node;
} }
// Test whether a statement node is the string literal `"use strict"`.
function isUseStrict(stmt) {
return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" &&
stmt.expression.type === "Literal" && stmt.expression.value === "use strict";
}
// Predicate that tests whether the next token is of the given
// type, and if yes, consumes it as a side effect.
function eat(type) {
if (tokType === type) {
next();
return true;
}
}
// Test whether a semicolon can be inserted at the current position.
function canInsertSemicolon() {
return tokType === _eof || tokType === _braceR ||
!options.strictSemicolons &&
newline.test(options.linePositions ? input.slice(lastEnd.offset, tokStart.offset)
: input.slice(lastEnd, tokStart));
}
// Consume a semicolon, or, failing that, see if we are allowed to
// pretend that there is a semicolon at this position.
function semicolon() {
if (!eat(_semi) && !canInsertSemicolon()) unexpected();
}
// Expect a token of a given type. If found, consume it, otherwise,
// raise an unexpected token error.
function expect(type) {
if (tokType === type) next();
else unexpected();
}
// Raise an unexpected token error.
function unexpected() {
raise(tokStart, "Unexpected token");
}
// Verify that a node is an lval — something that can be assigned
// to.
function checkLVal(expr) {
if (expr.type !== "Identifier" && expr.type !== "MemberExpression")
raise(expr.start, "Assigning to rvalue");
if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name))
raise(expr.start, "Assigning to " + expr.name + " in strict mode");
}
// ### Statement parsing
// Parse a program. Initializes the parser, reads any number of
// statements, and wraps them in a Program node.
function parseTopLevel() { function parseTopLevel() {
initTokenState(); initTokenState();
lastStart = lastEnd = options.linePositions ? curLinePos() : tokPos; lastStart = lastEnd = options.linePositions ? curLinePos() : tokPos;
@ -805,46 +918,25 @@
return finishNode(node, "Program"); return finishNode(node, "Program");
}; };
function isUseStrict(stmt) { var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"};
return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" &&
stmt.expression.type === "Literal" && stmt.expression.value === "use strict";
}
function eat(type) { // Parse a single statement.
if (tokType === type) { //
next(); // If expecting a statement and finding a slash operator, parse a
return true; // regular expression literal. This is to handle cases like
} // `if (foo) /blah/.exec(foo);`, where looking at the previous token
} // does not help.
function canInsertSemicolon() {
return tokType === _eof || tokType === _braceR ||
!options.strictSemicolons &&
newline.test(options.linePositions ? input.slice(lastEnd.offset, tokStart.offset)
: input.slice(lastEnd, tokStart));
}
function semicolon() {
if (!eat(_semi) && !canInsertSemicolon()) unexpected();
}
function expect(type) {
if (tokType === type) next();
else unexpected();
}
function unexpected() {
raise(tokStart, "Unexpected token");
}
function parseStatement() { function parseStatement() {
// if expecting a statement and found a slash as operator,
// it must be a literal regexp.
if (tokType === _slash) if (tokType === _slash)
readToken(true); readToken(true);
var starttype = tokType, node = startNode(); var starttype = tokType, node = startNode();
// Most types of statements are recognized by the keyword they
// start with. Many are trivial to parse, some require a bit of
// complexity.
switch (starttype) { switch (starttype) {
case _break: case _continue: case _break: case _continue:
next(); next();
@ -855,6 +947,9 @@
node.label = parseIdent(); node.label = parseIdent();
semicolon(); semicolon();
} }
// Verify that there is an actual destination to break or
// continue to.
for (var i = 0; i < labels.length; ++i) { for (var i = 0; i < labels.length; ++i) {
var lab = labels[i]; var lab = labels[i];
if ((node.label == null || lab.name === node.label.name) && if ((node.label == null || lab.name === node.label.name) &&
@ -869,15 +964,24 @@
case _do: case _do:
next(); next();
labels.push({kind: "loop"}); labels.push(loopLabel);
node.body = parseStatement(); node.body = parseStatement();
labels.pop(); labels.pop();
expect(_while); expect(_while);
node.test = parseParenExpression(); node.test = parseParenExpression();
return finishNode(node, "DoWhileStatement"); return finishNode(node, "DoWhileStatement");
// Disambiguating between a `for` and a `for`/`in` loop is
// non-trivial. Basically, we have to parse the init `var`
// statement or expression, disallowing the `in` operator (see
// the second parameter to `parseExpression`), and then check
// whether the next token is `in`. When there is no init part
// (semicolon immediately after the opening parenthesis), it is
// a regular `for` loop.
case _for: case _for:
next(); next();
labels.push(loopLabel);
expect(_parenL); expect(_parenL);
if (tokType === _semi) return parseFor(node, null); if (tokType === _semi) return parseFor(node, null);
if (tokType === _var) { if (tokType === _var) {
@ -906,6 +1010,11 @@
case _return: case _return:
if (!inFunction) raise(tokStart, "'return' outside of function"); if (!inFunction) raise(tokStart, "'return' outside of function");
next(); next();
// In `return` (and `break`/`continue`), the keywords with
// optional arguments, we eagerly look for a semicolon or the
// possibility to insert one.
if (eat(_semi) || canInsertSemicolon()) node.argument = null; if (eat(_semi) || canInsertSemicolon()) node.argument = null;
else { node.argument = parseExpression(); semicolon(); } else { node.argument = parseExpression(); semicolon(); }
return finishNode(node, "ReturnStatement"); return finishNode(node, "ReturnStatement");
@ -915,8 +1024,13 @@
node.discriminant = parseParenExpression(); node.discriminant = parseParenExpression();
node.cases = []; node.cases = [];
expect(_braceL); expect(_braceL);
labels.push({kind: "switch"}); labels.push(switchLabel);
for (var cur, sawDefault; !eat(_braceR);) {
// Statements under must be grouped (by label) in SwitchCase
// nodes. `cur` is used to keep the node that we are currently
// adding statements to.
for (var cur, sawDefault; tokType != _braceR;) {
if (tokType === _case || tokType === _default) { if (tokType === _case || tokType === _default) {
var isCase = tokType === _case; var isCase = tokType === _case;
if (cur) finishNode(cur, "SwitchCase"); if (cur) finishNode(cur, "SwitchCase");
@ -935,6 +1049,7 @@
} }
} }
if (cur) finishNode(cur, "SwitchCase"); if (cur) finishNode(cur, "SwitchCase");
next(); // Closing brace
labels.pop(); labels.pop();
return finishNode(node, "SwitchStatement"); return finishNode(node, "SwitchStatement");
@ -973,7 +1088,7 @@
case _while: case _while:
next(); next();
node.test = parseParenExpression(); node.test = parseParenExpression();
labels.push({kind: "loop"}); labels.push(loopLabel);
node.body = parseStatement(); node.body = parseStatement();
labels.pop(); labels.pop();
return finishNode(node, "WhileStatement"); return finishNode(node, "WhileStatement");
@ -992,6 +1107,12 @@
next(); next();
return finishNode(node, "EmptyStatement"); return finishNode(node, "EmptyStatement");
// If the statement does not start with a statement keyword or a
// brace, it's an ExpressionStatement or LabeledStatement. We
// simply start parsing an expression, and afterwards, if the
// next token is a colon and the expression was a simple
// Identifier node, we switch to interpreting it as a label.
default: default:
var maybeName = tokVal, expr = parseExpression(); var maybeName = tokVal, expr = parseExpression();
if (starttype === _name && expr.type === "Identifier" && eat(_colon)) { if (starttype === _name && expr.type === "Identifier" && eat(_colon)) {
@ -1010,6 +1131,9 @@
} }
} }
// Used for constructs like `switch` and `if` that insist on
// parentheses around their expression.
function parseParenExpression() { function parseParenExpression() {
expect(_parenL); expect(_parenL);
var val = parseExpression(); var val = parseExpression();
@ -1017,6 +1141,10 @@
return val; return val;
} }
// Parse a semicolon-enclosed block of statements, handling `"use
// strict"` declarations when `allowStrict` is true (used for
// function bodies).
function parseBlock(allowStrict) { function parseBlock(allowStrict) {
var node = startNode(), first = true, strict = false, oldStrict; var node = startNode(), first = true, strict = false, oldStrict;
node.body = []; node.body = [];
@ -1034,6 +1162,10 @@
return finishNode(node, "BlockStatement"); return finishNode(node, "BlockStatement");
} }
// Parse a regular `for` loop. The disambiguation code in
// `parseStatement` will already have parsed the init statement or
// expression.
function parseFor(node, init) { function parseFor(node, init) {
node.init = init; node.init = init;
expect(_semi); expect(_semi);
@ -1041,22 +1173,24 @@
expect(_semi); expect(_semi);
node.update = tokType === _parenR ? null : parseExpression(); node.update = tokType === _parenR ? null : parseExpression();
expect(_parenR); expect(_parenR);
labels.push({kind: "loop"});
node.body = parseStatement(); node.body = parseStatement();
labels.pop(); labels.pop();
return finishNode(node, "ForStatement"); return finishNode(node, "ForStatement");
} }
// Parse a `for`/`in` loop.
function parseForIn(node, init) { function parseForIn(node, init) {
node.left = init; node.left = init;
node.right = parseExpression(); node.right = parseExpression();
expect(_parenR); expect(_parenR);
labels.push({kind: "loop"});
node.body = parseStatement(); node.body = parseStatement();
labels.pop(); labels.pop();
return finishNode(node, "ForInStatement"); return finishNode(node, "ForInStatement");
} }
// Parse a list of variable declarations.
function parseVar(node, noIn) { function parseVar(node, noIn) {
node.declarations = []; node.declarations = [];
node.kind = "var"; node.kind = "var";
@ -1072,113 +1206,150 @@
return finishNode(node, "VariableDeclaration"); return finishNode(node, "VariableDeclaration");
} }
function parsePropertyName() { // ### Expression parsing
if (tokType === _num || tokType === _string) return parseExprAtom();
return parseIdent(true);
}
function parseIdent(liberal) { // These nest, from the most general expression type at the top to
var node = startNode(); // 'atomic', nondivisible expression types at the bottom. Most of
if (tokType !== _name) { // the functions will simply let the function(s) below them parse,
if (liberal && tokType.keyword) node.name = tokType.keyword; // and, *if* the syntactic construct they handle is present, wrap
else unexpected(); // the AST node that the inner parser gave them in another node.
} else node.name = tokVal;
next();
return finishNode(node, "Identifier");
}
function parseFunction(node, isStatement) { // Parse a full expression. The arguments are used to forbid comma
if (tokType === _name) node.id = parseIdent(); // sequences (in argument lists, array literals, or object literals)
else if (isStatement) unexpected(); // or the `in` operator (in for loops initalization expressions).
else node.id = null;
node.params = []; function parseExpression(noComma, noIn) {
var first = true; var expr = parseMaybeAssign(noIn);
expect(_parenL); if (!noComma && tokType === _comma) {
while (!eat(_parenR)) { var node = startNodeFrom(expr);
if (!first) expect(_comma); else first = false; node.expressions = [expr];
node.params.push(parseIdent()); while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
return finishNode(node, "SequenceExpression");
} }
return expr;
}
var oldInFunc = inFunction, oldLabels = labels; // Parse an assignment expression. This includes applications of
inFunction = true; labels = []; // operators like `+=`.
node.body = parseBlock(true);
if (strict || node.body.body.length && isUseStrict(node.body.body[0])) { function parseMaybeAssign(noIn) {
for (var i = node.id ? -1 : 0; i < node.params.length; ++i) { var left = parseMaybeConditional(noIn);
var id = i < 0 ? node.id : node.params[i]; if (tokType.isAssign) {
if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name)) var node = startNodeFrom(left);
raise(id.start, "Defining '" + id.name + "' in strict mode"); node.operator = tokVal;
if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name) node.left = left;
raise(id.start, "Argument name clash in strict mode"); next();
node.right = parseMaybeAssign(noIn);
checkLVal(left);
return finishNode(node, "AssignmentExpression");
}
return left;
}
// Parse a ternary conditional (`?:`) operator.
function parseMaybeConditional(noIn) {
var expr = parseExprOps(noIn);
if (eat(_question)) {
var node = startNodeFrom(expr);
node.test = expr;
node.consequent = parseExpression(true);
expect(_colon);
node.alternate = parseExpression(true, noIn);
return finishNode(node, "ConditionalExpression");
}
return expr;
}
// Start the precedence parser.
function parseExprOps(noIn) {
return parseExprOp(parseMaybeUnary(noIn), -1, noIn);
}
// Parse binary operators with the operator precedence parsing
// algorithm. `left` is the left-hand side of the operator.
// `minPrec` provides context that allows the function to stop and
// defer further parser to one of its callers when it encounters an
// operator that has a lower precedence than the set it is parsing.
function parseExprOp(left, minPrec, noIn) {
var prec = tokType.binop;
if (prec != null && (!noIn || tokType !== _in)) {
if (prec > minPrec) {
var node = startNodeFrom(left);
node.left = left;
node.operator = tokVal;
next();
node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
return parseExprOp(node, minPrec, noIn);
} }
} }
return left;
inFunction = oldInFunc; labels = oldLabels;
return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
} }
// EXPRESSION PARSING // Parse unary operators, both prefix and postfix.
function parseExprList(close, allowTrailingComma, allowEmpty) { function parseMaybeUnary(noIn) {
var elts = [], first = true; if (tokType.prefix) {
while (!eat(close)) { var node = startNode(), update = tokType.isUpdate;
if (!first) { node.operator = tokVal;
expect(_comma); node.prefix = true;
if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break; next();
} else first = false; node.argument = parseMaybeUnary(noIn);
if (update) checkLVal(node.argument);
if (allowEmpty && tokType === _comma) elts.push(null); else if (strict && node.operator === "delete" &&
else elts.push(parseExpression(true)); node.argument.type === "Identifier")
raise(node.start, "Deleting local variable in strict mode");
return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
} }
return elts; var expr = parseExprSubscripts();
} while (tokType.postfix && !canInsertSemicolon()) {
var node = startNodeFrom(expr);
function parseNew() { node.operator = tokVal;
var node = startNode(); node.prefix = false;
next(); node.argument = expr;
node.callee = parseSubscripts(parseExprAtom(false), false); checkLVal(expr);
if (eat(_parenL)) node.arguments = parseExprList(_parenR, false); next();
else node.arguments = []; expr = finishNode(node, "UpdateExpression");
return finishNode(node, "NewExpression");
}
function parseObj() {
var node = startNode(), first = true;
node.properties = [];
next();
while (!eat(_braceR)) {
if (!first) {
expect(_comma);
if (options.allowTrailingCommas && eat(_braceR)) break;
} else first = false;
var prop = {key: parsePropertyName()}, isGetSet, kind;
if (eat(_colon)) {
prop.value = parseExpression(true);
kind = prop.kind = "init";
} else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
(prop.key.name === "get" || prop.key.name === "set")) {
isGetSet = true;
kind = prop.kind = prop.key.name;
prop.key = parsePropertyName();
if (!tokType === _parenL) unexpected();
prop.value = parseFunction(startNode(), false);
} else unexpected();
if (prop.key.type === "Identifier" && (strict || isGetSet)) {
for (var i = 0; i < node.properties.length; ++i) {
var other = node.properties[i];
if (other.key.name === prop.key.name) {
var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
kind === "init" && (other.kind === "get" || other.kind === "set");
if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
if (conflict) raise(prop.key.start, "Redefinition of property");
}
}
}
node.properties.push(prop);
} }
return finishNode(node, "ObjectExpression"); return expr;
} }
// Parse call, dot, and `[]`-subscript expressions.
function parseExprSubscripts() {
return parseSubscripts(parseExprAtom());
}
function parseSubscripts(base, noCalls) {
if (eat(_dot)) {
var node = startNodeFrom(base);
node.object = base;
node.property = parseIdent(true);
node.computed = false;
return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
} else if (eat(_bracketL)) {
var node = startNodeFrom(base);
node.object = base;
node.property = parseExpression();
node.computed = true;
expect(_bracketR);
return parseSubscripts(finishNode(node, "MemberExpression"), noCalls);
} else if (!noCalls && eat(_parenL)) {
var node = startNodeFrom(base);
node.callee = base;
node.arguments = parseExprList(_parenR, false);
return parseSubscripts(finishNode(node, "CallExpression"), noCalls);
} else return base;
}
// Parse an atomic expression — either a single token that is an
// expression, an expression started by a keyword like `function` or
// `new`, or an expression wrapped in punctuation like `()`, `[]`,
// or `{}`.
function parseExprAtom() { function parseExprAtom() {
switch (tokType) { switch (tokType) {
case _name: case _name:
@ -1228,120 +1399,123 @@
} }
} }
function parseSubscripts(base, allowCalls) { // New's precedence is slightly tricky. It must allow its argument
if (eat(_dot)) { // to be a `[]` or dot subscript expression, but not a call — at
var node = startNodeFrom(base); // least, not without wrapping it in parentheses. Thus, it uses the
node.object = base;
node.property = parseIdent(true); function parseNew() {
node.computed = false; var node = startNode();
return parseSubscripts(finishNode(node, "MemberExpression"), allowCalls); next();
} else if (eat(_bracketL)) { node.callee = parseSubscripts(parseExprAtom(false), true);
var node = startNodeFrom(base); if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
node.object = base; else node.arguments = [];
node.property = parseExpression(); return finishNode(node, "NewExpression");
node.computed = true;
expect(_bracketR);
return parseSubscripts(finishNode(node, "MemberExpression"), allowCalls);
} else if (allowCalls && eat(_parenL)) {
var node = startNodeFrom(base);
node.callee = base;
node.arguments = parseExprList(_parenR, false);
return parseSubscripts(finishNode(node, "CallExpression"), allowCalls);
} else return base;
} }
function parseExprSubscripts(allowCalls) { // Parse an object literal.
return parseSubscripts(parseExprAtom(allowCalls), allowCalls);
}
function parseMaybeUnary(noIn) { function parseObj() {
if (tokType.prefix) { var node = startNode(), first = true, sawGetSet = false;
var node = startNode(), update = tokType.isUpdate; node.properties = [];
node.operator = tokVal; next();
node.prefix = true; while (!eat(_braceR)) {
next(); if (!first) {
node.argument = parseMaybeUnary(noIn); expect(_comma);
if (update) checkLVal(node.argument); if (options.allowTrailingCommas && eat(_braceR)) break;
else if (strict && node.operator === "delete" && } else first = false;
node.argument.type === "Identifier")
raise(node.start, "Deleting local variable in strict mode"); var prop = {key: parsePropertyName()}, isGetSet = false, kind;
return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); if (eat(_colon)) {
prop.value = parseExpression(true);
kind = prop.kind = "init";
} else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
(prop.key.name === "get" || prop.key.name === "set")) {
isGetSet = sawGetSet = true;
kind = prop.kind = prop.key.name;
prop.key = parsePropertyName();
if (!tokType === _parenL) unexpected();
prop.value = parseFunction(startNode(), false);
} else unexpected();
// getters and setters are not allowed to clash — either with
// each other or with an init property — and in strict mode,
// init properties are also not allowed to be repeated.
if (prop.key.type === "Identifier" && (strict || sawGetSet)) {
for (var i = 0; i < node.properties.length; ++i) {
var other = node.properties[i];
if (other.key.name === prop.key.name) {
var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
kind === "init" && (other.kind === "get" || other.kind === "set");
if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
if (conflict) raise(prop.key.start, "Redefinition of property");
}
}
}
node.properties.push(prop);
} }
var expr = parseExprSubscripts(true); return finishNode(node, "ObjectExpression");
while (tokType.postfix && !canInsertSemicolon()) {
var node = startNodeFrom(expr);
node.operator = tokVal;
node.prefix = false;
node.argument = expr;
checkLVal(expr);
next();
expr = finishNode(node, "UpdateExpression");
}
return expr;
} }
function parseExprOp(left, minPrec, noIn) { function parsePropertyName() {
var prec = tokType.binop; if (tokType === _num || tokType === _string) return parseExprAtom();
if (prec != null && (!noIn || tokType !== _in)) { return parseIdent(true);
if (prec > minPrec) { }
var node = startNodeFrom(left);
node.left = left; // Parse a function declaration or literal (depending on the
node.operator = tokVal; // `isStatement` parameter).
next();
node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn); function parseFunction(node, isStatement) {
var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression"); if (tokType === _name) node.id = parseIdent();
return parseExprOp(node, minPrec, noIn); else if (isStatement) unexpected();
else node.id = null;
node.params = [];
var first = true;
expect(_parenL);
while (!eat(_parenR)) {
if (!first) expect(_comma); else first = false;
node.params.push(parseIdent());
}
var oldInFunc = inFunction, oldLabels = labels;
inFunction = true; labels = [];
node.body = parseBlock(true);
if (strict || node.body.body.length && isUseStrict(node.body.body[0])) {
for (var i = node.id ? -1 : 0; i < node.params.length; ++i) {
var id = i < 0 ? node.id : node.params[i];
if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name))
raise(id.start, "Defining '" + id.name + "' in strict mode");
if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name)
raise(id.start, "Argument name clash in strict mode");
} }
} }
return left;
inFunction = oldInFunc; labels = oldLabels;
return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
} }
function parseExprOps(noIn) { function parseExprList(close, allowTrailingComma, allowEmpty) {
return parseExprOp(parseMaybeUnary(noIn), -1, noIn); var elts = [], first = true;
} while (!eat(close)) {
if (!first) {
expect(_comma);
if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
} else first = false;
function parseMaybeConditional(noIn) { if (allowEmpty && tokType === _comma) elts.push(null);
var expr = parseExprOps(noIn); else elts.push(parseExpression(true));
if (eat(_question)) {
var node = startNodeFrom(expr);
node.test = expr;
node.consequent = parseExpression(true);
expect(_colon);
node.alternate = parseExpression(true, noIn);
return finishNode(node, "ConditionalExpression");
} }
return expr; return elts;
} }
function parseMaybeAssign(noIn) { function parseIdent(liberal) {
var left = parseMaybeConditional(noIn); var node = startNode();
if (tokType.isAssign) { if (tokType !== _name) {
var node = startNodeFrom(left); if (liberal && tokType.keyword) node.name = tokType.keyword;
node.operator = tokVal; else unexpected();
node.left = left; } else node.name = tokVal;
next(); next();
node.right = parseMaybeAssign(noIn); return finishNode(node, "Identifier");
checkLVal(left);
return finishNode(node, "AssignmentExpression");
}
return left;
} }
function parseExpression(noComma, noIn) {
var expr = parseMaybeAssign(noIn);
if (!noComma && tokType === _comma) {
var node = startNodeFrom(expr);
node.expressions = [expr];
while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
return finishNode(node, "SequenceExpression");
}
return expr;
}
function checkLVal(expr) {
if (expr.type !== "Identifier" && expr.type !== "MemberExpression")
raise(expr.start, "Assigning to rvalue");
if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name))
raise(expr.start, "Assigning to " + expr.name + " in strict mode");
}
})(typeof exports === "undefined" ? (window.acorn = {}) : exports); })(typeof exports === "undefined" ? (window.acorn = {}) : exports);

View File

@ -2495,7 +2495,7 @@ test("switch (answer) { case 42: /* perfect */ bingo() }", {
value: 42, value: 42,
end: {line: 1, column: 25} end: {line: 1, column: 25}
}, },
end: {line: 1, column: 50} end: {line: 1, column: 48}
} }
], ],
end: {line: 1, column: 50} end: {line: 1, column: 50}
@ -9390,7 +9390,7 @@ test("switch (answer) { case 42: hi(); break; }", {
value: 42, value: 42,
end: {line: 1, column: 25} end: {line: 1, column: 25}
}, },
end: {line: 1, column: 41} end: {line: 1, column: 39}
} }
], ],
end: {line: 1, column: 41} end: {line: 1, column: 41}
@ -9461,7 +9461,7 @@ test("switch (answer) { case 42: hi(); break; default: break }", {
} }
], ],
test: null, test: null,
end: {line: 1, column: 56} end: {line: 1, column: 54}
} }
], ],
end: {line: 1, column: 56} end: {line: 1, column: 56}