[Pkg-javascript-commits] [node-acorn-jsx] 12/484: Comment tokenizer

Bastien Roucariès rouca at moszumanska.debian.org
Sat Aug 19 14:19:56 UTC 2017


This is an automated email from the git hooks/post-receive script.

rouca pushed a commit to branch master
in repository node-acorn-jsx.

commit 237bfbfb9b15f66d96dde2dce6367158da8d9b03
Author: Marijn Haverbeke <marijnh at gmail.com>
Date:   Tue Oct 2 08:44:41 2012 +0200

    Comment tokenizer
---
 acorn.js | 1924 +++++++++++++++++++++++++++++++++++---------------------------
 1 file changed, 1091 insertions(+), 833 deletions(-)

diff --git a/acorn.js b/acorn.js
index 5cfca5b..9d6e6ab 100644
--- a/acorn.js
+++ b/acorn.js
@@ -1,731 +1,358 @@
-// Some regexps (identifierchars, whitespace) and error message
-// strings from esprima(.org) by Ariya Hidayat.
+// Acorn is a tiny, fast JavaScript parser written in JavaScript.
+//
+// Acorn was written by Marijn Haverbeke and released under an MIT
+// license. The Unicode regexps (for identifiers and whitespace) were
+// taken from [Esprima](http://esprima.org) by Ariya Hidayat.
 
 (function(exports) {
   "strict mode";
 
   exports.version = "0.0.1";
 
-  // State variables
+  // The main exported interface (under `window.acorn` when in the
+  // browser) a `parse` function that takes a code string and returns
+  // an abstract syntax tree as specified by [Mozilla parser
+  // API][api], with the caveat that the SpiderMonkey-specific syntax
+  // (`let`, `yield`, inline XML, etc) is not recognized, and that
+  // range information is attached directly to the AST nodes as
+  // `start` and `end` properties, rather than wrapped in an
+  // additional object under a `loc` property.
+  //
+  // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
 
   var options, input, inputLen;
-  var next, last;
-  var lastStart, lastEnd, lastCommentsAfter;
-  var inFunction, labels, strict;
 
-  // PARSER
+  exports.parse = function(inpt, opts) {
+    input = String(inpt); inputLen = input.length;
+    options = opts || {};
+    for (var opt in defaultOptions) if (!options.hasOwnProperty(opt))
+      options[opt] = defaultOptions[opt];
+    return parseTopLevel();
+  };
+
+  // A second optional argument can be given to further configure
+  // the parser process. These options are recognized:
 
   var defaultOptions = exports.defaultOptions = {
+    // `ecmaVersion` indicates the ECMAScript version to parse. Must
+    // be either 3 or 5. This
+    // influences support for strict mode, the set of reserved words, and
+    // support for getters and setter.
     ecmaVersion: 5,
+    // Turn on `strictSemicolons` to prevent the parser from doing
+    // automatic semicolon insertion.
     strictSemicolons: false,
+    // When `allowTrailingCommas` is false, the parser will not allow
+    // trailing commas in array and object literals.
     allowTrailingCommas: true,
+    // By default, reserved words are not enforced. Enable
+    // `forbidReserved` to enforce them.
     forbidReserved: false,
+    // When `trackComments` is turned on, the parser will attach
+    // `commentsBefore` and `commentsAfter` properties to AST nodes
+    // holding arrays of strings. A single comment may appear in both
+    // a `commentsBefore` and `commentsAfter` array (of the nodes
+    // after and before it), but never twice in the before (or after)
+    // array of different nodes.
     trackComments: false,
+    // By default, the `start` and `end` properties attached to nodes hold
+    // offsets into the file. When `linePositions` is on, they will hold
+    // `{offset, line, column}` (with line being 1-based and column
+    // 0-based) instead.
     linePositions: false
   };
 
-  function next() {
-    lastStart = tokStart;
-    lastEnd = tokEnd;
-    lastCommentsAfter = tokCommentsAfter;
-    readToken();
-  }
-
-  function setStrict(strct) {
-    strict = strct;
-    tokPos = options.linePositions ? lastEnd.offset : lastEnd;
-    skipSpace();
-    readToken();
-  }
+  // The `getLineInfo` function is mostly useful when the
+  // `linePositions` option is off (for performance reasons) and you
+  // want to find the line/column position for a given character
+  // offset. `input` should be the code string that the offset refers
+  // into.
 
-  function startNode() {
-    var node = {type: null, start: tokStart};
-    if (options.trackComments && tokCommentsBefore) {
-      node.commentsBefore = tokCommentsBefore;
-      tokCommentsBefore = null;
+  var getLineInfo = exports.getLineInfo = function(input, offset) {
+    for (var line = 1, cur = 0;;) {
+      lineBreak.lastIndex = cur;
+      var match = lineBreak.exec(input);
+      if (match && match.index < offset) {
+        ++line;
+        cur = match.index + match[0].length;
+      } else break;
     }
-    return node;
-  }
+    return {line: line, column: offset - cur};
+  };
 
-  function startNodeFrom(other) {
-    var node = {type: null, start: other.start};
-    if (other.commentsBefore) {
-      node.commentsBefore = other.commentsBefore;
-      other.commentsBefore = null;
-    }
-    return node;
-  }
+  // Acorn is organized as a tokenizer and a recursive-descent parser.
+  // Both use (closure-)global variables to keep their state and
+  // communicate. We already saw the `options`, `input`, and
+  // `inputLen` variables above (set in `parse`).
 
-  function finishNode(node, type) {
-    if (type != null) node.type = type;
-    node.end = lastEnd;
-    if (options.trackComments && lastCommentsAfter)
-      node.commentsAfter = tokCommentsAfter;
-    return node;
-  }
+  // The current position of the tokenizer in the input.
 
-  exports.parse = function(inpt, opts) {
-    input = String(inpt); inputLen = input.length;
-    options = opts || {};
-    for (var opt in defaultOptions) if (!options.hasOwnProperty(opt))
-      options[opt] = defaultOptions[opt];
-    initTokenState();
-    lastStart = lastEnd = options.linePositions ? curLinePos() : tokPos;
-    lastCommentsAfter = inFunction = strict = null;
-    labels = [];
-    readToken();
+  var tokPos;
 
-    var node = startNode(), first = true;
-    node.body = [];
-    while (tokType !== _eof) {
-      var stmt = parseStatement();
-      node.body.push(stmt);
-      if (first && isUseStrict(stmt)) setStrict(true);
-      first = false;
-    }
-    return finishNode(node, "Program");
-  };
+  // The start and end positions of the current token. These hold
+  // integers when `options.linePositions` is false, and objects
+  // otherwise.
 
-  function isUseStrict(stmt) {
-    return stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" &&
-      stmt.expression.value === "use strict";
-  }
+  var tokStart, tokEnd;
 
-  function eat(type) {
-    if (tokType === type) {
-      next();
-      return true;
-    }
-  }
+  // The type and value of the current token. Token types are objects,
+  // named by variables against which they can be compared, and
+  // holding properties that describe them (indicating, for example,
+  // the precedence of an infix operator, and the original name of a
+  // keyword token). The kind of value that's held in `tokVal` depends
+  // on the type of the token. For literals, it is the literal value,
+  // for operators, the operator name, and so on.
 
-  function canInsertSemicolon() {
-    return tokType === _eof || tokType === _braceR ||
-      !options.strictSemicolons &&
-      newline.test(options.linePositions ? input.slice(lastEnd.offset, tokStart.offset)
-                                         : input.slice(lastEnd, tokStart));
-  }
+  var tokType, tokVal;
 
-  function semicolon() {
-    if (!eat(_semi) && !canInsertSemicolon()) unexpected();
-  }
+  // These are used to hold arrays of comments when
+  // `options.trackComments` is true.
 
-  function expect(type) {
-    if (tokType === type) next();
-    else unexpected();
-  }
+  var tokCommentsBefore, tokCommentsAfter;
 
-  function unexpected() {
-    raise(tokStart, "Unexpected token");
-  }
+  // Interal state for the tokenizer. To distinguish between division
+  // operators and regular expressions, it remembers whether the last
+  // token was one that is allowed to be followed by an expression.
+  // (If it is, a slash is probably a regexp, if it isn't it's a
+  // division operator. See the `parseStatement` function for a
+  // caveat.)
 
-  function parseStatement() {
-    // if expecting a statement and found a slash as operator,
-    // it must be a literal regexp.
-    if (tokType === _slash)
-      readToken(true);
+  var tokRegexpAllowed, tokComments;
 
-    var starttype = tokType, node = startNode();
+  // When `options.linePositions` is true, these are used to keep
+  // track of the current line, and know when a new line has been
+  // entered. See the `curLinePos` function.
 
-    switch (starttype) {
-    case _break: case _continue:
-      next();
-      var isBreak = starttype === _break;
-      if (eat(_semi) || canInsertSemicolon()) node.label = null;
-      else if (tokType !== _name) unexpected();
-      else {
-        node.label = parseIdent();
-        semicolon();
-      }
-      for (var i = 0; i < labels.length; ++i) {
-        var lab = labels[i];
-        if ((node.label == null || lab.name === node.label.name) &&
-            lab.kind != null && (isBreak || lab.kind === "loop")) break;
-      }
-      if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword);
-      return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
+  var tokCurLine, tokLineStart, tokLineStartNext;
 
-    case _debugger:
-      next();
-      return finishNode(node, "DebuggerStatement");
+  // These store the position of the previous token, which is useful
+  // when finishing a node and assigning its `end` position.
 
-    case _do:
-      next();
-      labels.push({kind: "loop"});
-      node.body = parseStatement();
-      labels.pop();
-      expect(_while);
-      node.test = parseParenExpression();
-      return finishNode(node, "DoWhileStatement");
+  var lastStart, lastEnd;
 
-    case _for:
-      next();
-      expect(_parenL);
-      if (tokType === _semi) return parseFor(node, null);
-      if (tokType === _var) {
-        var init = startNode();
-        next();
-        parseVar(init, true);
-        if (init.declarations.length === 1 && eat(_in))
-          return parseForIn(node, init);
-        return parseFor(node, init);
-      }
-      var init = parseExpression(false, true);
-      if (eat(_in)) {checkLVal(init); return parseForIn(node, init);}
-      return parseFor(node, init);
+  // This is the parser's state. `inFunction` is used to reject
+  // `return` statements outside of functions, `labels` to verify that
+  // `break` and `continue` have somewhere to jump to, and `strict`
+  // indicates whether strict mode is on.
 
-    case _function:
-      next();
-      return parseFunction(node, true);
+  var inFunction, labels, strict;
 
-    case _if:
-      next();
-      node.test = parseParenExpression();
-      node.consequent = parseStatement();
-      node.alternate = eat(_else) ? parseStatement() : null;
-      return finishNode(node, "IfStatement");
+  // This function is used to raise exceptions on parse errors. It
+  // takes either a `{line, column}` object or an offset integer (into
+  // the current `input`) as `pos` argument. It attaches the position
+  // to the end of the error message, and then raises a `SyntaxError`
+  // with that message.
 
-    case _return:
-      if (!inFunction) raise(tokStart, "'return' outside of function");
-      next();
-      if (eat(_semi) || canInsertSemicolon()) node.argument = null;
-      else { node.argument = parseExpression(); semicolon(); }
-      return finishNode(node, "ReturnStatement");
+  function raise(pos, message) {
+    if (typeof pos == "number") pos = getLineInfo(input, pos);
+    message += " (" + pos.line + ":" + pos.column + ")";
+    throw new SyntaxError(message);
+  }
 
-    case _switch:
-      next();
-      node.discriminant = parseParenExpression();
-      node.cases = [];
-      expect(_braceL);
-      labels.push({kind: "switch"});
-      for (var cur, sawDefault; !eat(_braceR);) {
-        if (tokType === _case || tokType === _default) {
-          var isCase = tokType === _case;
-          if (cur) finishNode(cur, "SwitchCase");
-          node.cases.push(cur = startNode());
-          cur.consequent = [];
-          next();
-          if (isCase) cur.test = parseExpression();
-          else {
-            if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true;
-            cur.test = null;
-          }
-          expect(_colon);
-        } else {
-          if (!cur) unexpected();
-          cur.consequent.push(parseStatement());
-        }
-      }
-      if (cur) finishNode(cur, "SwitchCase");
-      labels.pop();
-      return finishNode(node, "SwitchStatement");
+  // ## Token types
 
-    case _throw:
-      next();
-      node.argument = parseExpression();
-      return finishNode(node, "ThrowStatement");
+  // The assignment of fine-grained, information-carrying type objects
+  // allows the tokenizer to store the information it has about a
+  // token in a way that is very cheap for the parser to look up.
 
-    case _try:
-      next();
-      node.block = parseBlock();
-      node.handlers = [];
-      while (tokType === _catch) {
-        var clause = startNode();
-        next();
-        expect(_parenL);
-        clause.param = parseIdent();
-        if (strict && isStrictBadIdWord(clause.param.name))
-          raise(clause.param.start, "Binding " + clause.param.name + " in strict mode");
-        expect(_parenR);
-        clause.guard = null;
-        clause.body = parseBlock();
-        node.handlers.push(finishNode(clause, "CatchClause"));
-      }
-      node.finalizer = eat(_finally) ? parseBlock() : null;
-      if (!node.handlers.length && !node.finalizer)
-        raise(node.start, "Missing catch or finally clause");
-      return finishNode(node, "TryStatement");
+  // All token type variables start with an underscore, to make them
+  // easy to recognize.
 
-    case _var:
-      next();
-      node = parseVar(node);
-      semicolon();
-      return node;
+  // These are the general types. The `type` property is only used to
+  // make them recognizeable when debugging.
 
-    case _while:
-      next();
-      node.test = parseParenExpression();
-      labels.push({kind: "loop"});
-      node.body = parseStatement();
-      labels.pop();
-      return finishNode(node, "WhileStatement");
+  var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"};
+  var _name = {type: "name"}, _eof = {type: "eof"};
 
-    case _with:
-      if (strict) raise(tokStart, "'with' in strict mode");
-      next();
-      node.object = parseParenExpression();
-      node.body = parseStatement();
-      return finishNode(node, "WithStatement");
+  // Keyword tokens. The `keyword` property (also used in keyword-like
+  // operators) indicates that the token originated from an
+  // identifier-like word, which is used when parsing property names.
+  //
+  // The `beforeExpr` property is used to disambiguate between regular
+  // expressions and divisions. It is set on all token types that can
+  // be followed by an expression (thus, a slash after them would be a
+  // regular expression).
+  //
+  // `isLoop` marks a keyword as starting a loop, which is important
+  // to know when parsing a label, in order to allow or disallow
+  // continue jumps to that label.
 
-    case _braceL:
-      return parseBlock();
+  var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"};
+  var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"};
+  var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true};
+  var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"};
+  var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"};
+  var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"};
+  var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
 
-    case _semi:
-      next();
-      return finishNode(node, "EmptyStatement");
+  // The keywords that denote values.
 
-    default:
-      var maybeName = tokVal, expr = parseExpression();
-      if (starttype === _name && expr.type === "Identifier" && eat(_colon)) {
-        for (var i = 0; i < labels.length; ++i)
-          if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared");
-        var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null;
-        labels.push({name: maybeName, kind: kind});
-        node.body = parseStatement();
-        node.label = expr;
-        return finishNode(node, "LabeledStatement");
-      } else {
-        node.expression = expr;
-        semicolon();
-        return finishNode(node, "ExpressionStatement");
-      }
-    }
-  }
+  var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true};
+  var _false = {keyword: "false", atomValue: false};
 
