diff -r 000000000000 -r 6474c204b198 toolkit/devtools/acorn/acorn_loose.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toolkit/devtools/acorn/acorn_loose.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,777 @@ +// 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. + +(function(root, mod) { + 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 + mod(root.acorn || (root.acorn = {}), root.acorn); // Plain browser env +})(this, function(exports, acorn) { + "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; + } + } + + function closes(closeTok, indent, line, blockHeuristic) { + if (token.type === closeTok || token.type === tt.eof) return true; + if (line != curLineStart && curIndent < indent && tokenStartsLine() && + (!blockHeuristic || nextLineStart >= input.length || + indentationAfter(nextLineStart) < indent)) return true; + return false; + } + + function tokenStartsLine() { + for (var p = token.start - 1; p >= curLineStart; --p) { + 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(); + if (options.directSourceFile) + node.sourceFile = options.directSourceFile; + return node; + } + + 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) { + case tt._break: case tt._continue: + next(); + var isBreak = starttype === tt._break; + node.label = token.type === tt.name ? parseIdent() : null; + semicolon(); + return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); + + case tt._debugger: + next(); + semicolon(); + return finishNode(node, "DebuggerStatement"); + + case tt._do: + next(); + node.body = parseStatement(); + node.test = eat(tt._while) ? parseParenExpression() : dummyIdent(); + semicolon(); + return finishNode(node, "DoWhileStatement"); + + case tt._for: + next(); + pushCx(); + expect(tt.parenL); + if (token.type === tt.semi) return parseFor(node, null); + if (token.type === tt._var) { + var init = startNode(); + next(); + parseVar(init, true); + if (init.declarations.length === 1 && eat(tt._in)) + return parseForIn(node, init); + return parseFor(node, init); + } + var init = parseExpression(false, true); + if (eat(tt._in)) {return parseForIn(node, checkLVal(init));} + return parseFor(node, init); + + case tt._function: + next(); + return parseFunction(node, true); + + case tt._if: + next(); + node.test = parseParenExpression(); + node.consequent = parseStatement(); + node.alternate = eat(tt._else) ? parseStatement() : null; + return finishNode(node, "IfStatement"); + + case tt._return: + next(); + if (eat(tt.semi) || canInsertSemicolon()) node.argument = null; + else { node.argument = parseExpression(); semicolon(); } + return finishNode(node, "ReturnStatement"); + + case tt._switch: + var blockIndent = curIndent, line = curLineStart; + next(); + node.discriminant = parseParenExpression(); + node.cases = []; + pushCx(); + expect(tt.braceL); + + for (var cur; !closes(tt.braceR, blockIndent, line, true);) { + if (token.type === tt._case || token.type === tt._default) { + var isCase = token.type === tt._case; + 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"); + + case tt._throw: + next(); + node.argument = parseExpression(); + semicolon(); + return finishNode(node, "ThrowStatement"); + + case tt._try: + next(); + node.block = parseBlock(); + node.handler = null; + if (token.type === tt._catch) { + var clause = startNode(); + next(); + expect(tt.parenL); + clause.param = parseIdent(); + expect(tt.parenR); + clause.guard = null; + clause.body = parseBlock(); + node.handler = finishNode(clause, "CatchClause"); + } + node.finalizer = eat(tt._finally) ? parseBlock() : null; + if (!node.handler && !node.finalizer) return node.block; + return finishNode(node, "TryStatement"); + + case tt._var: + next(); + node = parseVar(node); + semicolon(); + return node; + + case tt._while: + next(); + node.test = parseParenExpression(); + node.body = parseStatement(); + return finishNode(node, "WhileStatement"); + + case tt._with: + 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: + var expr = parseExpression(); + 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 = []; + while (!closes(tt.braceR, blockIndent, line, true)) + 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; + } + if (!node.declarations.length) { + var decl = startNode(); + decl.id = dummyIdent(); + node.declarations.push(finishNode(decl, "VariableDeclarator")); + } + 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; + if (prec != null && (!noIn || token.type !== tt._in)) { + 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() { + return parseSubscripts(parseExprAtom(), false, curIndent, curLineStart); + } + + 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) { + case tt._this: + 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"); + + case tt._null: case tt._true: case tt._false: + var node = startNode(); + node.value = token.type.atomValue; + node.raw = token.type.keyword; + 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(); + + case tt._function: + var node = startNode(); + next(); + return parseFunction(node, false); + + case tt._new: + 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; + while (!closes(tt.braceR, propIndent, line)) { + 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) { + var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart; + next(); // Opening bracket + if (curLineStart > continuedLine) continuedLine = curLineStart; + while (!closes(close, indent + (curLineStart <= continuedLine ? 1 : 0), line)) { + var elt = parseExpression(true); + if (isDummy(elt)) { + if (closes(close, indent, line)) break; + next(); + } else { + elts.push(elt); + } + while (eat(tt.comma)) {} + } + popCx(); + eat(close); + return elts; + } +});