Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 | }); |