-  function parseParenExpression() {
-    expect(_parenL);
-    var val = parseExpression();
-    expect(_parenR);
-    return val;
-  }
+  // Some keywords are treated as regular operators. `in` sometimes
+  // (when parsing `for`) needs to be tested against specifically, so
+  // we assign a variable name to it for quick comparing.
 
-  function parseBlock(allowStrict) {
-    var node = startNode(), first = true, strict = false, oldStrict;
-    node.body = [];
-    expect(_braceL);
-    while (!eat(_braceR)) {
-      var stmt = parseStatement();
-      node.body.push(stmt);
-      if (first && isUseStrict(stmt)) {
-        oldStrict = strict;
-        setStrict(strict = true);
-      }
-      first = false
-    }
-    if (strict && !oldStrict) setStrict(false);
-    return finishNode(node, "BlockStatement");
-  }
+  var _in = {keyword: "in", binop: 7, beforeExpr: true};
 
-  function parseFor(node, init) {
-    node.init = init;
-    expect(_semi);
-    node.test = tokType === _semi ? null : parseExpression();
-    expect(_semi);
-    node.update = tokType === _parenR ? null : parseExpression();
-    expect(_parenR);
-    labels.push({kind: "loop"});
-    node.body = parseStatement();
-    labels.pop();
-    return finishNode(node, "ForStatement");
-  }
+  // Map keyword names to token types.
 
-  function parseForIn(node, init) {
-    node.left = init;
-    node.right = parseExpression();
-    expect(_parenR);
-    labels.push({kind: "loop"});
-    node.body = parseStatement();
-    labels.pop();
-    return finishNode(node, "ForInStatement");
-  }
+  var keywordTypes = {"break": _break, "case": _case, "catch": _catch,
+                      "continue": _continue, "debugger": _debugger, "default": _default,
+                      "do": _do, "else": _else, "finally": _finally, "for": _for,
+                      "function": _function, "if": _if, "return": _return, "switch": _switch,
+                      "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with,
+                      "null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
+                      "instanceof": {keyword: "instanceof", binop: 7},
+                      "typeof": {keyword: "typeof", prefix: true},
+                      "void": {keyword: "void", prefix: true},
+                      "delete": {keyword: "delete", prefix: true}};
 
