Partial parser comments
This commit is contained in:
parent
237bfbfb9b
commit
32e62f1cfc
646
acorn.js
646
acorn.js
@ -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) {
|
||||||
|
if (tokCommentsAfter) {
|
||||||
node.commentsAfter = 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,112 +1206,149 @@
|
|||||||
return finishNode(node, "VariableDeclaration");
|
return finishNode(node, "VariableDeclaration");
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePropertyName() {
|
// ### Expression parsing
|
||||||
if (tokType === _num || tokType === _string) return parseExprAtom();
|
|
||||||
return parseIdent(true);
|
// These nest, from the most general expression type at the top to
|
||||||
|
// 'atomic', nondivisible expression types at the bottom. Most of
|
||||||
|
// the functions will simply let the function(s) below them parse,
|
||||||
|
// and, *if* the syntactic construct they handle is present, wrap
|
||||||
|
// the AST node that the inner parser gave them in another node.
|
||||||
|
|
||||||
|
// Parse a full expression. The arguments are used to forbid comma
|
||||||
|
// sequences (in argument lists, array literals, or object literals)
|
||||||
|
// or the `in` operator (in for loops initalization expressions).
|
||||||
|
|
||||||
|
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 parseIdent(liberal) {
|
// Parse an assignment expression. This includes applications of
|
||||||
var node = startNode();
|
// operators like `+=`.
|
||||||
if (tokType !== _name) {
|
|
||||||
if (liberal && tokType.keyword) node.name = tokType.keyword;
|
function parseMaybeAssign(noIn) {
|
||||||
else unexpected();
|
var left = parseMaybeConditional(noIn);
|
||||||
} else node.name = tokVal;
|
if (tokType.isAssign) {
|
||||||
|
var node = startNodeFrom(left);
|
||||||
|
node.operator = tokVal;
|
||||||
|
node.left = left;
|
||||||
next();
|
next();
|
||||||
return finishNode(node, "Identifier");
|
node.right = parseMaybeAssign(noIn);
|
||||||
|
checkLVal(left);
|
||||||
|
return finishNode(node, "AssignmentExpression");
|
||||||
|
}
|
||||||
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFunction(node, isStatement) {
|
// Parse a ternary conditional (`?:`) operator.
|
||||||
if (tokType === _name) node.id = parseIdent();
|
|
||||||
else if (isStatement) unexpected();
|
function parseMaybeConditional(noIn) {
|
||||||
else node.id = null;
|
var expr = parseExprOps(noIn);
|
||||||
node.params = [];
|
if (eat(_question)) {
|
||||||
var first = true;
|
var node = startNodeFrom(expr);
|
||||||
expect(_parenL);
|
node.test = expr;
|
||||||
while (!eat(_parenR)) {
|
node.consequent = parseExpression(true);
|
||||||
if (!first) expect(_comma); else first = false;
|
expect(_colon);
|
||||||
node.params.push(parseIdent());
|
node.alternate = parseExpression(true, noIn);
|
||||||
|
return finishNode(node, "ConditionalExpression");
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldInFunc = inFunction, oldLabels = labels;
|
// Start the precedence parser.
|
||||||
inFunction = true; labels = [];
|
|
||||||
node.body = parseBlock(true);
|
function parseExprOps(noIn) {
|
||||||
if (strict || node.body.body.length && isUseStrict(node.body.body[0])) {
|
return parseExprOp(parseMaybeUnary(noIn), -1, noIn);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inFunction = oldInFunc; labels = oldLabels;
|
// Parse binary operators with the operator precedence parsing
|
||||||
return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
|
// 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.
|
||||||
|
|
||||||
// EXPRESSION PARSING
|
function parseExprOp(left, minPrec, noIn) {
|
||||||
|
var prec = tokType.binop;
|
||||||
function parseExprList(close, allowTrailingComma, allowEmpty) {
|
if (prec != null && (!noIn || tokType !== _in)) {
|
||||||
var elts = [], first = true;
|
if (prec > minPrec) {
|
||||||
while (!eat(close)) {
|
var node = startNodeFrom(left);
|
||||||
if (!first) {
|
node.left = left;
|
||||||
expect(_comma);
|
node.operator = tokVal;
|
||||||
if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
|
|
||||||
} else first = false;
|
|
||||||
|
|
||||||
if (allowEmpty && tokType === _comma) elts.push(null);
|
|
||||||
else elts.push(parseExpression(true));
|
|
||||||
}
|
|
||||||
return elts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNew() {
|
|
||||||
var node = startNode();
|
|
||||||
next();
|
next();
|
||||||
node.callee = parseSubscripts(parseExprAtom(false), false);
|
node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
|
||||||
if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
|
var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
|
||||||
else node.arguments = [];
|
return parseExprOp(node, minPrec, noIn);
|
||||||
return finishNode(node, "NewExpression");
|
}
|
||||||
|
}
|
||||||
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseObj() {
|
// Parse unary operators, both prefix and postfix.
|
||||||
var node = startNode(), first = true;
|
|
||||||
node.properties = [];
|
function parseMaybeUnary(noIn) {
|
||||||
|
if (tokType.prefix) {
|
||||||
|
var node = startNode(), update = tokType.isUpdate;
|
||||||
|
node.operator = tokVal;
|
||||||
|
node.prefix = true;
|
||||||
next();
|
next();
|
||||||
while (!eat(_braceR)) {
|
node.argument = parseMaybeUnary(noIn);
|
||||||
if (!first) {
|
if (update) checkLVal(node.argument);
|
||||||
expect(_comma);
|
else if (strict && node.operator === "delete" &&
|
||||||
if (options.allowTrailingCommas && eat(_braceR)) break;
|
node.argument.type === "Identifier")
|
||||||
} else first = false;
|
raise(node.start, "Deleting local variable in strict mode");
|
||||||
|
return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
|
||||||
|
}
|
||||||
|
var expr = parseExprSubscripts();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
var prop = {key: parsePropertyName()}, isGetSet, kind;
|
// Parse call, dot, and `[]`-subscript expressions.
|
||||||
if (eat(_colon)) {
|
|
||||||
prop.value = parseExpression(true);
|
function parseExprSubscripts() {
|
||||||
kind = prop.kind = "init";
|
return parseSubscripts(parseExprAtom());
|
||||||
} 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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
node.properties.push(prop);
|
// Parse an atomic expression — either a single token that is an
|
||||||
}
|
// expression, an expression started by a keyword like `function` or
|
||||||
return finishNode(node, "ObjectExpression");
|
// `new`, or an expression wrapped in punctuation like `()`, `[]`,
|
||||||
}
|
// or `{}`.
|
||||||
|
|
||||||
function parseExprAtom() {
|
function parseExprAtom() {
|
||||||
switch (tokType) {
|
switch (tokType) {
|
||||||
@ -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);
|
|
||||||
node.computed = false;
|
|
||||||
return parseSubscripts(finishNode(node, "MemberExpression"), allowCalls);
|
|
||||||
} else if (eat(_bracketL)) {
|
|
||||||
var node = startNodeFrom(base);
|
|
||||||
node.object = base;
|
|
||||||
node.property = parseExpression();
|
|
||||||
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) {
|
function parseNew() {
|
||||||
return parseSubscripts(parseExprAtom(allowCalls), allowCalls);
|
var node = startNode();
|
||||||
}
|
|
||||||
|
|
||||||
function parseMaybeUnary(noIn) {
|
|
||||||
if (tokType.prefix) {
|
|
||||||
var node = startNode(), update = tokType.isUpdate;
|
|
||||||
node.operator = tokVal;
|
|
||||||
node.prefix = true;
|
|
||||||
next();
|
next();
|
||||||
node.argument = parseMaybeUnary(noIn);
|
node.callee = parseSubscripts(parseExprAtom(false), true);
|
||||||
if (update) checkLVal(node.argument);
|
if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
|
||||||
else if (strict && node.operator === "delete" &&
|
else node.arguments = [];
|
||||||
node.argument.type === "Identifier")
|
return finishNode(node, "NewExpression");
|
||||||
raise(node.start, "Deleting local variable in strict mode");
|
|
||||||
return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
|
|
||||||
}
|
}
|
||||||
var expr = parseExprSubscripts(true);
|
|
||||||
while (tokType.postfix && !canInsertSemicolon()) {
|
// Parse an object literal.
|
||||||
var node = startNodeFrom(expr);
|
|
||||||
node.operator = tokVal;
|
function parseObj() {
|
||||||
node.prefix = false;
|
var node = startNode(), first = true, sawGetSet = false;
|
||||||
node.argument = expr;
|
node.properties = [];
|
||||||
checkLVal(expr);
|
|
||||||
next();
|
next();
|
||||||
expr = finishNode(node, "UpdateExpression");
|
while (!eat(_braceR)) {
|
||||||
|
if (!first) {
|
||||||
|
expect(_comma);
|
||||||
|
if (options.allowTrailingCommas && eat(_braceR)) break;
|
||||||
|
} else first = false;
|
||||||
|
|
||||||
|
var prop = {key: parsePropertyName()}, isGetSet = false, 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 = 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");
|
||||||
}
|
}
|
||||||
return expr;
|
}
|
||||||
|
}
|
||||||
|
node.properties.push(prop);
|
||||||
|
}
|
||||||
|
return finishNode(node, "ObjectExpression");
|
||||||
}
|
}
|
||||||
|
|
||||||
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).
|
||||||
|
|
||||||
|
function parseFunction(node, isStatement) {
|
||||||
|
if (tokType === _name) node.id = parseIdent();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inFunction = oldInFunc; labels = oldLabels;
|
||||||
|
return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseExprList(close, allowTrailingComma, allowEmpty) {
|
||||||
|
var elts = [], first = true;
|
||||||
|
while (!eat(close)) {
|
||||||
|
if (!first) {
|
||||||
|
expect(_comma);
|
||||||
|
if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
|
||||||
|
} else first = false;
|
||||||
|
|
||||||
|
if (allowEmpty && tokType === _comma) elts.push(null);
|
||||||
|
else elts.push(parseExpression(true));
|
||||||
|
}
|
||||||
|
return elts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIdent(liberal) {
|
||||||
|
var node = startNode();
|
||||||
|
if (tokType !== _name) {
|
||||||
|
if (liberal && tokType.keyword) node.name = tokType.keyword;
|
||||||
|
else unexpected();
|
||||||
|
} else node.name = tokVal;
|
||||||
next();
|
next();
|
||||||
node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
|
return finishNode(node, "Identifier");
|
||||||
var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
|
|
||||||
return parseExprOp(node, minPrec, noIn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseExprOps(noIn) {
|
|
||||||
return parseExprOp(parseMaybeUnary(noIn), -1, noIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseMaybeAssign(noIn) {
|
|
||||||
var left = parseMaybeConditional(noIn);
|
|
||||||
if (tokType.isAssign) {
|
|
||||||
var node = startNodeFrom(left);
|
|
||||||
node.operator = tokVal;
|
|
||||||
node.left = left;
|
|
||||||
next();
|
|
||||||
node.right = parseMaybeAssign(noIn);
|
|
||||||
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);
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user