2013-10-31 15:48:26 -07:00
|
|
|
// Acorn: Loose parser
|
|
|
|
//
|
|
|
|
// This module provides an alternative parser (`parse_dammit`) that
|
|
|
|
// exposes that same interface as `parse`, but will try to parse
|
|
|
|
// anything as JavaScript, repairing syntax error the best it can.
|
|
|
|
// There are circumstances in which it will raise an error and give
|
|
|
|
// up, but they are very rare. The resulting AST will be a mostly
|
|
|
|
// valid JavaScript AST (as per the [Mozilla parser API][api], except
|
|
|
|
// that:
|
|
|
|
//
|
|
|
|
// - Return outside functions is allowed
|
|
|
|
//
|
|
|
|
// - Label consistency (no conflicts, break only to existing labels)
|
|
|
|
// is not enforced.
|
|
|
|
//
|
|
|
|
// - Bogus Identifier nodes with a name of `"✖"` are inserted whenever
|
|
|
|
// the parser got too confused to return anything meaningful.
|
|
|
|
//
|
|
|
|
// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
|
|
|
|
//
|
|
|
|
// The expected use for this is to *first* try `acorn.parse`, and only
|
|
|
|
// if that fails switch to `parse_dammit`. The loose parser might
|
|
|
|
// parse badly indented code incorrectly, so **don't** use it as
|
|
|
|
// your default parser.
|
|
|
|
//
|
|
|
|
// Quite a lot of acorn.js is duplicated here. The alternative was to
|
|
|
|
// add a *lot* of extra cruft to that file, making it less readable
|
|
|
|
// and slower. Copying and editing the code allowed me to make
|
|
|
|
// invasive changes and simplifications without creating a complicated
|
|
|
|
// tangle.
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
(function(root, mod) {
|
2013-10-31 15:48:26 -07:00
|
|
|
if (typeof exports == "object" && typeof module == "object") return mod(exports, require("./acorn")); // CommonJS
|
|
|
|
if (typeof define == "function" && define.amd) return define(["exports", "./acorn"], mod); // AMD
|
2014-02-04 17:52:38 -08:00
|
|
|
mod(root.acorn || (root.acorn = {}), root.acorn); // Plain browser env
|
|
|
|
})(this, function(exports, acorn) {
|
2013-10-31 15:48:26 -07:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var tt = acorn.tokTypes;
|
|
|
|
|
|
|
|
var options, input, fetchToken, context;
|
|
|
|
|
|
|
|
exports.parse_dammit = function(inpt, opts) {
|
|
|
|
if (!opts) opts = {};
|
|
|
|
input = String(inpt);
|
|
|
|
options = opts;
|
|
|
|
if (!opts.tabSize) opts.tabSize = 4;
|
|
|
|
fetchToken = acorn.tokenize(inpt, opts);
|
|
|
|
sourceFile = options.sourceFile || null;
|
|
|
|
context = [];
|
|
|
|
nextLineStart = 0;
|
|
|
|
ahead.length = 0;
|
|
|
|
next();
|
|
|
|
return parseTopLevel();
|
|
|
|
};
|
|
|
|
|
|
|
|
var lastEnd, token = {start: 0, end: 0}, ahead = [];
|
|
|
|
var curLineStart, nextLineStart, curIndent, lastEndLoc, sourceFile;
|
|
|
|
|
|
|
|
function next() {
|
|
|
|
lastEnd = token.end;
|
|
|
|
if (options.locations)
|
|
|
|
lastEndLoc = token.endLoc;
|
|
|
|
|
|
|
|
if (ahead.length)
|
|
|
|
token = ahead.shift();
|
|
|
|
else
|
|
|
|
token = readToken();
|
|
|
|
|
|
|
|
if (token.start >= nextLineStart) {
|
|
|
|
while (token.start >= nextLineStart) {
|
|
|
|
curLineStart = nextLineStart;
|
|
|
|
nextLineStart = lineEnd(curLineStart) + 1;
|
|
|
|
}
|
|
|
|
curIndent = indentationAfter(curLineStart);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function readToken() {
|
|
|
|
for (;;) {
|
|
|
|
try {
|
|
|
|
return fetchToken();
|
|
|
|
} catch(e) {
|
|
|
|
if (!(e instanceof SyntaxError)) throw e;
|
|
|
|
|
|
|
|
// Try to skip some text, based on the error message, and then continue
|
|
|
|
var msg = e.message, pos = e.raisedAt, replace = true;
|
|
|
|
if (/unterminated/i.test(msg)) {
|
|
|
|
pos = lineEnd(e.pos);
|
|
|
|
if (/string/.test(msg)) {
|
|
|
|
replace = {start: e.pos, end: pos, type: tt.string, value: input.slice(e.pos + 1, pos)};
|
|
|
|
} else if (/regular expr/i.test(msg)) {
|
|
|
|
var re = input.slice(e.pos, pos);
|
|
|
|
try { re = new RegExp(re); } catch(e) {}
|
|
|
|
replace = {start: e.pos, end: pos, type: tt.regexp, value: re};
|
|
|
|
} else {
|
|
|
|
replace = false;
|
|
|
|
}
|
|
|
|
} else if (/invalid (unicode|regexp|number)|expecting unicode|octal literal|is reserved|directly after number/i.test(msg)) {
|
|
|
|
while (pos < input.length && !isSpace(input.charCodeAt(pos))) ++pos;
|
|
|
|
} else if (/character escape|expected hexadecimal/i.test(msg)) {
|
|
|
|
while (pos < input.length) {
|
|
|
|
var ch = input.charCodeAt(pos++);
|
|
|
|
if (ch === 34 || ch === 39 || isNewline(ch)) break;
|
|
|
|
}
|
|
|
|
} else if (/unexpected character/i.test(msg)) {
|
|
|
|
pos++;
|
|
|
|
replace = false;
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
resetTo(pos);
|
|
|
|
if (replace === true) replace = {start: pos, end: pos, type: tt.name, value: "✖"};
|
|
|
|
if (replace) {
|
|
|
|
if (options.locations) {
|
|
|
|
replace.startLoc = acorn.getLineInfo(input, replace.start);
|
|
|
|
replace.endLoc = acorn.getLineInfo(input, replace.end);
|
|
|
|
}
|
|
|
|
return replace;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function resetTo(pos) {
|
|
|
|
var ch = input.charAt(pos - 1);
|
|
|
|
var reAllowed = !ch || /[\[\{\(,;:?\/*=+\-~!|&%^<>]/.test(ch) ||
|
|
|
|
/[enwfd]/.test(ch) && /\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(input.slice(pos - 10, pos));
|
|
|
|
fetchToken.jumpTo(pos, reAllowed);
|
|
|
|
}
|
|
|
|
|
|
|
|
function copyToken(token) {
|
|
|
|
var copy = {start: token.start, end: token.end, type: token.type, value: token.value};
|
|
|
|
if (options.locations) {
|
|
|
|
copy.startLoc = token.startLoc;
|
|
|
|
copy.endLoc = token.endLoc;
|
|
|
|
}
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
function lookAhead(n) {
|
|
|
|
// Copy token objects, because fetchToken will overwrite the one
|
|
|
|
// it returns, and in this case we still need it
|
|
|
|
if (!ahead.length)
|
|
|
|
token = copyToken(token);
|
|
|
|
while (n > ahead.length)
|
|
|
|
ahead.push(copyToken(readToken()));
|
|
|
|
return ahead[n-1];
|
|
|
|
}
|
|
|
|
|
|
|
|
var newline = /[\n\r\u2028\u2029]/;
|
|
|
|
|
|
|
|
function isNewline(ch) {
|
|
|
|
return ch === 10 || ch === 13 || ch === 8232 || ch === 8329;
|
|
|
|
}
|
|
|
|
function isSpace(ch) {
|
|
|
|
return (ch < 14 && ch > 8) || ch === 32 || ch === 160 || isNewline(ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
function pushCx() {
|
|
|
|
context.push(curIndent);
|
|
|
|
}
|
|
|
|
function popCx() {
|
|
|
|
curIndent = context.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
function lineEnd(pos) {
|
|
|
|
while (pos < input.length && !isNewline(input.charCodeAt(pos))) ++pos;
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
function indentationAfter(pos) {
|
|
|
|
for (var count = 0;; ++pos) {
|
|
|
|
var ch = input.charCodeAt(pos);
|
|
|
|
if (ch === 32) ++count;
|
|
|
|
else if (ch === 9) count += options.tabSize;
|
|
|
|
else return count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
function closes(closeTok, indent, line, blockHeuristic) {
|
2013-10-31 15:48:26 -07:00
|
|
|
if (token.type === closeTok || token.type === tt.eof) return true;
|
|
|
|
if (line != curLineStart && curIndent < indent && tokenStartsLine() &&
|
2014-02-04 17:52:38 -08:00
|
|
|
(!blockHeuristic || nextLineStart >= input.length ||
|
2013-10-31 15:48:26 -07:00
|
|
|
indentationAfter(nextLineStart) < indent)) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function tokenStartsLine() {
|
2014-02-04 17:52:38 -08:00
|
|
|
for (var p = token.start - 1; p >= curLineStart; --p) {
|
2013-10-31 15:48:26 -07:00
|
|
|
var ch = input.charCodeAt(p);
|
|
|
|
if (ch !== 9 && ch !== 32) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function node_t(start) {
|
|
|
|
this.type = null;
|
|
|
|
this.start = start;
|
|
|
|
this.end = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function node_loc_t(start) {
|
|
|
|
this.start = start || token.startLoc || {line: 1, column: 0};
|
|
|
|
this.end = null;
|
|
|
|
if (sourceFile !== null) this.source = sourceFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
function startNode() {
|
|
|
|
var node = new node_t(token.start);
|
|
|
|
if (options.locations)
|
|
|
|
node.loc = new node_loc_t();
|
2014-02-04 17:52:38 -08:00
|
|
|
if (options.directSourceFile)
|
|
|
|
node.sourceFile = options.directSourceFile;
|
|
|
|
return node;
|
2013-10-31 15:48:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function startNodeFrom(other) {
|
|
|
|
var node = new node_t(other.start);
|
|
|
|
if (options.locations)
|
|
|
|
node.loc = new node_loc_t(other.loc.start);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
function finishNode(node, type) {
|
|
|
|
node.type = type;
|
|
|
|
node.end = lastEnd;
|
|
|
|
if (options.locations)
|
|
|
|
node.loc.end = lastEndLoc;
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDummyLoc() {
|
|
|
|
if (options.locations) {
|
|
|
|
var loc = new node_loc_t();
|
|
|
|
loc.end = loc.start;
|
|
|
|
return loc;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function dummyIdent() {
|
|
|
|
var dummy = new node_t(token.start);
|
|
|
|
dummy.type = "Identifier";
|
|
|
|
dummy.end = token.start;
|
|
|
|
dummy.name = "✖";
|
|
|
|
dummy.loc = getDummyLoc();
|
|
|
|
return dummy;
|
|
|
|
}
|
|
|
|
function isDummy(node) { return node.name == "✖"; }
|
|
|
|
|
|
|
|
function eat(type) {
|
|
|
|
if (token.type === type) {
|
|
|
|
next();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function canInsertSemicolon() {
|
|
|
|
return (token.type === tt.eof || token.type === tt.braceR || newline.test(input.slice(lastEnd, token.start)));
|
|
|
|
}
|
|
|
|
function semicolon() {
|
|
|
|
eat(tt.semi);
|
|
|
|
}
|
|
|
|
|
|
|
|
function expect(type) {
|
|
|
|
if (eat(type)) return true;
|
|
|
|
if (lookAhead(1).type == type) {
|
|
|
|
next(); next();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (lookAhead(2).type == type) {
|
|
|
|
next(); next(); next();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkLVal(expr) {
|
|
|
|
if (expr.type === "Identifier" || expr.type === "MemberExpression") return expr;
|
|
|
|
return dummyIdent();
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseTopLevel() {
|
|
|
|
var node = startNode();
|
|
|
|
node.body = [];
|
|
|
|
while (token.type !== tt.eof) node.body.push(parseStatement());
|
|
|
|
return finishNode(node, "Program");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseStatement() {
|
|
|
|
var starttype = token.type, node = startNode();
|
|
|
|
|
|
|
|
switch (starttype) {
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._break: case tt._continue:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
2014-02-04 17:52:38 -08:00
|
|
|
var isBreak = starttype === tt._break;
|
2013-10-31 15:48:26 -07:00
|
|
|
node.label = token.type === tt.name ? parseIdent() : null;
|
|
|
|
semicolon();
|
|
|
|
return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._debugger:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
semicolon();
|
|
|
|
return finishNode(node, "DebuggerStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._do:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
node.body = parseStatement();
|
2014-02-04 17:52:38 -08:00
|
|
|
node.test = eat(tt._while) ? parseParenExpression() : dummyIdent();
|
2013-10-31 15:48:26 -07:00
|
|
|
semicolon();
|
|
|
|
return finishNode(node, "DoWhileStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._for:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
pushCx();
|
|
|
|
expect(tt.parenL);
|
|
|
|
if (token.type === tt.semi) return parseFor(node, null);
|
2014-02-04 17:52:38 -08:00
|
|
|
if (token.type === tt._var) {
|
2013-10-31 15:48:26 -07:00
|
|
|
var init = startNode();
|
|
|
|
next();
|
|
|
|
parseVar(init, true);
|
2014-02-04 17:52:38 -08:00
|
|
|
if (init.declarations.length === 1 && eat(tt._in))
|
2013-10-31 15:48:26 -07:00
|
|
|
return parseForIn(node, init);
|
|
|
|
return parseFor(node, init);
|
|
|
|
}
|
|
|
|
var init = parseExpression(false, true);
|
2014-02-04 17:52:38 -08:00
|
|
|
if (eat(tt._in)) {return parseForIn(node, checkLVal(init));}
|
2013-10-31 15:48:26 -07:00
|
|
|
return parseFor(node, init);
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._function:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
return parseFunction(node, true);
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._if:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
node.test = parseParenExpression();
|
|
|
|
node.consequent = parseStatement();
|
2014-02-04 17:52:38 -08:00
|
|
|
node.alternate = eat(tt._else) ? parseStatement() : null;
|
2013-10-31 15:48:26 -07:00
|
|
|
return finishNode(node, "IfStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._return:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
if (eat(tt.semi) || canInsertSemicolon()) node.argument = null;
|
|
|
|
else { node.argument = parseExpression(); semicolon(); }
|
|
|
|
return finishNode(node, "ReturnStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._switch:
|
2013-10-31 15:48:26 -07:00
|
|
|
var blockIndent = curIndent, line = curLineStart;
|
|
|
|
next();
|
|
|
|
node.discriminant = parseParenExpression();
|
|
|
|
node.cases = [];
|
|
|
|
pushCx();
|
|
|
|
expect(tt.braceL);
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
for (var cur; !closes(tt.braceR, blockIndent, line, true);) {
|
|
|
|
if (token.type === tt._case || token.type === tt._default) {
|
|
|
|
var isCase = token.type === tt._case;
|
2013-10-31 15:48:26 -07:00
|
|
|
if (cur) finishNode(cur, "SwitchCase");
|
|
|
|
node.cases.push(cur = startNode());
|
|
|
|
cur.consequent = [];
|
|
|
|
next();
|
|
|
|
if (isCase) cur.test = parseExpression();
|
|
|
|
else cur.test = null;
|
|
|
|
expect(tt.colon);
|
|
|
|
} else {
|
|
|
|
if (!cur) {
|
|
|
|
node.cases.push(cur = startNode());
|
|
|
|
cur.consequent = [];
|
|
|
|
cur.test = null;
|
|
|
|
}
|
|
|
|
cur.consequent.push(parseStatement());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cur) finishNode(cur, "SwitchCase");
|
|
|
|
popCx();
|
|
|
|
eat(tt.braceR);
|
|
|
|
return finishNode(node, "SwitchStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._throw:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
node.argument = parseExpression();
|
|
|
|
semicolon();
|
|
|
|
return finishNode(node, "ThrowStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._try:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
node.block = parseBlock();
|
|
|
|
node.handler = null;
|
2014-02-04 17:52:38 -08:00
|
|
|
if (token.type === tt._catch) {
|
2013-10-31 15:48:26 -07:00
|
|
|
var clause = startNode();
|
|
|
|
next();
|
|
|
|
expect(tt.parenL);
|
|
|
|
clause.param = parseIdent();
|
|
|
|
expect(tt.parenR);
|
|
|
|
clause.guard = null;
|
|
|
|
clause.body = parseBlock();
|
|
|
|
node.handler = finishNode(clause, "CatchClause");
|
|
|
|
}
|
2014-02-04 17:52:38 -08:00
|
|
|
node.finalizer = eat(tt._finally) ? parseBlock() : null;
|
2013-10-31 15:48:26 -07:00
|
|
|
if (!node.handler && !node.finalizer) return node.block;
|
|
|
|
return finishNode(node, "TryStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._var:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
node = parseVar(node);
|
|
|
|
semicolon();
|
|
|
|
return node;
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._while:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
node.test = parseParenExpression();
|
|
|
|
node.body = parseStatement();
|
|
|
|
return finishNode(node, "WhileStatement");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._with:
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
node.object = parseParenExpression();
|
|
|
|
node.body = parseStatement();
|
|
|
|
return finishNode(node, "WithStatement");
|
|
|
|
|
|
|
|
case tt.braceL:
|
|
|
|
return parseBlock();
|
|
|
|
|
|
|
|
case tt.semi:
|
|
|
|
next();
|
|
|
|
return finishNode(node, "EmptyStatement");
|
|
|
|
|
|
|
|
default:
|
2014-02-04 17:52:38 -08:00
|
|
|
var expr = parseExpression();
|
2013-10-31 15:48:26 -07:00
|
|
|
if (isDummy(expr)) {
|
|
|
|
next();
|
|
|
|
if (token.type === tt.eof) return finishNode(node, "EmptyStatement");
|
|
|
|
return parseStatement();
|
|
|
|
} else if (starttype === tt.name && expr.type === "Identifier" && eat(tt.colon)) {
|
|
|
|
node.body = parseStatement();
|
|
|
|
node.label = expr;
|
|
|
|
return finishNode(node, "LabeledStatement");
|
|
|
|
} else {
|
|
|
|
node.expression = expr;
|
|
|
|
semicolon();
|
|
|
|
return finishNode(node, "ExpressionStatement");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseBlock() {
|
|
|
|
var node = startNode();
|
|
|
|
pushCx();
|
|
|
|
expect(tt.braceL);
|
|
|
|
var blockIndent = curIndent, line = curLineStart;
|
|
|
|
node.body = [];
|
2014-02-04 17:52:38 -08:00
|
|
|
while (!closes(tt.braceR, blockIndent, line, true))
|
2013-10-31 15:48:26 -07:00
|
|
|
node.body.push(parseStatement());
|
|
|
|
popCx();
|
|
|
|
eat(tt.braceR);
|
|
|
|
return finishNode(node, "BlockStatement");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseFor(node, init) {
|
|
|
|
node.init = init;
|
|
|
|
node.test = node.update = null;
|
|
|
|
if (eat(tt.semi) && token.type !== tt.semi) node.test = parseExpression();
|
|
|
|
if (eat(tt.semi) && token.type !== tt.parenR) node.update = parseExpression();
|
|
|
|
popCx();
|
|
|
|
expect(tt.parenR);
|
|
|
|
node.body = parseStatement();
|
|
|
|
return finishNode(node, "ForStatement");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseForIn(node, init) {
|
|
|
|
node.left = init;
|
|
|
|
node.right = parseExpression();
|
|
|
|
popCx();
|
|
|
|
expect(tt.parenR);
|
|
|
|
node.body = parseStatement();
|
|
|
|
return finishNode(node, "ForInStatement");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseVar(node, noIn) {
|
|
|
|
node.declarations = [];
|
|
|
|
node.kind = "var";
|
|
|
|
while (token.type === tt.name) {
|
|
|
|
var decl = startNode();
|
|
|
|
decl.id = parseIdent();
|
|
|
|
decl.init = eat(tt.eq) ? parseExpression(true, noIn) : null;
|
|
|
|
node.declarations.push(finishNode(decl, "VariableDeclarator"));
|
|
|
|
if (!eat(tt.comma)) break;
|
|
|
|
}
|
2014-02-04 17:52:38 -08:00
|
|
|
if (!node.declarations.length) {
|
|
|
|
var decl = startNode();
|
|
|
|
decl.id = dummyIdent();
|
|
|
|
node.declarations.push(finishNode(decl, "VariableDeclarator"));
|
|
|
|
}
|
2013-10-31 15:48:26 -07:00
|
|
|
return finishNode(node, "VariableDeclaration");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseExpression(noComma, noIn) {
|
|
|
|
var expr = parseMaybeAssign(noIn);
|
|
|
|
if (!noComma && token.type === tt.comma) {
|
|
|
|
var node = startNodeFrom(expr);
|
|
|
|
node.expressions = [expr];
|
|
|
|
while (eat(tt.comma)) node.expressions.push(parseMaybeAssign(noIn));
|
|
|
|
return finishNode(node, "SequenceExpression");
|
|
|
|
}
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseParenExpression() {
|
|
|
|
pushCx();
|
|
|
|
expect(tt.parenL);
|
|
|
|
var val = parseExpression();
|
|
|
|
popCx();
|
|
|
|
expect(tt.parenR);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseMaybeAssign(noIn) {
|
|
|
|
var left = parseMaybeConditional(noIn);
|
|
|
|
if (token.type.isAssign) {
|
|
|
|
var node = startNodeFrom(left);
|
|
|
|
node.operator = token.value;
|
|
|
|
node.left = checkLVal(left);
|
|
|
|
next();
|
|
|
|
node.right = parseMaybeAssign(noIn);
|
|
|
|
return finishNode(node, "AssignmentExpression");
|
|
|
|
}
|
|
|
|
return left;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseMaybeConditional(noIn) {
|
|
|
|
var expr = parseExprOps(noIn);
|
|
|
|
if (eat(tt.question)) {
|
|
|
|
var node = startNodeFrom(expr);
|
|
|
|
node.test = expr;
|
|
|
|
node.consequent = parseExpression(true);
|
|
|
|
node.alternate = expect(tt.colon) ? parseExpression(true, noIn) : dummyIdent();
|
|
|
|
return finishNode(node, "ConditionalExpression");
|
|
|
|
}
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseExprOps(noIn) {
|
|
|
|
var indent = curIndent, line = curLineStart;
|
|
|
|
return parseExprOp(parseMaybeUnary(noIn), -1, noIn, indent, line);
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseExprOp(left, minPrec, noIn, indent, line) {
|
|
|
|
if (curLineStart != line && curIndent < indent && tokenStartsLine()) return left;
|
|
|
|
var prec = token.type.binop;
|
2014-02-04 17:52:38 -08:00
|
|
|
if (prec != null && (!noIn || token.type !== tt._in)) {
|
2013-10-31 15:48:26 -07:00
|
|
|
if (prec > minPrec) {
|
|
|
|
var node = startNodeFrom(left);
|
|
|
|
node.left = left;
|
|
|
|
node.operator = token.value;
|
|
|
|
next();
|
|
|
|
if (curLineStart != line && curIndent < indent && tokenStartsLine())
|
|
|
|
node.right = dummyIdent();
|
|
|
|
else
|
|
|
|
node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn, indent, line);
|
|
|
|
var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
|
|
|
|
return parseExprOp(node, minPrec, noIn, indent, line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return left;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseMaybeUnary(noIn) {
|
|
|
|
if (token.type.prefix) {
|
|
|
|
var node = startNode(), update = token.type.isUpdate;
|
|
|
|
node.operator = token.value;
|
|
|
|
node.prefix = true;
|
|
|
|
next();
|
|
|
|
node.argument = parseMaybeUnary(noIn);
|
|
|
|
if (update) node.argument = checkLVal(node.argument);
|
|
|
|
return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
|
|
|
|
}
|
|
|
|
var expr = parseExprSubscripts();
|
|
|
|
while (token.type.postfix && !canInsertSemicolon()) {
|
|
|
|
var node = startNodeFrom(expr);
|
|
|
|
node.operator = token.value;
|
|
|
|
node.prefix = false;
|
|
|
|
node.argument = checkLVal(expr);
|
|
|
|
next();
|
|
|
|
expr = finishNode(node, "UpdateExpression");
|
|
|
|
}
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseExprSubscripts() {
|
2014-02-04 17:52:38 -08:00
|
|
|
return parseSubscripts(parseExprAtom(), false, curIndent, curLineStart);
|
2013-10-31 15:48:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function parseSubscripts(base, noCalls, startIndent, line) {
|
|
|
|
for (;;) {
|
|
|
|
if (curLineStart != line && curIndent <= startIndent && tokenStartsLine()) {
|
|
|
|
if (token.type == tt.dot && curIndent == startIndent)
|
|
|
|
--startIndent;
|
|
|
|
else
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (eat(tt.dot)) {
|
|
|
|
var node = startNodeFrom(base);
|
|
|
|
node.object = base;
|
|
|
|
if (curLineStart != line && curIndent <= startIndent && tokenStartsLine())
|
|
|
|
node.property = dummyIdent();
|
|
|
|
else
|
|
|
|
node.property = parsePropertyName() || dummyIdent();
|
|
|
|
node.computed = false;
|
|
|
|
base = finishNode(node, "MemberExpression");
|
|
|
|
} else if (token.type == tt.bracketL) {
|
|
|
|
pushCx();
|
|
|
|
next();
|
|
|
|
var node = startNodeFrom(base);
|
|
|
|
node.object = base;
|
|
|
|
node.property = parseExpression();
|
|
|
|
node.computed = true;
|
|
|
|
popCx();
|
|
|
|
expect(tt.bracketR);
|
|
|
|
base = finishNode(node, "MemberExpression");
|
|
|
|
} else if (!noCalls && token.type == tt.parenL) {
|
|
|
|
pushCx();
|
|
|
|
var node = startNodeFrom(base);
|
|
|
|
node.callee = base;
|
|
|
|
node.arguments = parseExprList(tt.parenR);
|
|
|
|
base = finishNode(node, "CallExpression");
|
|
|
|
} else {
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseExprAtom() {
|
|
|
|
switch (token.type) {
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._this:
|
2013-10-31 15:48:26 -07:00
|
|
|
var node = startNode();
|
|
|
|
next();
|
|
|
|
return finishNode(node, "ThisExpression");
|
|
|
|
case tt.name:
|
|
|
|
return parseIdent();
|
|
|
|
case tt.num: case tt.string: case tt.regexp:
|
|
|
|
var node = startNode();
|
|
|
|
node.value = token.value;
|
|
|
|
node.raw = input.slice(token.start, token.end);
|
|
|
|
next();
|
|
|
|
return finishNode(node, "Literal");
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._null: case tt._true: case tt._false:
|
2013-10-31 15:48:26 -07:00
|
|
|
var node = startNode();
|
|
|
|
node.value = token.type.atomValue;
|
2014-02-04 17:52:38 -08:00
|
|
|
node.raw = token.type.keyword;
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
return finishNode(node, "Literal");
|
|
|
|
|
|
|
|
case tt.parenL:
|
|
|
|
var tokStart1 = token.start;
|
|
|
|
next();
|
|
|
|
var val = parseExpression();
|
|
|
|
val.start = tokStart1;
|
|
|
|
val.end = token.end;
|
|
|
|
expect(tt.parenR);
|
|
|
|
return val;
|
|
|
|
|
|
|
|
case tt.bracketL:
|
|
|
|
var node = startNode();
|
|
|
|
pushCx();
|
|
|
|
node.elements = parseExprList(tt.bracketR);
|
|
|
|
return finishNode(node, "ArrayExpression");
|
|
|
|
|
|
|
|
case tt.braceL:
|
|
|
|
return parseObj();
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._function:
|
2013-10-31 15:48:26 -07:00
|
|
|
var node = startNode();
|
|
|
|
next();
|
|
|
|
return parseFunction(node, false);
|
|
|
|
|
2014-02-04 17:52:38 -08:00
|
|
|
case tt._new:
|
2013-10-31 15:48:26 -07:00
|
|
|
return parseNew();
|
|
|
|
|
|
|
|
default:
|
|
|
|
return dummyIdent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseNew() {
|
|
|
|
var node = startNode(), startIndent = curIndent, line = curLineStart;
|
|
|
|
next();
|
|
|
|
node.callee = parseSubscripts(parseExprAtom(), true, startIndent, line);
|
|
|
|
if (token.type == tt.parenL) {
|
|
|
|
pushCx();
|
|
|
|
node.arguments = parseExprList(tt.parenR);
|
|
|
|
} else {
|
|
|
|
node.arguments = [];
|
|
|
|
}
|
|
|
|
return finishNode(node, "NewExpression");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseObj() {
|
|
|
|
var node = startNode();
|
|
|
|
node.properties = [];
|
|
|
|
pushCx();
|
|
|
|
next();
|
|
|
|
var propIndent = curIndent, line = curLineStart;
|
2014-02-04 17:52:38 -08:00
|
|
|
while (!closes(tt.braceR, propIndent, line)) {
|
2013-10-31 15:48:26 -07:00
|
|
|
var name = parsePropertyName();
|
|
|
|
if (!name) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; }
|
|
|
|
var prop = {key: name}, isGetSet = false, kind;
|
|
|
|
if (eat(tt.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() || dummyIdent();
|
|
|
|
prop.value = parseFunction(startNode(), false);
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
eat(tt.comma);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
node.properties.push(prop);
|
|
|
|
eat(tt.comma);
|
|
|
|
}
|
|
|
|
popCx();
|
|
|
|
eat(tt.braceR);
|
|
|
|
return finishNode(node, "ObjectExpression");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parsePropertyName() {
|
|
|
|
if (token.type === tt.num || token.type === tt.string) return parseExprAtom();
|
|
|
|
if (token.type === tt.name || token.type.keyword) return parseIdent();
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseIdent() {
|
|
|
|
var node = startNode();
|
|
|
|
node.name = token.type === tt.name ? token.value : token.type.keyword;
|
|
|
|
next();
|
|
|
|
return finishNode(node, "Identifier");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseFunction(node, isStatement) {
|
|
|
|
if (token.type === tt.name) node.id = parseIdent();
|
|
|
|
else if (isStatement) node.id = dummyIdent();
|
|
|
|
else node.id = null;
|
|
|
|
node.params = [];
|
|
|
|
pushCx();
|
|
|
|
expect(tt.parenL);
|
|
|
|
while (token.type == tt.name) {
|
|
|
|
node.params.push(parseIdent());
|
|
|
|
eat(tt.comma);
|
|
|
|
}
|
|
|
|
popCx();
|
|
|
|
eat(tt.parenR);
|
|
|
|
node.body = parseBlock();
|
|
|
|
return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseExprList(close) {
|
2014-02-04 17:52:38 -08:00
|
|
|
var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart;
|
2013-10-31 15:48:26 -07:00
|
|
|
next(); // Opening bracket
|
2014-02-04 17:52:38 -08:00
|
|
|
if (curLineStart > continuedLine) continuedLine = curLineStart;
|
|
|
|
while (!closes(close, indent + (curLineStart <= continuedLine ? 1 : 0), line)) {
|
2013-10-31 15:48:26 -07:00
|
|
|
var elt = parseExpression(true);
|
|
|
|
if (isDummy(elt)) {
|
2014-02-04 17:52:38 -08:00
|
|
|
if (closes(close, indent, line)) break;
|
2013-10-31 15:48:26 -07:00
|
|
|
next();
|
|
|
|
} else {
|
|
|
|
elts.push(elt);
|
|
|
|
}
|
|
|
|
while (eat(tt.comma)) {}
|
|
|
|
}
|
|
|
|
popCx();
|
|
|
|
eat(close);
|
|
|
|
return elts;
|
|
|
|
}
|
|
|
|
});
|