-  function parseVar(node, noIn) {
-    node.declarations = [];
-    node.kind = "var";
-    for (;;) {
-      var decl = startNode();
-      decl.id = parseIdent();
-      if (strict && isStrictBadIdWord(decl.id.name))
-        raise(decl.id.start, "Binding " + decl.id.name + " in strict mode");
-      decl.init = eat(_eq) ? parseExpression(true, noIn) : null;
-      node.declarations.push(finishNode(decl, "VariableDeclarator"));
-      if (!eat(_comma)) break;
-    }
-    return finishNode(node, "VariableDeclaration");
-  }
+  // Punctuation token types. Again, the `type` property is purely for debugging.
 
-  function parsePropertyName() {
-    if (tokType === _num || tokType === _string) return parseExprAtom();
-    return parseIdent(true);
-  }
+  var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true};
+  var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"};
+  var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
+  var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true};
 
-  function parseIdent(liberal) {
-    var node = startNode();
-    if (tokType !== _name) {
-      if (liberal && tokType.keyword) node.name = tokType.keyword;
-      else unexpected();
-    } else node.name = tokVal;
-    next();
-    return finishNode(node, "Identifier");
-  }
+  // Operators. These carry several kinds of properties to help the
+  // parser use them properly (the presence of these properties is
+  // what categorizes them as operators).
+  //
+  // `binop`, when present, specifies that this operator is a binary
+  // operator, and will refer to its precedence.
+  //
+  // `prefix` and `postfix` mark the operator as a prefix or postfix
+  // unary operator. `isUpdate` specifies that the node produced by
+  // the operator should be of type UpdateExpression rather than
+  // simply UnaryExpression (`++` and `--`).
+  //
+  // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as
+  // binary operators with a very low precedence, that should result
+  // in AssignmentExpression nodes.
 
-  function parseFunction(node, isStatement) {
-    if (tokType === _name) node.id = parseIdent();
-    else if (isStatement) unexpected();
-    else node.id = null;
-    node.params = [];
-    var first = true;
-    expect(_parenL);
-    while (!eat(_parenR)) {
-      if (!first) expect(_comma); else first = false;
-      node.params.push(parseIdent());
-    }
+  var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
+  var _assign = {isAssign: true, beforeExpr: true}, _plusmin = {binop: 9, prefix: true, beforeExpr: true};
+  var _incdec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
+  var _bin1 = {binop: 1, beforeExpr: true}, _bin2 = {binop: 2, beforeExpr: true};
+  var _bin3 = {binop: 3, beforeExpr: true}, _bin4 = {binop: 4, beforeExpr: true};
+  var _bin5 = {binop: 5, beforeExpr: true}, _bin6 = {binop: 6, beforeExpr: true};
+  var _bin7 = {binop: 7, beforeExpr: true}, _bin8 = {binop: 8, beforeExpr: true};
+  var _bin10 = {binop: 10, beforeExpr: true};
 
-    var oldInFunc = inFunction, oldLabels = labels;
-    inFunction = true; labels = [];
-    node.body = parseBlock(true);
-    if (strict || node.body.body.length && isUseStrict(node.body.body[0])) {
-      for (var i = node.id ? -1 : 0; i < node.params.length; ++i) {
-        var id = i < 0 ? node.id : node.params[i];
-        if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name))
-          raise(id.start, "Defining '" + id.name + "' in strict mode");
-        if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name)
-          raise(id.start, "Argument name clash in strict mode");
-      }
+  // This is a trick taken from Esprima. It turns out that, on
+  // non-Chrome browsers, to check whether a string is in a set, a
+  // predicate containing a big ugly `switch` statement is faster than
+  // a regular expression, and on Chrome the two are about on par.
+  // This function uses `eval` (non-lexical) to produce such a
+  // predicate from a space-separated string of words.
+  //
+  // It starts by sorting the words by length.
+
+  function makePredicate(words) {
+    words = words.split(" ");
+    var f = "(function(str){", cats = [];
+    out: for (var i = 0; i < words.length; ++i) {
+      for (var j = 0; j < cats.length; ++j)
+        if (cats[j][0].length == words[i].length) {
+          cats[j].push(words[i]);
+          continue out;
+        }
+      cats.push([words[i]]);
+    }
+    function compareTo(arr) {
+      if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";";
+      f += "switch(str){";
+      for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":";
+      f += "return true}return false;";
     }
 
-    inFunction = oldInFunc; labels = oldLabels;
-    return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
-  }
+    // When there are more than three length categories, an outer
+    // switch first dispatches on the lengths, to save on comparisons.
 
-  // EXPRESSION PARSING
+    if (cats.length > 3) {
+      cats.sort(function(a, b) {return b.length - a.length;});
+      f += "switch(str.length){";
+      for (var i = 0; i < cats.length; ++i) {
+        var cat = cats[i];
+        f += "case " + cat[0].length + ":";
+        compareTo(cat);
+      }
+      f += "}";
 
-  function parseExprList(close, allowTrailingComma, allowEmpty) {
-    var elts = [], first = true;
-    while (!eat(close)) {
-      if (!first) {
-        expect(_comma);
-        if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
-      } else first = false;
+    // Otherwise, simply generate a flat `switch` statement.
 
-      if (allowEmpty && tokType === _comma) elts.push(null);
-      else elts.push(parseExpression(true));
+    } else {
+      compareTo(words);
     }
-    return elts;
+    return (1, eval)(f + "})");
   }
 
-  function parseNew() {
-    var node = startNode();
-    next();
-    node.callee = parseSubscripts(parseExprAtom(false), false);
-    if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
-    else node.arguments = [];
-    return finishNode(node, "NewExpression");
-  }
+  // The ECMAScript 3 reserved word list.
 
-  function parseObj() {
-    var node = startNode(), first = true;
-    node.properties = [];
-    next();
-    while (!eat(_braceR)) {
-      if (!first) {
-        expect(_comma);
-        if (options.allowTrailingCommas && eat(_braceR)) break;
-      } else first = false;
+  var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile");
 
-      var prop = {key: parsePropertyName()}, isGetSet, kind;
-      if (eat(_colon)) {
-        prop.value = parseExpression(true);
-        kind = prop.kind = "init";
-      } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
-                 (prop.key.name === "get" || prop.key.name === "set")) {
-        isGetSet = true;
-        kind = prop.kind = prop.key.name;
-        prop.key = parsePropertyName();
-        if (!tokType === _parenL) unexpected();
-        prop.value = parseFunction(startNode(), false);
-      } else unexpected();
-      if (prop.key.type === "Identifier" && (strict || isGetSet)) {
-        for (var i = 0; i < node.properties.length; ++i) {
-          var other = node.properties[i];
-          if (other.key.name === prop.key.name) {
-            var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
-              kind === "init" && (other.kind === "get" || other.kind === "set");
-            if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
-            if (conflict) raise(prop.key.start, "Redefinition of property");
-          }
-        }
-      }
-      node.properties.push(prop);
-    }
-    return finishNode(node, "ObjectExpression");
-  }
+  // ECMAScript 5 reserved words.
 
-  function parseExprAtom() {
-    switch (tokType) {
-    case _name:
-      if (tokVal === "this") {
-        var node = startNode();
-        next();
-        return finishNode(node, "ThisExpression");
-      } else return parseIdent();
+  var isReservedWord5 = makePredicate("class enum extends super const export import");
 
-    case _num: case _string: case _regexp:
-      var node = startNode();
-      node.value = tokVal;
-      next();
-      return finishNode(node, "Literal");
+  // The additional reserved words in strict mode.
 
-    case _null: case _true: case _false:
-      var node = startNode();
-      node.value = tokType.atomValue;
-      next();
-      return finishNode(node, "Literal");
+  var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield");
 
-    case _parenL:
-      next();
-      var val = parseExpression();
-      expect(_parenR);
-      return val;
+  // The forbidden variable names in strict mode.
 
-    case _bracketL:
-      var node = startNode();
-      next();
-      node.elements = parseExprList(_bracketR, true, true);
-      return finishNode(node, "ArrayExpression");
+  var isStrictBadIdWord = makePredicate("eval arguments");
 
-    case _braceL:
-      return parseObj();
+  // And the keywords.
 
-    case _function:
-      var node = startNode();
-      next();
-      return parseFunction(node, false);
+  var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in");
 
-    case _new:
-      return parseNew();
+  // ## Character categories
 
-    default:
-      unexpected();
-    }
-  }
+  // Big ugly regular expressions that match characters in the
+  // whitespace, identifier, and identifier-start categories. These
+  // are only applied when a character is found to actually have a
+  // code point above 128.
 
