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