toolkit/devtools/acorn/acorn_loose.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 // Acorn: Loose parser
michael@0 2 //
michael@0 3 // This module provides an alternative parser (`parse_dammit`) that
michael@0 4 // exposes that same interface as `parse`, but will try to parse
michael@0 5 // anything as JavaScript, repairing syntax error the best it can.
michael@0 6 // There are circumstances in which it will raise an error and give
michael@0 7 // up, but they are very rare. The resulting AST will be a mostly
michael@0 8 // valid JavaScript AST (as per the [Mozilla parser API][api], except
michael@0 9 // that:
michael@0 10 //
michael@0 11 // - Return outside functions is allowed
michael@0 12 //
michael@0 13 // - Label consistency (no conflicts, break only to existing labels)
michael@0 14 // is not enforced.
michael@0 15 //
michael@0 16 // - Bogus Identifier nodes with a name of `"✖"` are inserted whenever
michael@0 17 // the parser got too confused to return anything meaningful.
michael@0 18 //
michael@0 19 // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
michael@0 20 //
michael@0 21 // The expected use for this is to *first* try `acorn.parse`, and only
michael@0 22 // if that fails switch to `parse_dammit`. The loose parser might
michael@0 23 // parse badly indented code incorrectly, so **don't** use it as
michael@0 24 // your default parser.
michael@0 25 //
michael@0 26 // Quite a lot of acorn.js is duplicated here. The alternative was to
michael@0 27 // add a *lot* of extra cruft to that file, making it less readable
michael@0 28 // and slower. Copying and editing the code allowed me to make
michael@0 29 // invasive changes and simplifications without creating a complicated
michael@0 30 // tangle.
michael@0 31
michael@0 32 (function(root, mod) {
michael@0 33 if (typeof exports == "object" && typeof module == "object") return mod(exports, require("./acorn")); // CommonJS
michael@0 34 if (typeof define == "function" && define.amd) return define(["exports", "./acorn"], mod); // AMD
michael@0 35 mod(root.acorn || (root.acorn = {}), root.acorn); // Plain browser env
michael@0 36 })(this, function(exports, acorn) {
michael@0 37 "use strict";
michael@0 38
michael@0 39 var tt = acorn.tokTypes;
michael@0 40
michael@0 41 var options, input, fetchToken, context;
michael@0 42
michael@0 43 exports.parse_dammit = function(inpt, opts) {
michael@0 44 if (!opts) opts = {};
michael@0 45 input = String(inpt);
michael@0 46 options = opts;
michael@0 47 if (!opts.tabSize) opts.tabSize = 4;
michael@0 48 fetchToken = acorn.tokenize(inpt, opts);
michael@0 49 sourceFile = options.sourceFile || null;
michael@0 50 context = [];
michael@0 51 nextLineStart = 0;
michael@0 52 ahead.length = 0;
michael@0 53 next();
michael@0 54 return parseTopLevel();
michael@0 55 };
michael@0 56
michael@0 57 var lastEnd, token = {start: 0, end: 0}, ahead = [];
michael@0 58 var curLineStart, nextLineStart, curIndent, lastEndLoc, sourceFile;
michael@0 59
michael@0 60 function next() {
michael@0 61 lastEnd = token.end;
michael@0 62 if (options.locations)
michael@0 63 lastEndLoc = token.endLoc;
michael@0 64
michael@0 65 if (ahead.length)
michael@0 66 token = ahead.shift();
michael@0 67 else
michael@0 68 token = readToken();
michael@0 69
michael@0 70 if (token.start >= nextLineStart) {
michael@0 71 while (token.start >= nextLineStart) {
michael@0 72 curLineStart = nextLineStart;
michael@0 73 nextLineStart = lineEnd(curLineStart) + 1;
michael@0 74 }
michael@0 75 curIndent = indentationAfter(curLineStart);
michael@0 76 }
michael@0 77 }
michael@0 78
michael@0 79 function readToken() {
michael@0 80 for (;;) {
michael@0 81 try {
michael@0 82 return fetchToken();
michael@0 83 } catch(e) {
michael@0 84 if (!(e instanceof SyntaxError)) throw e;
michael@0 85
michael@0 86 // Try to skip some text, based on the error message, and then continue
michael@0 87 var msg = e.message, pos = e.raisedAt, replace = true;
michael@0 88 if (/unterminated/i.test(msg)) {
michael@0 89 pos = lineEnd(e.pos);
michael@0 90 if (/string/.test(msg)) {
michael@0 91 replace = {start: e.pos, end: pos, type: tt.string, value: input.slice(e.pos + 1, pos)};
michael@0 92 } else if (/regular expr/i.test(msg)) {
michael@0 93 var re = input.slice(e.pos, pos);
michael@0 94 try { re = new RegExp(re); } catch(e) {}
michael@0 95 replace = {start: e.pos, end: pos, type: tt.regexp, value: re};
michael@0 96 } else {
michael@0 97 replace = false;
michael@0 98 }
michael@0 99 } else if (/invalid (unicode|regexp|number)|expecting unicode|octal literal|is reserved|directly after number/i.test(msg)) {
michael@0 100 while (pos < input.length && !isSpace(input.charCodeAt(pos))) ++pos;
michael@0 101 } else if (/character escape|expected hexadecimal/i.test(msg)) {
michael@0 102 while (pos < input.length) {
michael@0 103 var ch = input.charCodeAt(pos++);
michael@0 104 if (ch === 34 || ch === 39 || isNewline(ch)) break;
michael@0 105 }
michael@0 106 } else if (/unexpected character/i.test(msg)) {
michael@0 107 pos++;
michael@0 108 replace = false;
michael@0 109 } else {
michael@0 110 throw e;
michael@0 111 }
michael@0 112 resetTo(pos);
michael@0 113 if (replace === true) replace = {start: pos, end: pos, type: tt.name, value: "✖"};
michael@0 114 if (replace) {
michael@0 115 if (options.locations) {
michael@0 116 replace.startLoc = acorn.getLineInfo(input, replace.start);
michael@0 117 replace.endLoc = acorn.getLineInfo(input, replace.end);
michael@0 118 }
michael@0 119 return replace;
michael@0 120 }
michael@0 121 }
michael@0 122 }
michael@0 123 }
michael@0 124
michael@0 125 function resetTo(pos) {
michael@0 126 var ch = input.charAt(pos - 1);
michael@0 127 var reAllowed = !ch || /[\[\{\(,;:?\/*=+\-~!|&%^<>]/.test(ch) ||
michael@0 128 /[enwfd]/.test(ch) && /\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(input.slice(pos - 10, pos));
michael@0 129 fetchToken.jumpTo(pos, reAllowed);
michael@0 130 }
michael@0 131
michael@0 132 function copyToken(token) {
michael@0 133 var copy = {start: token.start, end: token.end, type: token.type, value: token.value};
michael@0 134 if (options.locations) {
michael@0 135 copy.startLoc = token.startLoc;
michael@0 136 copy.endLoc = token.endLoc;
michael@0 137 }
michael@0 138 return copy;
michael@0 139 }
michael@0 140
michael@0 141 function lookAhead(n) {
michael@0 142 // Copy token objects, because fetchToken will overwrite the one
michael@0 143 // it returns, and in this case we still need it
michael@0 144 if (!ahead.length)
michael@0 145 token = copyToken(token);
michael@0 146 while (n > ahead.length)
michael@0 147 ahead.push(copyToken(readToken()));
michael@0 148 return ahead[n-1];
michael@0 149 }
michael@0 150
michael@0 151 var newline = /[\n\r\u2028\u2029]/;
michael@0 152
michael@0 153 function isNewline(ch) {
michael@0 154 return ch === 10 || ch === 13 || ch === 8232 || ch === 8329;
michael@0 155 }
michael@0 156 function isSpace(ch) {
michael@0 157 return (ch < 14 && ch > 8) || ch === 32 || ch === 160 || isNewline(ch);
michael@0 158 }
michael@0 159
michael@0 160 function pushCx() {
michael@0 161 context.push(curIndent);
michael@0 162 }
michael@0 163 function popCx() {
michael@0 164 curIndent = context.pop();
michael@0 165 }
michael@0 166
michael@0 167 function lineEnd(pos) {
michael@0 168 while (pos < input.length && !isNewline(input.charCodeAt(pos))) ++pos;
michael@0 169 return pos;
michael@0 170 }
michael@0 171 function indentationAfter(pos) {
michael@0 172 for (var count = 0;; ++pos) {
michael@0 173 var ch = input.charCodeAt(pos);
michael@0 174 if (ch === 32) ++count;
michael@0 175 else if (ch === 9) count += options.tabSize;
michael@0 176 else return count;
michael@0 177 }
michael@0 178 }
michael@0 179
michael@0 180 function closes(closeTok, indent, line, blockHeuristic) {
michael@0 181 if (token.type === closeTok || token.type === tt.eof) return true;
michael@0 182 if (line != curLineStart && curIndent < indent && tokenStartsLine() &&
michael@0 183 (!blockHeuristic || nextLineStart >= input.length ||
michael@0 184 indentationAfter(nextLineStart) < indent)) return true;
michael@0 185 return false;
michael@0 186 }
michael@0 187
michael@0 188 function tokenStartsLine() {
michael@0 189 for (var p = token.start - 1; p >= curLineStart; --p) {
michael@0 190 var ch = input.charCodeAt(p);
michael@0 191 if (ch !== 9 && ch !== 32) return false;
michael@0 192 }
michael@0 193 return true;
michael@0 194 }
michael@0 195
michael@0 196 function node_t(start) {
michael@0 197 this.type = null;
michael@0 198 this.start = start;
michael@0 199 this.end = null;
michael@0 200 }
michael@0 201
michael@0 202 function node_loc_t(start) {
michael@0 203 this.start = start || token.startLoc || {line: 1, column: 0};
michael@0 204 this.end = null;
michael@0 205 if (sourceFile !== null) this.source = sourceFile;
michael@0 206 }
michael@0 207
michael@0 208 function startNode() {
michael@0 209 var node = new node_t(token.start);
michael@0 210 if (options.locations)
michael@0 211 node.loc = new node_loc_t();
michael@0 212 if (options.directSourceFile)
michael@0 213 node.sourceFile = options.directSourceFile;
michael@0 214 return node;
michael@0 215 }
michael@0 216
michael@0 217 function startNodeFrom(other) {
michael@0 218 var node = new node_t(other.start);
michael@0 219 if (options.locations)
michael@0 220 node.loc = new node_loc_t(other.loc.start);
michael@0 221 return node;
michael@0 222 }
michael@0 223
michael@0 224 function finishNode(node, type) {
michael@0 225 node.type = type;
michael@0 226 node.end = lastEnd;
michael@0 227 if (options.locations)
michael@0 228 node.loc.end = lastEndLoc;
michael@0 229 return node;
michael@0 230 }
michael@0 231
michael@0 232 function getDummyLoc() {
michael@0 233 if (options.locations) {
michael@0 234 var loc = new node_loc_t();
michael@0 235 loc.end = loc.start;
michael@0 236 return loc;
michael@0 237 }
michael@0 238 };
michael@0 239
michael@0 240 function dummyIdent() {
michael@0 241 var dummy = new node_t(token.start);
michael@0 242 dummy.type = "Identifier";
michael@0 243 dummy.end = token.start;
michael@0 244 dummy.name = "✖";
michael@0 245 dummy.loc = getDummyLoc();
michael@0 246 return dummy;
michael@0 247 }
michael@0 248 function isDummy(node) { return node.name == "✖"; }
michael@0 249
michael@0 250 function eat(type) {
michael@0 251 if (token.type === type) {
michael@0 252 next();
michael@0 253 return true;
michael@0 254 }
michael@0 255 }
michael@0 256
michael@0 257 function canInsertSemicolon() {
michael@0 258 return (token.type === tt.eof || token.type === tt.braceR || newline.test(input.slice(lastEnd, token.start)));
michael@0 259 }
michael@0 260 function semicolon() {
michael@0 261 eat(tt.semi);
michael@0 262 }
michael@0 263
michael@0 264 function expect(type) {
michael@0 265 if (eat(type)) return true;
michael@0 266 if (lookAhead(1).type == type) {
michael@0 267 next(); next();
michael@0 268 return true;
michael@0 269 }
michael@0 270 if (lookAhead(2).type == type) {
michael@0 271 next(); next(); next();
michael@0 272 return true;
michael@0 273 }
michael@0 274 }
michael@0 275
michael@0 276 function checkLVal(expr) {
michael@0 277 if (expr.type === "Identifier" || expr.type === "MemberExpression") return expr;
michael@0 278 return dummyIdent();
michael@0 279 }
michael@0 280
michael@0 281 function parseTopLevel() {
michael@0 282 var node = startNode();
michael@0 283 node.body = [];
michael@0 284 while (token.type !== tt.eof) node.body.push(parseStatement());
michael@0 285 return finishNode(node, "Program");
michael@0 286 }
michael@0 287
michael@0 288 function parseStatement() {
michael@0 289 var starttype = token.type, node = startNode();
michael@0 290
michael@0 291 switch (starttype) {
michael@0 292 case tt._break: case tt._continue:
michael@0 293 next();
michael@0 294 var isBreak = starttype === tt._break;
michael@0 295 node.label = token.type === tt.name ? parseIdent() : null;
michael@0 296 semicolon();
michael@0 297 return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
michael@0 298
michael@0 299 case tt._debugger:
michael@0 300 next();
michael@0 301 semicolon();
michael@0 302 return finishNode(node, "DebuggerStatement");
michael@0 303
michael@0 304 case tt._do:
michael@0 305 next();
michael@0 306 node.body = parseStatement();
michael@0 307 node.test = eat(tt._while) ? parseParenExpression() : dummyIdent();
michael@0 308 semicolon();
michael@0 309 return finishNode(node, "DoWhileStatement");
michael@0 310
michael@0 311 case tt._for:
michael@0 312 next();
michael@0 313 pushCx();
michael@0 314 expect(tt.parenL);
michael@0 315 if (token.type === tt.semi) return parseFor(node, null);
michael@0 316 if (token.type === tt._var) {
michael@0 317 var init = startNode();
michael@0 318 next();
michael@0 319 parseVar(init, true);
michael@0 320 if (init.declarations.length === 1 && eat(tt._in))
michael@0 321 return parseForIn(node, init);
michael@0 322 return parseFor(node, init);
michael@0 323 }
michael@0 324 var init = parseExpression(false, true);
michael@0 325 if (eat(tt._in)) {return parseForIn(node, checkLVal(init));}
michael@0 326 return parseFor(node, init);
michael@0 327
michael@0 328 case tt._function:
michael@0 329 next();
michael@0 330 return parseFunction(node, true);
michael@0 331
michael@0 332 case tt._if:
michael@0 333 next();
michael@0 334 node.test = parseParenExpression();
michael@0 335 node.consequent = parseStatement();
michael@0 336 node.alternate = eat(tt._else) ? parseStatement() : null;
michael@0 337 return finishNode(node, "IfStatement");
michael@0 338
michael@0 339 case tt._return:
michael@0 340 next();
michael@0 341 if (eat(tt.semi) || canInsertSemicolon()) node.argument = null;
michael@0 342 else { node.argument = parseExpression(); semicolon(); }
michael@0 343 return finishNode(node, "ReturnStatement");
michael@0 344
michael@0 345 case tt._switch:
michael@0 346 var blockIndent = curIndent, line = curLineStart;
michael@0 347 next();
michael@0 348 node.discriminant = parseParenExpression();
michael@0 349 node.cases = [];
michael@0 350 pushCx();
michael@0 351 expect(tt.braceL);
michael@0 352
michael@0 353 for (var cur; !closes(tt.braceR, blockIndent, line, true);) {
michael@0 354 if (token.type === tt._case || token.type === tt._default) {
michael@0 355 var isCase = token.type === tt._case;
michael@0 356 if (cur) finishNode(cur, "SwitchCase");
michael@0 357 node.cases.push(cur = startNode());
michael@0 358 cur.consequent = [];
michael@0 359 next();
michael@0 360 if (isCase) cur.test = parseExpression();
michael@0 361 else cur.test = null;
michael@0 362 expect(tt.colon);
michael@0 363 } else {
michael@0 364 if (!cur) {
michael@0 365 node.cases.push(cur = startNode());
michael@0 366 cur.consequent = [];
michael@0 367 cur.test = null;
michael@0 368 }
michael@0 369 cur.consequent.push(parseStatement());
michael@0 370 }
michael@0 371 }
michael@0 372 if (cur) finishNode(cur, "SwitchCase");
michael@0 373 popCx();
michael@0 374 eat(tt.braceR);
michael@0 375 return finishNode(node, "SwitchStatement");
michael@0 376
michael@0 377 case tt._throw:
michael@0 378 next();
michael@0 379 node.argument = parseExpression();
michael@0 380 semicolon();
michael@0 381 return finishNode(node, "ThrowStatement");
michael@0 382
michael@0 383 case tt._try:
michael@0 384 next();
michael@0 385 node.block = parseBlock();
michael@0 386 node.handler = null;
michael@0 387 if (token.type === tt._catch) {
michael@0 388 var clause = startNode();
michael@0 389 next();
michael@0 390 expect(tt.parenL);
michael@0 391 clause.param = parseIdent();
michael@0 392 expect(tt.parenR);
michael@0 393 clause.guard = null;
michael@0 394 clause.body = parseBlock();
michael@0 395 node.handler = finishNode(clause, "CatchClause");
michael@0 396 }
michael@0 397 node.finalizer = eat(tt._finally) ? parseBlock() : null;
michael@0 398 if (!node.handler && !node.finalizer) return node.block;
michael@0 399 return finishNode(node, "TryStatement");
michael@0 400
michael@0 401 case tt._var:
michael@0 402 next();
michael@0 403 node = parseVar(node);
michael@0 404 semicolon();
michael@0 405 return node;
michael@0 406
michael@0 407 case tt._while:
michael@0 408 next();
michael@0 409 node.test = parseParenExpression();
michael@0 410 node.body = parseStatement();
michael@0 411 return finishNode(node, "WhileStatement");
michael@0 412
michael@0 413 case tt._with:
michael@0 414 next();
michael@0 415 node.object = parseParenExpression();
michael@0 416 node.body = parseStatement();
michael@0 417 return finishNode(node, "WithStatement");
michael@0 418
michael@0 419 case tt.braceL:
michael@0 420 return parseBlock();
michael@0 421
michael@0 422 case tt.semi:
michael@0 423 next();
michael@0 424 return finishNode(node, "EmptyStatement");
michael@0 425
michael@0 426 default:
michael@0 427 var expr = parseExpression();
michael@0 428 if (isDummy(expr)) {
michael@0 429 next();
michael@0 430 if (token.type === tt.eof) return finishNode(node, "EmptyStatement");
michael@0 431 return parseStatement();
michael@0 432 } else if (starttype === tt.name && expr.type === "Identifier" && eat(tt.colon)) {
michael@0 433 node.body = parseStatement();
michael@0 434 node.label = expr;
michael@0 435 return finishNode(node, "LabeledStatement");
michael@0 436 } else {
michael@0 437 node.expression = expr;
michael@0 438 semicolon();
michael@0 439 return finishNode(node, "ExpressionStatement");
michael@0 440 }
michael@0 441 }
michael@0 442 }
michael@0 443
michael@0 444 function parseBlock() {
michael@0 445 var node = startNode();
michael@0 446 pushCx();
michael@0 447 expect(tt.braceL);
michael@0 448 var blockIndent = curIndent, line = curLineStart;
michael@0 449 node.body = [];
michael@0 450 while (!closes(tt.braceR, blockIndent, line, true))
michael@0 451 node.body.push(parseStatement());
michael@0 452 popCx();
michael@0 453 eat(tt.braceR);
michael@0 454 return finishNode(node, "BlockStatement");
michael@0 455 }
michael@0 456
michael@0 457 function parseFor(node, init) {
michael@0 458 node.init = init;
michael@0 459 node.test = node.update = null;
michael@0 460 if (eat(tt.semi) && token.type !== tt.semi) node.test = parseExpression();
michael@0 461 if (eat(tt.semi) && token.type !== tt.parenR) node.update = parseExpression();
michael@0 462 popCx();
michael@0 463 expect(tt.parenR);
michael@0 464 node.body = parseStatement();
michael@0 465 return finishNode(node, "ForStatement");
michael@0 466 }
michael@0 467
michael@0 468 function parseForIn(node, init) {
michael@0 469 node.left = init;
michael@0 470 node.right = parseExpression();
michael@0 471 popCx();
michael@0 472 expect(tt.parenR);
michael@0 473 node.body = parseStatement();
michael@0 474 return finishNode(node, "ForInStatement");
michael@0 475 }
michael@0 476
michael@0 477 function parseVar(node, noIn) {
michael@0 478 node.declarations = [];
michael@0 479 node.kind = "var";
michael@0 480 while (token.type === tt.name) {
michael@0 481 var decl = startNode();
michael@0 482 decl.id = parseIdent();
michael@0 483 decl.init = eat(tt.eq) ? parseExpression(true, noIn) : null;
michael@0 484 node.declarations.push(finishNode(decl, "VariableDeclarator"));
michael@0 485 if (!eat(tt.comma)) break;
michael@0 486 }
michael@0 487 if (!node.declarations.length) {
michael@0 488 var decl = startNode();
michael@0 489 decl.id = dummyIdent();
michael@0 490 node.declarations.push(finishNode(decl, "VariableDeclarator"));
michael@0 491 }
michael@0 492 return finishNode(node, "VariableDeclaration");
michael@0 493 }
michael@0 494
michael@0 495 function parseExpression(noComma, noIn) {
michael@0 496 var expr = parseMaybeAssign(noIn);
michael@0 497 if (!noComma && token.type === tt.comma) {
michael@0 498 var node = startNodeFrom(expr);
michael@0 499 node.expressions = [expr];
michael@0 500 while (eat(tt.comma)) node.expressions.push(parseMaybeAssign(noIn));
michael@0 501 return finishNode(node, "SequenceExpression");
michael@0 502 }
michael@0 503 return expr;
michael@0 504 }
michael@0 505
michael@0 506 function parseParenExpression() {
michael@0 507 pushCx();
michael@0 508 expect(tt.parenL);
michael@0 509 var val = parseExpression();
michael@0 510 popCx();
michael@0 511 expect(tt.parenR);
michael@0 512 return val;
michael@0 513 }
michael@0 514
michael@0 515 function parseMaybeAssign(noIn) {
michael@0 516 var left = parseMaybeConditional(noIn);
michael@0 517 if (token.type.isAssign) {
michael@0 518 var node = startNodeFrom(left);
michael@0 519 node.operator = token.value;
michael@0 520 node.left = checkLVal(left);
michael@0 521 next();
michael@0 522 node.right = parseMaybeAssign(noIn);
michael@0 523 return finishNode(node, "AssignmentExpression");
michael@0 524 }
michael@0 525 return left;
michael@0 526 }
michael@0 527
michael@0 528 function parseMaybeConditional(noIn) {
michael@0 529 var expr = parseExprOps(noIn);
michael@0 530 if (eat(tt.question)) {
michael@0 531 var node = startNodeFrom(expr);
michael@0 532 node.test = expr;
michael@0 533 node.consequent = parseExpression(true);
michael@0 534 node.alternate = expect(tt.colon) ? parseExpression(true, noIn) : dummyIdent();
michael@0 535 return finishNode(node, "ConditionalExpression");
michael@0 536 }
michael@0 537 return expr;
michael@0 538 }
michael@0 539
michael@0 540 function parseExprOps(noIn) {
michael@0 541 var indent = curIndent, line = curLineStart;
michael@0 542 return parseExprOp(parseMaybeUnary(noIn), -1, noIn, indent, line);
michael@0 543 }
michael@0 544
michael@0 545 function parseExprOp(left, minPrec, noIn, indent, line) {
michael@0 546 if (curLineStart != line && curIndent < indent && tokenStartsLine()) return left;
michael@0 547 var prec = token.type.binop;
michael@0 548 if (prec != null && (!noIn || token.type !== tt._in)) {
michael@0 549 if (prec > minPrec) {
michael@0 550 var node = startNodeFrom(left);
michael@0 551 node.left = left;
michael@0 552 node.operator = token.value;
michael@0 553 next();
michael@0 554 if (curLineStart != line && curIndent < indent && tokenStartsLine())
michael@0 555 node.right = dummyIdent();
michael@0 556 else
michael@0 557 node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn, indent, line);
michael@0 558 var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
michael@0 559 return parseExprOp(node, minPrec, noIn, indent, line);
michael@0 560 }
michael@0 561 }
michael@0 562 return left;
michael@0 563 }
michael@0 564
michael@0 565 function parseMaybeUnary(noIn) {
michael@0 566 if (token.type.prefix) {
michael@0 567 var node = startNode(), update = token.type.isUpdate;
michael@0 568 node.operator = token.value;
michael@0 569 node.prefix = true;
michael@0 570 next();
michael@0 571 node.argument = parseMaybeUnary(noIn);
michael@0 572 if (update) node.argument = checkLVal(node.argument);
michael@0 573 return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
michael@0 574 }
michael@0 575 var expr = parseExprSubscripts();
michael@0 576 while (token.type.postfix && !canInsertSemicolon()) {
michael@0 577 var node = startNodeFrom(expr);
michael@0 578 node.operator = token.value;
michael@0 579 node.prefix = false;
michael@0 580 node.argument = checkLVal(expr);
michael@0 581 next();
michael@0 582 expr = finishNode(node, "UpdateExpression");
michael@0 583 }
michael@0 584 return expr;
michael@0 585 }
michael@0 586
michael@0 587 function parseExprSubscripts() {
michael@0 588 return parseSubscripts(parseExprAtom(), false, curIndent, curLineStart);
michael@0 589 }
michael@0 590
michael@0 591 function parseSubscripts(base, noCalls, startIndent, line) {
michael@0 592 for (;;) {
michael@0 593 if (curLineStart != line && curIndent <= startIndent && tokenStartsLine()) {
michael@0 594 if (token.type == tt.dot && curIndent == startIndent)
michael@0 595 --startIndent;
michael@0 596 else
michael@0 597 return base;
michael@0 598 }
michael@0 599
michael@0 600 if (eat(tt.dot)) {
michael@0 601 var node = startNodeFrom(base);
michael@0 602 node.object = base;
michael@0 603 if (curLineStart != line && curIndent <= startIndent && tokenStartsLine())
michael@0 604 node.property = dummyIdent();
michael@0 605 else
michael@0 606 node.property = parsePropertyName() || dummyIdent();
michael@0 607 node.computed = false;
michael@0 608 base = finishNode(node, "MemberExpression");
michael@0 609 } else if (token.type == tt.bracketL) {
michael@0 610 pushCx();
michael@0 611 next();
michael@0 612 var node = startNodeFrom(base);
michael@0 613 node.object = base;
michael@0 614 node.property = parseExpression();
michael@0 615 node.computed = true;
michael@0 616 popCx();
michael@0 617 expect(tt.bracketR);
michael@0 618 base = finishNode(node, "MemberExpression");
michael@0 619 } else if (!noCalls && token.type == tt.parenL) {
michael@0 620 pushCx();
michael@0 621 var node = startNodeFrom(base);
michael@0 622 node.callee = base;
michael@0 623 node.arguments = parseExprList(tt.parenR);
michael@0 624 base = finishNode(node, "CallExpression");
michael@0 625 } else {
michael@0 626 return base;
michael@0 627 }
michael@0 628 }
michael@0 629 }
michael@0 630
michael@0 631 function parseExprAtom() {
michael@0 632 switch (token.type) {
michael@0 633 case tt._this:
michael@0 634 var node = startNode();
michael@0 635 next();
michael@0 636 return finishNode(node, "ThisExpression");
michael@0 637 case tt.name:
michael@0 638 return parseIdent();
michael@0 639 case tt.num: case tt.string: case tt.regexp:
michael@0 640 var node = startNode();
michael@0 641 node.value = token.value;
michael@0 642 node.raw = input.slice(token.start, token.end);
michael@0 643 next();
michael@0 644 return finishNode(node, "Literal");
michael@0 645
michael@0 646 case tt._null: case tt._true: case tt._false:
michael@0 647 var node = startNode();
michael@0 648 node.value = token.type.atomValue;
michael@0 649 node.raw = token.type.keyword;
michael@0 650 next();
michael@0 651 return finishNode(node, "Literal");
michael@0 652
michael@0 653 case tt.parenL:
michael@0 654 var tokStart1 = token.start;
michael@0 655 next();
michael@0 656 var val = parseExpression();
michael@0 657 val.start = tokStart1;
michael@0 658 val.end = token.end;
michael@0 659 expect(tt.parenR);
michael@0 660 return val;
michael@0 661
michael@0 662 case tt.bracketL:
michael@0 663 var node = startNode();
michael@0 664 pushCx();
michael@0 665 node.elements = parseExprList(tt.bracketR);
michael@0 666 return finishNode(node, "ArrayExpression");
michael@0 667
michael@0 668 case tt.braceL:
michael@0 669 return parseObj();
michael@0 670
michael@0 671 case tt._function:
michael@0 672 var node = startNode();
michael@0 673 next();
michael@0 674 return parseFunction(node, false);
michael@0 675
michael@0 676 case tt._new:
michael@0 677 return parseNew();
michael@0 678
michael@0 679 default:
michael@0 680 return dummyIdent();
michael@0 681 }
michael@0 682 }
michael@0 683
michael@0 684 function parseNew() {
michael@0 685 var node = startNode(), startIndent = curIndent, line = curLineStart;
michael@0 686 next();
michael@0 687 node.callee = parseSubscripts(parseExprAtom(), true, startIndent, line);
michael@0 688 if (token.type == tt.parenL) {
michael@0 689 pushCx();
michael@0 690 node.arguments = parseExprList(tt.parenR);
michael@0 691 } else {
michael@0 692 node.arguments = [];
michael@0 693 }
michael@0 694 return finishNode(node, "NewExpression");
michael@0 695 }
michael@0 696
michael@0 697 function parseObj() {
michael@0 698 var node = startNode();
michael@0 699 node.properties = [];
michael@0 700 pushCx();
michael@0 701 next();
michael@0 702 var propIndent = curIndent, line = curLineStart;
michael@0 703 while (!closes(tt.braceR, propIndent, line)) {
michael@0 704 var name = parsePropertyName();
michael@0 705 if (!name) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; }
michael@0 706 var prop = {key: name}, isGetSet = false, kind;
michael@0 707 if (eat(tt.colon)) {
michael@0 708 prop.value = parseExpression(true);
michael@0 709 kind = prop.kind = "init";
michael@0 710 } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
michael@0 711 (prop.key.name === "get" || prop.key.name === "set")) {
michael@0 712 isGetSet = true;
michael@0 713 kind = prop.kind = prop.key.name;
michael@0 714 prop.key = parsePropertyName() || dummyIdent();
michael@0 715 prop.value = parseFunction(startNode(), false);
michael@0 716 } else {
michael@0 717 next();
michael@0 718 eat(tt.comma);
michael@0 719 continue;
michael@0 720 }
michael@0 721
michael@0 722 node.properties.push(prop);
michael@0 723 eat(tt.comma);
michael@0 724 }
michael@0 725 popCx();
michael@0 726 eat(tt.braceR);
michael@0 727 return finishNode(node, "ObjectExpression");
michael@0 728 }
michael@0 729
michael@0 730 function parsePropertyName() {
michael@0 731 if (token.type === tt.num || token.type === tt.string) return parseExprAtom();
michael@0 732 if (token.type === tt.name || token.type.keyword) return parseIdent();
michael@0 733 }
michael@0 734
michael@0 735 function parseIdent() {
michael@0 736 var node = startNode();
michael@0 737 node.name = token.type === tt.name ? token.value : token.type.keyword;
michael@0 738 next();
michael@0 739 return finishNode(node, "Identifier");
michael@0 740 }
michael@0 741
michael@0 742 function parseFunction(node, isStatement) {
michael@0 743 if (token.type === tt.name) node.id = parseIdent();
michael@0 744 else if (isStatement) node.id = dummyIdent();
michael@0 745 else node.id = null;
michael@0 746 node.params = [];
michael@0 747 pushCx();
michael@0 748 expect(tt.parenL);
michael@0 749 while (token.type == tt.name) {
michael@0 750 node.params.push(parseIdent());
michael@0 751 eat(tt.comma);
michael@0 752 }
michael@0 753 popCx();
michael@0 754 eat(tt.parenR);
michael@0 755 node.body = parseBlock();
michael@0 756 return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
michael@0 757 }
michael@0 758
michael@0 759 function parseExprList(close) {
michael@0 760 var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart;
michael@0 761 next(); // Opening bracket
michael@0 762 if (curLineStart > continuedLine) continuedLine = curLineStart;
michael@0 763 while (!closes(close, indent + (curLineStart <= continuedLine ? 1 : 0), line)) {
michael@0 764 var elt = parseExpression(true);
michael@0 765 if (isDummy(elt)) {
michael@0 766 if (closes(close, indent, line)) break;
michael@0 767 next();
michael@0 768 } else {
michael@0 769 elts.push(elt);
michael@0 770 }
michael@0 771 while (eat(tt.comma)) {}
michael@0 772 }
michael@0 773 popCx();
michael@0 774 eat(close);
michael@0 775 return elts;
michael@0 776 }
michael@0 777 });

mercurial