-  function parseSubscripts(base, allowCalls) {
-    if (eat(_dot)) {
-      var node = startNodeFrom(base);
-      node.object = base;
-      node.property = parseIdent(true);
-      node.computed = false;
-      return parseSubscripts(finishNode(node, "MemberExpression"), allowCalls);
-    } else if (eat(_bracketL)) {
-      var node = startNodeFrom(base);
-      node.object = base;
-      node.property = parseExpression();
-      node.computed = true;
-      expect(_bracketR);
-      return parseSubscripts(finishNode(node, "MemberExpression"), allowCalls);
-    } else if (allowCalls && eat(_parenL)) {
-      var node = startNodeFrom(base);
-      node.callee = base;
-      node.arguments = parseExprList(_parenR, false);
-      return parseSubscripts(finishNode(node, "CallExpression"), allowCalls);
-    } else return base;
-  }
-
-  function parseExprSubscripts(allowCalls) {
-    return parseSubscripts(parseExprAtom(allowCalls), allowCalls);
-  }
-
-  function parseMaybeUnary(noIn) {
-    if (tokType.prefix) {
-      var node = startNode(), update = tokType.isUpdate;
-      node.operator = tokVal;
-      node.prefix = true;
-      next();
-      node.argument = parseMaybeUnary(noIn);
-      if (update) checkLVal(node.argument);
-      else if (strict && node.operator === "delete" &&
-               node.argument.type === "Identifier")
-        raise(node.start, "Deleting local variable in strict mode");
-      return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
-    }
-    var expr = parseExprSubscripts(true);
-    while (tokType.postfix && !canInsertSemicolon()) {
-      var node = startNodeFrom(expr);
-      node.operator = tokVal;
-      node.prefix = false;
-      node.argument = expr;
-      checkLVal(expr);
-      next();
-      expr = finishNode(node, "UpdateExpression");
-    }
-    return expr;
-  }
+  var nonASCIIwhitespace = /[\u1680\u180E\u2000-\u200A\u202F\u205F\u3000\uFEFF]/;
+  var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u [...]
+  var nonASCIIidentifierChars = "\u0371-\u0374\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a8 [...]
+  var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
+  var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
 
-  function parseExprOp(left, minPrec, noIn) {
-    var prec = tokType.binop;
-    if (prec != null && (!noIn || tokType !== _in)) {
-      if (prec > minPrec) {
-        var node = startNodeFrom(left);
-        node.left = left;
-        node.operator = tokVal;
-        next();
-        node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
-        var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
-        return parseExprOp(node, minPrec, noIn);
-      }
-    }
-    return left;
-  }
+  // Whether a single character denotes a newline.
 
-  function parseExprOps(noIn) {
-    return parseExprOp(parseMaybeUnary(noIn), -1, noIn);
-  }
+  var newline = /[\n\r\u2028\u2029]/;
 
-  function parseMaybeConditional(noIn) {
-    var expr = parseExprOps(noIn);
-    if (eat(_question)) {
-      var node = startNodeFrom(expr);
-      node.test = expr;
-      node.consequent = parseExpression(true);
-      expect(_colon);
-      node.alternate = parseExpression(true, noIn);
-      return finishNode(node, "ConditionalExpression");
-    }
-    return expr;
-  }
+  // Matches a whole line break (where CRLF is considered a single
+  // line break). Used to count lines.
 
-  function parseMaybeAssign(noIn) {
-    var left = parseMaybeConditional(noIn);
-    if (tokType.isAssign) {
-      var node = startNodeFrom(left);
-      node.operator = tokVal;
-      node.left = left;
-      next();
-      node.right = parseMaybeAssign(noIn);
-      checkLVal(left);
-      return finishNode(node, "AssignmentExpression");
-    }
-    return left;
-  }
+  var lineBreak = /\r\n?|[\n\r\u2028\u2029]/g;
 
-  function parseExpression(noComma, noIn) {
-    var expr = parseMaybeAssign(noIn);
-    if (!noComma && tokType === _comma) {
-      var node = startNodeFrom(expr);
-      node.expressions = [expr];
-      while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
-      return finishNode(node, "SequenceExpression");
-    }
-    return expr;
-  }
+  // Test whether a given character code starts an identifier.
 
-  function checkLVal(expr) {
-    if (expr.type !== "Identifier" && expr.type !== "MemberExpression")
-      raise(expr.start, "Assigning to rvalue");
-    if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name))
-      raise(expr.start, "Assigning to " + expr.name + " in strict mode");
+  function isIdentifierStart(code) {
+    return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) ||
+      code === 36 || code === 95 ||
+      (code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)));
   }
 
-  // TOKENIZER DEFINITIONS
-
-  // Token types
-  var _name = {type: "name"}, _eof = {type: "eof"};
-  var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"};
-
-  var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"};
-  var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"};
-  var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true};
-  var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"};
-  var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"};
-  var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"};
-  var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
-  var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true};
-  var _false = {keyword: "false", atomValue: false}, _in = {keyword: "in", binop: 7, beforeExpr: true};
-  var keywordTypes = {"break": _break, "case": _case, "catch": _catch,
-                      "continue": _continue, "debugger": _debugger, "default": _default,
-                      "do": _do, "else": _else, "finally": _finally, "for": _for,
-                      "function": _function, "if": _if, "return": _return, "switch": _switch,
-                      "throw": _throw, "try": _try, "var": _var, "while": _while, "with": _with,
-                      "null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
-                      "instanceof": {keyword: "instanceof", binop: 7},
-                      "typeof": {keyword: "typeof", prefix: true},
-                      "void": {keyword: "void", prefix: true},
-                      "delete": {keyword: "delete", prefix: true}};
-  var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in");
-
-  var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true};
-  var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"};
-  var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
-  var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true};
-
-  var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
-  var _assign = {isAssign: true, beforeExpr: true}, _plusmin = {binop: 9, prefix: true, beforeExpr: true};
-  var _incdec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
-  var _bin1 = {binop: 1, beforeExpr: true}, _bin2 = {binop: 2, beforeExpr: true};
-  var _bin3 = {binop: 3, beforeExpr: true}, _bin4 = {binop: 4, beforeExpr: true};
-  var _bin5 = {binop: 5, beforeExpr: true}, _bin6 = {binop: 6, beforeExpr: true};
-  var _bin7 = {binop: 7, beforeExpr: true}, _bin8 = {binop: 8, beforeExpr: true};
-  var _bin10 = {binop: 10, beforeExpr: true};
-
-  var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile");
-  var isReservedWord5 = makePredicate("class enum extends super const export import");
-
-  function isStrictBadIdWord(str) {
-    return str === "eval" || str === "arguments";
-  }
-  var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield");
+  // Test whether a given character is part of an identifier.
 
-  function makePredicate(words) {
-    words = words.split(" ");
-    var f = "(function(str){", cats = [];
-    out: for (var i = 0; i < words.length; ++i) {
-      for (var j = 0; j < cats.length; ++j)
-        if (cats[j][0].length == words[i].length) {
-          cats[j].push(words[i]);
-          continue out;
-        }
-      cats.push([words[i]]);
-    }
-    function compareTo(arr) {
-      if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";";
-      f += "switch(str){";
-      for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":";
-      f += "return true}return false;";
-    }
-    if (cats.length > 3) {
-      cats.sort(function(a, b) {return b.length - a.length;});
-      f += "switch(str.length){";
-      for (var i = 0; i < cats.length; ++i) {
-        var cat = cats[i];
-        f += "case " + cat[0].length + ":";
-        compareTo(cat);
-      }
-      f += "}";
-    } else {
-      compareTo(words);
-    }
-    return (1, eval)(f + "})");
+  function isIdentifierChar(ch) {
+    return ((ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") ||
+            (ch >= "0" && ch <= "9") || ch === "$" || ch === "_" ||
+            (ch >= "\xaa" && nonASCIIidentifier.test(ch)));
   }
 
-  // CHARACTER CATEGORIES
-
-  var operatorChar = /[+\-\*\/&%=<>!|~^]/;
-  var reModifiers = /^[gmsiy]*$/;
-
-  var nonASCIIwhitespace = /[\u1680\u180E\u2000-\u200A\u202F\u205F\u3000\uFEFF]/;
-  var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u [...]
-  var nonASCIIidentifierChars = "\u0371-\u0374\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a8 [...]
-  var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
-  var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
-  var newline = /[\n\r\u2028\u2029]/;
-  var lineBreak = /\r\n?|[\n\r\u2028\u2029]/g;
-
-  // TOKENIZER STATE
-
-  var tokStart, tokEnd, tokType, tokVal, tokCommentsBefore, tokCommentsAfter;
+  // ## Tokenizer
 
-  var tokPos, tokRegexpAllowed, tokComments;
-  var tokCurLine, tokLineStart, tokLineStartNext;
+  // These are used when `options.linePositions` is on, in order to
+  // track the current line number and start of line offset, so that
+  // `tokStart` and `tokEnd` refer to position objects indead of
+  // integer offsets.
 
   function nextLineStart() {
     lineBreak.lastIndex = tokLineStart;
@@ -742,6 +369,8 @@
     return {offset: tokPos, line: tokCurLine, column: tokPos - tokLineStart};
   }
 
+  // Reset the token state. Used at the start of a parse.
+
   function initTokenState() {
     tokCurLine = 1;
     tokPos = tokLineStart = 0;
@@ -751,6 +380,11 @@
     skipSpace();
   }
 
+  // Called at the end of every token. Sets `tokEnd`, `tokVal`,
+  // `tokCommentsAfter`, and `tokRegexpAllowed`, and skips the space
+  // after the token, so that the next one's `tokStart` will point at
+  // the right position.
+
   function finishToken(type, val) {
     tokEnd = options.linePositions ? curLinePos() : tokPos;
     tokType = type;
@@ -760,19 +394,7 @@
     tokRegexpAllowed = type.beforeExpr;
   }
 
-  var getLineInfo = exports.getLineInfo = function(input, pos) {
-    for (var line = 1, cur = 0;;) {
-      lineBreak.lastIndex = cur;
-      var match = lineBreak.exec(input);
-      if (match && match.index < pos) {
-        ++line;
-        cur = match.index + match[0].length;
-      } else break;
-    }
-    return {line: line, column: pos - cur};
-  };
-
-  function readBlockComment() {
+  function skipBlockComment() {
     var end = input.indexOf("*/", tokPos += 2);
     if (end === -1) raise(tokPos - 2, "Unterminated comment");
     if (options.trackComments)
@@ -780,7 +402,7 @@
     tokPos = end + 2;
   }
 
-  function readLineComment() {
+  function skipLineComment() {
     var start = tokPos;
     tokPos += 2;
     while (tokPos < inputLen && !newline.test(input.charAt(tokPos))) ++tokPos;
@@ -788,6 +410,10 @@
       (tokComments || (tokComments = [])).push(input.slice(start, tokPos));
   }
 
+  // Called at the start of the parse and after every token. Skips
+  // whitespace and comments, and, if `options.trackComments` is on,
+  // will store all skipped comments in `tokComments`.
+
   function skipSpace() {
     tokComments = null;
     while (tokPos < inputLen) {
@@ -795,9 +421,9 @@
       if (ch === "/") {
         var nextCh = input.charAt(tokPos+1);
         if (nextCh === "*") {
-          readBlockComment();
+          skipBlockComment();
         } else if (nextCh === "/") {
-          readLineComment();
+          skipLineComment();
         } else break;
       } else if (ch === " " || ch === '\t' || ch === "\n" || ch === "\r" || ch === "\f" ||
                  ch === "\xa0" || ch === "\x0b" || (ch >= "\u1680" && nonASCIIwhitespace.test(ch))) {
@@ -808,7 +434,17 @@
     }
   }
 
-  // TOKENIZER READING
+  // ### Token reading
+
+  // This is the function that is called to fetch the next token. It
+  // is somewhat obscure, because it works in character codes rather
+  // than characters, and because operator parsing has been inlined
+  // into it.
+  //
+  // All in the name of speed.
+  //
+  // The `forceRegexp` parameter is used in the one case where the
+  // `tokRegexpAllowed` trick does not work. See `parseStatement`.
 
   function readToken(forceRegexp) {
     tokStart = options.linePositions ? curLinePos() : tokPos;
@@ -816,14 +452,21 @@
     if (forceRegexp) return readRegexp();
     if (tokPos >= inputLen) return finishToken(_eof);
 
-    var code = input.charCodeAt(tokPos), next = input.charCodeAt(tokPos+1);
-    if (isIdentifierStart(code) || code === 92) return readWord();
+    var code = input.charCodeAt(tokPos);
+    // Identifier or keyword. '\uXXXX' sequences are allowed in
+    // identifiers, so '\' also dispatches to that.
+    if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
+    var next = input.charCodeAt(tokPos+1);
 
     switch(code) {
+      // The interpretation of a dot depends on whether it is followed
+      // by a digit.
     case 46: // '.'
       if (next >= 48 && next <= 57) return readNumber(String.fromCharCode(code));
       ++tokPos;
       return finishToken(_dot);
+
+      // Punctuation tokens.
     case 40: ++tokPos; return finishToken(_parenL);
     case 41: ++tokPos; return finishToken(_parenR);
     case 59: ++tokPos; return finishToken(_semi);
@@ -834,13 +477,23 @@
     case 125: ++tokPos; return finishToken(_braceR);
     case 58: ++tokPos; return finishToken(_colon);
     case 63: ++tokPos; return finishToken(_question);
-    case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 0-9
-      if (code === 48 && (next === 120 || next === 88)) return readHexNumber();
+
+      // '0x' is a hexadecimal number.
+    case 48: // '0'
+      if (next === 120 || next === 88) return readHexNumber();
+      // Anything else beginning with a digit is an integer, octal
+      // number, or float.
+    case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
       return readNumber(String.fromCharCode(code));
+
+      // Quotes produce strings.
     case 34: case 39: // '"', "'"
       return readString(String.fromCharCode(code));
 
-    // Operators
+    // Operators are parsed inline in tiny state machines. '=' (61) is
+    // often referred to. `finishOp` simply skips the amount of
+    // characters it is given as second argument, and returns a token
+    // of the type given by its first argument.
 
     case 47: // '/'
       if (tokRegexpAllowed) {++tokPos; return readRegexp();}
@@ -885,205 +538,810 @@
       return finishOp(_prefix, 1);
     }
 
-    var ch = String.fromCharCode(code);
-    if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
-    raise(tokPos, "Unexpected character '" + ch + "'");
-  }
+    // If we are here, we either found a non-ASCII identifier
+    // character, or something that's entirely disallowed.
+    var ch = String.fromCharCode(code);
+    if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
+    raise(tokPos, "Unexpected character '" + ch + "'");
+  }
+
+  function finishOp(type, size) {
+    var str = input.slice(tokPos, tokPos + size);
+    tokPos += size;
+    finishToken(type, str);
+  }
+
+  // Parse a regular expression. Some context-awareness is necessary,
+  // since a '/' inside a '[]' set does not end the expression.
+
+  function readRegexp() {
+    var content = "", escaped, inClass, start = tokPos;
+    for (;;) {
+      if (tokPos >= inputLen) raise(start, "Unterminated regular expression");
+      var ch = input.charAt(tokPos);
+      if (newline.test(ch)) raise(start, "Unterminated regular expression");
+      if (!escaped) {
+        if (ch === "[") inClass = true;
+        else if (ch === "]" && inClass) inClass = false;
+        else if (ch === "/" && !inClass) break;
+        escaped = ch === "\\";
+      } else escaped = false;
+      ++tokPos;
+    }
+    var content = input.slice(start, tokPos);
+    ++tokPos;
+    // Need to use `readWord1` because '\uXXXX' sequences are allowed
+    // here (don't ask).
+    var mods = readWord1();
+    if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag");
+    return finishToken(_regexp, new RegExp(content, mods));
+  }
+
+  // Read an integer in the given radix. Return null if zero digits
+  // were read, the integer value otherwise. When `len` is given, this
+  // will return `null` unless the integer has exactly `len` digits.
+
+  function readInt(radix, len) {
+    var start = tokPos, total = 0;
+    for (;;) {
+      var code = input.charCodeAt(tokPos), val;
+      if (code >= 97) val = code - 97 + 10; // a
+      else if (code >= 65) val = code - 65 + 10; // A
+      else if (code >= 48 && code <= 57) val = code - 48; // 0-9
+      else val = Infinity;
+      if (val >= radix) break;
+      ++tokPos;
+      total = total * radix + val;
+    }
+    if (tokPos === start || len != null && tokPos - start !== len) return null;
+
+    return total;
+  }
+
+  function readHexNumber() {
+    tokPos += 2; // 0x
+    var val = readInt(16);
+    if (val == null) raise(tokStart + 2, "Expected hexadecimal number");
+    if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
+    return finishToken(_num, val);
+  }
+
+  // Read an integer, octal integer, or floating-point number.
+  
+  function readNumber(ch) {
+    var start = tokPos, isFloat = ch === ".";
+    if (!isFloat && readInt(10) == null) raise(start, "Invalid number");
+    if (isFloat || input.charAt(tokPos) === ".") {
+      var next = input.charAt(++tokPos);
+      if (next === "-" || next === "+") ++tokPos;
+      if (readInt(10) === null) raise(start, "Invalid number");
+      isFloat = true;
+    }
+    if (/e/i.test(input.charAt(tokPos))) {
+      var next = input.charAt(++tokPos);
+      if (next === "-" || next === "+") ++tokPos;
+      if (readInt(10) === null) raise(start, "Invalid number")
+      isFloat = true;
+    }
+    if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
+
+    var str = input.slice(start, tokPos), val;
+    if (isFloat) val = parseFloat(str);
+    else if (ch !== "0" || str.length === 1) val = parseInt(str, 10);
+    else if (/[89]/.test(str) || strict) raise(start, "Invalid number");
+    else val = parseInt(str, 8);
+    return finishToken(_num, val);
+  }
+
+  // Read a string value, interpreting backslash-escapes.
+
+  function readString(quote) {
+    tokPos++;
+    var str = "";
+    for (;;) {
+      if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant");
+      var ch = input.charAt(tokPos);
+      if (ch === quote) {
+        ++tokPos;
+        return finishToken(_string, str);
+      }
+      if (ch === "\\") {
+        ch = input.charAt(++tokPos);
+        var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
+        if (octal) octal = octal[0];
+        while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, octal.length - 1);
+        ++tokPos;
+        if (octal) {
+          if (strict) raise(tokPos - 2, "Octal literal in strict mode");
+          str += String.fromCharCode(parseInt(octal, 8));
+          tokPos += octal.length - 1;
+        } else if (ch === "x") {
+          str += readHexChar(2);
+        } else if (ch === "u") {
+          str += readHexChar(4);
+        } else if (ch === "U") {
+          str += readHexChar(8);
+        } else {
+          switch (ch) {
+          case "n" : str += "\n"; break;
+          case "r" : str += "\r"; break;
+          case "t" : str += "\t"; break;
+          case "b" : str += "\b"; break;
+          case "v" : str += "\u000b"; break;
+          case "f" : str += "\f"; break;
+          case "0" : str += "\0"; break;
+          case "\r": if (input.charAt(tokPos) === "\n") ++tokPos;
+          case "\n": break;
+          default: str += ch; break;
+          }
+        }
+      } else {
+        if (newline.test(ch)) raise(tokStart, "Unterminated string constant");
+        if (ch !== "\\") str += ch;
+        ++tokPos;
+      }
+    }
+  }
+
+  // Used to read character escape sequences ('\x', '\u', '\U').
+
+  function readHexChar(len) {
+    var n = readInt(16, len);
+    if (n === null) raise(tokStart, "Bad character escape sequence");
+    return String.fromCharCode(n);
+  }
+
+  // Used to signal to callers of `readWord1` whether the word
+  // contained any escape sequences. This is needed because words with
+  // escape sequences must not be interpreted as keywords.
+
+  var containsEsc;
+
+  // Read an identifier, and return it as a string. Sets `containsEsc`
+  // to whether the word contained a '\u' escape.
+  //
+  // Only builds up the word character-by-character when it actually
+  // containeds an escape, as a micro-optimization.
+
+  function readWord1() {
+    containsEsc = false;
+    var word, first = true, start = tokPos;
+    for (;;) {
+      var ch = input.charAt(tokPos);
+      if (isIdentifierChar(ch)) {
+        if (containsEsc) word += ch;
+        ++tokPos;
+      } else if (ch === "\\") {
+        if (!containsEsc) word = input.slice(start, tokPos);
+        containsEsc = true;
+        if (input.charAt(++tokPos) != "u")
+          raise(tokPos, "Expecting Unicode escape sequence \\uXXXX");
+        ++tokPos;
+        var esc = readHexChar(4);
+        if (!esc) raise(tokPos - 1, "Invalid Unicode escape");
+        if (!(first ? isIdentifierStart(esc.charCodeAt(0)) : isIdentifierChar(esc)))
+          raise(tokPos - 4, "Invalid Unicode escape");
+        word += esc;
+      } else {
+        break;
+      }
+      first = false;
+    }
+    return containsEsc ? word : input.slice(start, tokPos);
+  }
+
+  // Read an identifier or keyword token. Will check for reserved
+  // words when necessary.
+
+  function readWord() {
+    var word = readWord1();
+    var type = _name;
+    if (!containsEsc) {
+      if (isKeyword(word)) type = keywordTypes[word];
+      else if (options.forbidReserved &&
+               (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(word) ||
+               strict && isStrictReservedWord(word))
+        raise(tokStart, "The keyword '" + word + "' is reserved");
+    }
+    return finishToken(type, word);
+  }
+
+  // ## Parser
+
+  function next() {
+    lastStart = tokStart;
+    lastEnd = tokEnd;
+    readToken();
+  }
+
+  function setStrict(strct) {
+    strict = strct;
+    tokPos = options.linePositions ? lastEnd.offset : lastEnd;
+    skipSpace();
+    readToken();
+  }
+
+  function startNode() {
+    var node = {type: null, start: tokStart};
+    if (options.trackComments && tokCommentsBefore) {
+      node.commentsBefore = tokCommentsBefore;
+      tokCommentsBefore = null;
+    }
+    return node;
+  }
+
+  function startNodeFrom(other) {
+    var node = {type: null, start: other.start};
+    if (other.commentsBefore) {
+      node.commentsBefore = other.commentsBefore;
+      other.commentsBefore = null;
+    }
+    return node;
+  }
+
+  function finishNode(node, type) {
+    if (type != null) node.type = type;
+    node.end = lastEnd;
+    if (options.trackComments && tokCommentsAfter)
+      node.commentsAfter = tokCommentsAfter;
+    return node;
+  }
+
+  function parseTopLevel() {
+    initTokenState();
+    lastStart = lastEnd = options.linePositions ? curLinePos() : tokPos;
+    inFunction = strict = null;
+    labels = [];
+    readToken();
+
+    var node = startNode(), first = true;
+    node.body = [];
+    while (tokType !== _eof) {
+      var stmt = parseStatement();
+      node.body.push(stmt);
+      if (first && isUseStrict(stmt)) setStrict(true);
+      first = false;
+    }
+    return finishNode(node, "Program");
+  };
+
+  function isUseStrict(stmt) {
+    return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" &&
+      stmt.expression.type === "Literal" && stmt.expression.value === "use strict";
+  }
+
+  function eat(type) {
+    if (tokType === type) {
+      next();
+      return true;
+    }
+  }
+
+  function canInsertSemicolon() {
+    return tokType === _eof || tokType === _braceR ||
+      !options.strictSemicolons &&
+      newline.test(options.linePositions ? input.slice(lastEnd.offset, tokStart.offset)
+                                         : input.slice(lastEnd, tokStart));
+  }
+
+  function semicolon() {
+    if (!eat(_semi) && !canInsertSemicolon()) unexpected();
+  }
+
+  function expect(type) {
+    if (tokType === type) next();
+    else unexpected();
+  }
+
+  function unexpected() {
+    raise(tokStart, "Unexpected token");
+  }
+
+  function parseStatement() {
+    // if expecting a statement and found a slash as operator,
+    // it must be a literal regexp.
+    if (tokType === _slash)
+      readToken(true);
+
+    var starttype = tokType, node = startNode();
+
+    switch (starttype) {
+    case _break: case _continue:
+      next();
+      var isBreak = starttype === _break;
+      if (eat(_semi) || canInsertSemicolon()) node.label = null;
+      else if (tokType !== _name) unexpected();
+      else {
+        node.label = parseIdent();
+        semicolon();
+      }
+      for (var i = 0; i < labels.length; ++i) {
+        var lab = labels[i];
+        if ((node.label == null || lab.name === node.label.name) &&
+            lab.kind != null && (isBreak || lab.kind === "loop")) break;
+      }
+      if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword);
+      return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
+
+    case _debugger:
+      next();
+      return finishNode(node, "DebuggerStatement");
+
+    case _do:
+      next();
+      labels.push({kind: "loop"});
+      node.body = parseStatement();
+      labels.pop();
+      expect(_while);
+      node.test = parseParenExpression();
+      return finishNode(node, "DoWhileStatement");
+
+    case _for:
+      next();
+      expect(_parenL);
+      if (tokType === _semi) return parseFor(node, null);
+      if (tokType === _var) {
+        var init = startNode();
+        next();
+        parseVar(init, true);
+        if (init.declarations.length === 1 && eat(_in))
+          return parseForIn(node, init);
+        return parseFor(node, init);
+      }
+      var init = parseExpression(false, true);
+      if (eat(_in)) {checkLVal(init); return parseForIn(node, init);}
+      return parseFor(node, init);
+
+    case _function:
+      next();
+      return parseFunction(node, true);
+
+    case _if:
+      next();
+      node.test = parseParenExpression();
+      node.consequent = parseStatement();
+      node.alternate = eat(_else) ? parseStatement() : null;
+      return finishNode(node, "IfStatement");
+
+    case _return:
+      if (!inFunction) raise(tokStart, "'return' outside of function");
+      next();
+      if (eat(_semi) || canInsertSemicolon()) node.argument = null;
+      else { node.argument = parseExpression(); semicolon(); }
+      return finishNode(node, "ReturnStatement");
+
+    case _switch:
+      next();
+      node.discriminant = parseParenExpression();
+      node.cases = [];
+      expect(_braceL);
+      labels.push({kind: "switch"});
+      for (var cur, sawDefault; !eat(_braceR);) {
+        if (tokType === _case || tokType === _default) {
+          var isCase = tokType === _case;
+          if (cur) finishNode(cur, "SwitchCase");
+          node.cases.push(cur = startNode());
+          cur.consequent = [];
+          next();
+          if (isCase) cur.test = parseExpression();
+          else {
+            if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true;
+            cur.test = null;
+          }
+          expect(_colon);
+        } else {
+          if (!cur) unexpected();
+          cur.consequent.push(parseStatement());
+        }
+      }
+      if (cur) finishNode(cur, "SwitchCase");
+      labels.pop();
+      return finishNode(node, "SwitchStatement");
+
+    case _throw:
+      next();
+      node.argument = parseExpression();
+      return finishNode(node, "ThrowStatement");
+
+    case _try:
+      next();
+      node.block = parseBlock();
+      node.handlers = [];
+      while (tokType === _catch) {
+        var clause = startNode();
+        next();
+        expect(_parenL);
+        clause.param = parseIdent();
+        if (strict && isStrictBadIdWord(clause.param.name))
+          raise(clause.param.start, "Binding " + clause.param.name + " in strict mode");
+        expect(_parenR);
+        clause.guard = null;
+        clause.body = parseBlock();
+        node.handlers.push(finishNode(clause, "CatchClause"));
+      }
+      node.finalizer = eat(_finally) ? parseBlock() : null;
+      if (!node.handlers.length && !node.finalizer)
+        raise(node.start, "Missing catch or finally clause");
+      return finishNode(node, "TryStatement");
+
+    case _var:
+      next();
+      node = parseVar(node);
+      semicolon();
+      return node;
+
+    case _while:
+      next();
+      node.test = parseParenExpression();
+      labels.push({kind: "loop"});
+      node.body = parseStatement();
+      labels.pop();
+      return finishNode(node, "WhileStatement");
+
+    case _with:
+      if (strict) raise(tokStart, "'with' in strict mode");
+      next();
+      node.object = parseParenExpression();
+      node.body = parseStatement();
+      return finishNode(node, "WithStatement");
+
+    case _braceL:
+      return parseBlock();
+
+    case _semi:
+      next();
+      return finishNode(node, "EmptyStatement");
+
+    default:
+      var maybeName = tokVal, expr = parseExpression();
+      if (starttype === _name && expr.type === "Identifier" && eat(_colon)) {
+        for (var i = 0; i < labels.length; ++i)
+          if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared");
+        var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null;
+        labels.push({name: maybeName, kind: kind});
+        node.body = parseStatement();
+        node.label = expr;
+        return finishNode(node, "LabeledStatement");
+      } else {
+        node.expression = expr;
+        semicolon();
+        return finishNode(node, "ExpressionStatement");
+      }
+    }
+  }
+
+  function parseParenExpression() {
+    expect(_parenL);
+    var val = parseExpression();
+    expect(_parenR);
+    return val;
+  }
+
+  function parseBlock(allowStrict) {
+    var node = startNode(), first = true, strict = false, oldStrict;
+    node.body = [];
+    expect(_braceL);
+    while (!eat(_braceR)) {
+      var stmt = parseStatement();
+      node.body.push(stmt);
+      if (first && isUseStrict(stmt)) {
+        oldStrict = strict;
+        setStrict(strict = true);
+      }
+      first = false
+    }
+    if (strict && !oldStrict) setStrict(false);
+    return finishNode(node, "BlockStatement");
+  }
+
+  function parseFor(node, init) {
+    node.init = init;
+    expect(_semi);
+    node.test = tokType === _semi ? null : parseExpression();
+    expect(_semi);
+    node.update = tokType === _parenR ? null : parseExpression();
+    expect(_parenR);
+    labels.push({kind: "loop"});
+    node.body = parseStatement();
+    labels.pop();
+    return finishNode(node, "ForStatement");
+  }
+
+  function parseForIn(node, init) {
+    node.left = init;
+    node.right = parseExpression();
+    expect(_parenR);
+    labels.push({kind: "loop"});
+    node.body = parseStatement();
+    labels.pop();
+    return finishNode(node, "ForInStatement");
+  }
+
+  function parseVar(node, noIn) {
+    node.declarations = [];
+    node.kind = "var";
+    for (;;) {
+      var decl = startNode();
+      decl.id = parseIdent();
+      if (strict && isStrictBadIdWord(decl.id.name))
+        raise(decl.id.start, "Binding " + decl.id.name + " in strict mode");
+      decl.init = eat(_eq) ? parseExpression(true, noIn) : null;
+      node.declarations.push(finishNode(decl, "VariableDeclarator"));
+      if (!eat(_comma)) break;
+    }
+    return finishNode(node, "VariableDeclaration");
+  }
+
+  function parsePropertyName() {
+    if (tokType === _num || tokType === _string) return parseExprAtom();
+    return parseIdent(true);
+  }
+
+  function parseIdent(liberal) {
+    var node = startNode();
+    if (tokType !== _name) {
+      if (liberal && tokType.keyword) node.name = tokType.keyword;
+      else unexpected();
+    } else node.name = tokVal;
+    next();
+    return finishNode(node, "Identifier");
+  }
+
+  function parseFunction(node, isStatement) {
+    if (tokType === _name) node.id = parseIdent();
+    else if (isStatement) unexpected();
+    else node.id = null;
+    node.params = [];
+    var first = true;
+    expect(_parenL);
+    while (!eat(_parenR)) {
+      if (!first) expect(_comma); else first = false;
+      node.params.push(parseIdent());
+    }
+
+    var oldInFunc = inFunction, oldLabels = labels;
+    inFunction = true; labels = [];
+    node.body = parseBlock(true);
+    if (strict || node.body.body.length && isUseStrict(node.body.body[0])) {
+      for (var i = node.id ? -1 : 0; i < node.params.length; ++i) {
+        var id = i < 0 ? node.id : node.params[i];
+        if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name))
+          raise(id.start, "Defining '" + id.name + "' in strict mode");
+        if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name)
+          raise(id.start, "Argument name clash in strict mode");
+      }
+    }
+
+    inFunction = oldInFunc; labels = oldLabels;
+    return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
+  }
+
+  // EXPRESSION PARSING
+
+  function parseExprList(close, allowTrailingComma, allowEmpty) {
+    var elts = [], first = true;
+    while (!eat(close)) {
+      if (!first) {
+        expect(_comma);
+        if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
+      } else first = false;
+
+      if (allowEmpty && tokType === _comma) elts.push(null);
+      else elts.push(parseExpression(true));
+    }
+    return elts;
+  }
+
+  function parseNew() {
+    var node = startNode();
+    next();
+    node.callee = parseSubscripts(parseExprAtom(false), false);
+    if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
+    else node.arguments = [];
+    return finishNode(node, "NewExpression");
+  }
+
+  function parseObj() {
+    var node = startNode(), first = true;
+    node.properties = [];
+    next();
+    while (!eat(_braceR)) {
+      if (!first) {
+        expect(_comma);
+        if (options.allowTrailingCommas && eat(_braceR)) break;
+      } else first = false;
+
+      var prop = {key: parsePropertyName()}, isGetSet, kind;
+      if (eat(_colon)) {
+        prop.value = parseExpression(true);
+        kind = prop.kind = "init";
+      } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
+                 (prop.key.name === "get" || prop.key.name === "set")) {
+        isGetSet = true;
+        kind = prop.kind = prop.key.name;
+        prop.key = parsePropertyName();
+        if (!tokType === _parenL) unexpected();
+        prop.value = parseFunction(startNode(), false);
+      } else unexpected();
+      if (prop.key.type === "Identifier" && (strict || isGetSet)) {
+        for (var i = 0; i < node.properties.length; ++i) {
+          var other = node.properties[i];
+          if (other.key.name === prop.key.name) {
+            var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
+              kind === "init" && (other.kind === "get" || other.kind === "set");
+            if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
+            if (conflict) raise(prop.key.start, "Redefinition of property");
+          }
+        }
+      }
+      node.properties.push(prop);
+    }
+    return finishNode(node, "ObjectExpression");
+  }
+
+  function parseExprAtom() {
+    switch (tokType) {
+    case _name:
+      if (tokVal === "this") {
+        var node = startNode();
+        next();
+        return finishNode(node, "ThisExpression");
+      } else return parseIdent();
+
+    case _num: case _string: case _regexp:
+      var node = startNode();
+      node.value = tokVal;
+      next();
+      return finishNode(node, "Literal");
+
+    case _null: case _true: case _false:
+      var node = startNode();
+      node.value = tokType.atomValue;
+      next();
+      return finishNode(node, "Literal");
+
+    case _parenL:
+      next();
+      var val = parseExpression();
+      expect(_parenR);
+      return val;
+
+    case _bracketL:
+      var node = startNode();
+      next();
+      node.elements = parseExprList(_bracketR, true, true);
+      return finishNode(node, "ArrayExpression");
+
+    case _braceL:
+      return parseObj();
+
+    case _function:
+      var node = startNode();
+      next();
+      return parseFunction(node, false);
 
-  function finishOp(type, size) {
-    var str = input.slice(tokPos, tokPos + size);
-    tokPos += size;
-    finishToken(type, str);
-  }
+    case _new:
+      return parseNew();
 
-  function readRegexp() {
-    var content = "", escaped, inClass, start = tokPos;
-    for (;;) {
-      if (tokPos >= inputLen) raise(start, "Unterminated regular expression");
-      var ch = input.charAt(tokPos);
-      if (newline.test(ch)) raise(start, "Unterminated regular expression");
-      if (!escaped) {
-        if (ch === "[") inClass = true;
-        else if (ch === "]" && inClass) inClass = false;
-        else if (ch === "/" && !inClass) break;
-        escaped = ch === "\\";
-      } else escaped = false;
-      ++tokPos;
+    default:
+      unexpected();
     }
-    var content = input.slice(start, tokPos);
-    ++tokPos;
-    var mods = readWord1();
-    if (!reModifiers.test(mods)) raise(start, "Invalid regexp flag");
-    return finishToken(_regexp, new RegExp(content, mods));
   }
 
-  function readInt(radix, len) {
-    var start = tokPos, total = 0;
-    for (;;) {
-      var code = input.charCodeAt(tokPos), val;
-      if (code >= 97) val = code - 97 + 10; // a
-      else if (code >= 65) val = code - 65 + 10; // A
-      else if (code >= 48 && code <= 57) val = code - 48; // 0-9
-      else val = Infinity;
-      if (val >= radix) break;
-      ++tokPos;
-      total = total * radix + val;
-    }
-    if (tokPos === start || len != null && tokPos - start !== len) return null;
-
-    return total;
+  function parseSubscripts(base, allowCalls) {
+    if (eat(_dot)) {
+      var node = startNodeFrom(base);
+      node.object = base;
+      node.property = parseIdent(true);
+      node.computed = false;
+      return parseSubscripts(finishNode(node, "MemberExpression"), allowCalls);
+    } else if (eat(_bracketL)) {
+      var node = startNodeFrom(base);
+      node.object = base;
+      node.property = parseExpression();
+      node.computed = true;
+      expect(_bracketR);
+      return parseSubscripts(finishNode(node, "MemberExpression"), allowCalls);
+    } else if (allowCalls && eat(_parenL)) {
+      var node = startNodeFrom(base);
+      node.callee = base;
+      node.arguments = parseExprList(_parenR, false);
+      return parseSubscripts(finishNode(node, "CallExpression"), allowCalls);
+    } else return base;
   }
 
-  function readHexNumber() {
-    tokPos += 2; // 0x
-    var val = readInt(16);
-    if (val == null) raise(tokStart + 2, "Expected hexadecimal number");
-    if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
-    return finishToken(_num, val);
+  function parseExprSubscripts(allowCalls) {
+    return parseSubscripts(parseExprAtom(allowCalls), allowCalls);
   }
 
-  function readNumber(ch) {
-    var start = tokPos, isFloat = ch === ".";
-    if (!isFloat && readInt(10) == null) raise(start, "Invalid number");
-    if (isFloat || input.charAt(tokPos) === ".") {
-      var next = input.charAt(++tokPos);
-      if (next === "-" || next === "+") ++tokPos;
-      if (readInt(10) === null) raise(start, "Invalid number");
-      isFloat = true;
+  function parseMaybeUnary(noIn) {
+    if (tokType.prefix) {
+      var node = startNode(), update = tokType.isUpdate;
+      node.operator = tokVal;
+      node.prefix = true;
+      next();
+      node.argument = parseMaybeUnary(noIn);
+      if (update) checkLVal(node.argument);
+      else if (strict && node.operator === "delete" &&
+               node.argument.type === "Identifier")
+        raise(node.start, "Deleting local variable in strict mode");
+      return finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
     }
-    if (/e/i.test(input.charAt(tokPos))) {
-      var next = input.charAt(++tokPos);
-      if (next === "-" || next === "+") ++tokPos;
-      if (readInt(10) === null) raise(start, "Invalid number")
-      isFloat = true;
+    var expr = parseExprSubscripts(true);
+    while (tokType.postfix && !canInsertSemicolon()) {
+      var node = startNodeFrom(expr);
+      node.operator = tokVal;
+      node.prefix = false;
+      node.argument = expr;
+      checkLVal(expr);
+      next();
+      expr = finishNode(node, "UpdateExpression");
     }
-    if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
-
-    var str = input.slice(start, tokPos), val;
-    if (isFloat) val = parseFloat(str);
-    else if (ch !== "0" || str.length === 1) val = parseInt(str, 10);
-    else if (/[89]/.test(str) || strict) raise(start, "Invalid number");
-    else val = parseInt(str, 8);
-    return finishToken(_num, val);
+    return expr;
   }
 
-  function readString(quote) {
-    tokPos++;
-    var escaped, str = "";
-    for (;;) {
-      if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant");
-      var ch = input.charAt(tokPos);
-      if (ch === quote && !escaped) {
-        ++tokPos;
-        return finishToken(_string, str);
-      }
-      if (escaped) {
-        var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
-        if (octal) octal = octal[0];
-        while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, octal.length - 1);
-        if (octal) {
-          if (strict) raise(tokPos - 1, "Octal literal in strict mode");
-          str += String.fromCharCode(parseInt(octal, 8));
-          tokPos += octal.length;
-        } else if (ch === "x") {
-          ++tokPos;
-          str += readHexChar(2);
-        } else if (ch === "u") {
-          ++tokPos;
-          str += readHexChar(4);
-        } else if (ch === "U") {
-          ++tokPos;
-          str += readHexChar(8);
-        } else {
-          ++tokPos;
-          switch (ch) {
-          case "n" : str += "\n"; break;
-          case "r" : str += "\r"; break;
-          case "t" : str += "\t"; break;
-          case "b" : str += "\b"; break;
-          case "v" : str += "\u000b"; break;
-          case "f" : str += "\f"; break;
-          case "0" : str += "\0"; break;
-          case "\r": if (input.charAt(tokPos) === "\n") ++tokPos;
-          case "\n": break;
-          default: str += ch; break;
-          }
-        }
-        escaped = false;
-      } else {
-        if (newline.test(ch)) raise(tokStart, "Unterminated string constant");
-        if (ch !== "\\") str += ch;
-        ++tokPos;
-        escaped = ch === "\\";
+  function parseExprOp(left, minPrec, noIn) {
+    var prec = tokType.binop;
+    if (prec != null && (!noIn || tokType !== _in)) {
+      if (prec > minPrec) {
+        var node = startNodeFrom(left);
+        node.left = left;
+        node.operator = tokVal;
+        next();
+        node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
+        var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
+        return parseExprOp(node, minPrec, noIn);
       }
     }
+    return left;
   }
 
-  function readHexChar(len) {
-    var aa = tokPos;
-    var n = readInt(16, len);
-    if (n === null) raise(tokStart, "Bad character escape sequence");
-    return String.fromCharCode(n);
-  }
-
-  function isIdentifierStart(code) {
-    return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) ||
-      code === 36 || code === 95 ||
-      (code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)));
+  function parseExprOps(noIn) {
+    return parseExprOp(parseMaybeUnary(noIn), -1, noIn);
   }
 
-  function isIdentifierChar(ch) {
-    return ((ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") ||
-            (ch >= "0" && ch <= "9") || ch === "$" || ch === "_" ||
-            (ch >= "\xaa" && nonASCIIidentifier.test(ch)));
+  function parseMaybeConditional(noIn) {
+    var expr = parseExprOps(noIn);
+    if (eat(_question)) {
+      var node = startNodeFrom(expr);
+      node.test = expr;
+      node.consequent = parseExpression(true);
+      expect(_colon);
+      node.alternate = parseExpression(true, noIn);
+      return finishNode(node, "ConditionalExpression");
+    }
+    return expr;
   }
 
-  var containsEsc;
-  function readWord1() {
-    containsEsc = false;
-    var word = "", first = true;
-    for (;;) {
-      var ch = input.charAt(tokPos);
-      if ((ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") ||
-          (ch >= "0" && ch <= "9") || ch === "$" || ch === "_" ||
-          (ch >= "\xaa" && nonASCIIidentifier.test(ch))) {
-        word += ch;
-        ++tokPos;
-      } else if (ch === "\\") {
-        containsEsc = true;
-        if (input.charAt(++tokPos) != "u")
-          raise(tokPos, "Expecting Unicode escape sequence \\uXXXX");
-        ++tokPos;
-        var esc = readHexChar(4);
-        if (!esc) raise(tokPos - 1, "Invalid Unicode escape");
-        if (!(first ? isIdentifierStart(esc.charCodeAt(0)) : isIdentifierChar(esc)))
-          raise(tokPos - 4, "Invalid Unicode escape");
-        word += esc;
-      } else {
-        break;
-      }
-      first = false;
+  function parseMaybeAssign(noIn) {
+    var left = parseMaybeConditional(noIn);
+    if (tokType.isAssign) {
+      var node = startNodeFrom(left);
+      node.operator = tokVal;
+      node.left = left;
+      next();
+      node.right = parseMaybeAssign(noIn);
+      checkLVal(left);
+      return finishNode(node, "AssignmentExpression");
     }
-    return word;
+    return left;
   }
 
-  function readWord() {
-    var word = readWord1();
-    var type = _name;
-    if (!containsEsc) {
-      if (isKeyword(word)) type = keywordTypes[word];
-      else if (options.forbidReserved &&
-               (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(word) ||
-               strict && isStrictReservedWord(word))
-        raise(tokStart, "The keyword '" + word + "' is reserved");
+  function parseExpression(noComma, noIn) {
+    var expr = parseMaybeAssign(noIn);
+    if (!noComma && tokType === _comma) {
+      var node = startNodeFrom(expr);
+      node.expressions = [expr];
+      while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
+      return finishNode(node, "SequenceExpression");
     }
-    return finishToken(type, word);
+    return expr;
   }
 
-  function raise(pos, message) {
-    if (typeof pos == "number") pos = getLineInfo(input, pos);
-    message += " (" + pos.line + ":" + pos.column + ")";
-    throw new SyntaxError(message);
+  function checkLVal(expr) {
+    if (expr.type !== "Identifier" && expr.type !== "MemberExpression")
+      raise(expr.start, "Assigning to rvalue");
+    if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name))
+      raise(expr.start, "Assigning to " + expr.name + " in strict mode");
   }
-
 })(typeof exports === "undefined" ? (window.acorn = {}) : exports);

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-acorn-jsx.git



More information about the Pkg-javascript-commits mailing list