/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this,
  "Reflect", "resource://gre/modules/reflect.jsm");

this.EXPORTED_SYMBOLS = ["Parser"];

/**
 * A JS parser using the reflection API.
 */
this.Parser = function Parser() {
  this._cache = new Map();
};

Parser.prototype = {
  /**
   * Gets a collection of parser methods for a specified source.
   *
   * @param string aUrl [optional]
   *        The source url. The AST nodes will be cached, so you can use this
   *        identifier to avoid parsing the whole source again.
   * @param string aSource
   *        The source text content.
   */
  get: function P_get(aUrl, aSource) {
    // Try to use the cached AST nodes, to avoid useless parsing operations.
    if (this._cache.has(aUrl)) {
      return this._cache.get(aUrl);
    }

    // The source may not necessarily be JS, in which case we need to extract
    // all the scripts. Fastest/easiest way is with a regular expression.
    // Don't worry, the rules of using a <script> tag are really strict,
    // this will work.
    let regexp = /<script[^>]*>([^]*?)<\/script\s*>/gim;
    let syntaxTrees = [];
    let scriptMatches = [];
    let scriptMatch;

    if (aSource.match(/^\s*</)) {
      // First non whitespace character is &lt, so most definitely HTML.
      while (scriptMatch = regexp.exec(aSource)) {
        scriptMatches.push(scriptMatch[1]); // Contents are captured at index 1.
      }
    }

    // If there are no script matches, send the whole source directly to the
    // reflection API to generate the AST nodes.
    if (!scriptMatches.length) {
      // Reflect.parse throws when encounters a syntax error.
      try {
        let nodes = Reflect.parse(aSource);
        let length = aSource.length;
        syntaxTrees.push(new SyntaxTree(nodes, aUrl, length));
      } catch (e) {
        log(aUrl, e);
      }
    }
    // Generate the AST nodes for each script.
    else {
      for (let script of scriptMatches) {
        // Reflect.parse throws when encounters a syntax error.
        try {
          let nodes = Reflect.parse(script);
          let offset = aSource.indexOf(script);
          let length = script.length;
          syntaxTrees.push(new SyntaxTree(nodes, aUrl, length, offset));
        } catch (e) {
          log(aUrl, e);
        }
      }
    }

    let pool = new SyntaxTreesPool(syntaxTrees);
    this._cache.set(aUrl, pool);
    return pool;
  },

  /**
   * Clears all the parsed sources from cache.
   */
  clearCache: function P_clearCache() {
    this._cache.clear();
  },

  _cache: null
};

/**
 * A pool handling a collection of AST nodes generated by the reflection API.
 *
 * @param object aSyntaxTrees
 *        A collection of AST nodes generated for a source.
 */
function SyntaxTreesPool(aSyntaxTrees) {
  this._trees = aSyntaxTrees;
  this._cache = new Map();
}

SyntaxTreesPool.prototype = {
  /**
   * @see SyntaxTree.prototype.getNamedFunctionDefinitions
   */
  getNamedFunctionDefinitions: function STP_getNamedFunctionDefinitions(aSubstring) {
    return this._call("getNamedFunctionDefinitions", aSubstring);
  },

  /**
   * @see SyntaxTree.prototype.getFunctionAtLocation
   */
  getFunctionAtLocation: function STP_getFunctionAtLocation(aLine, aColumn) {
    return this._call("getFunctionAtLocation", [aLine, aColumn]);
  },

  /**
   * Finds the offset and length of the script containing the specified offset
   * relative to its parent source.
   *
   * @param number aOffset
   *        The offset relative to the parent source.
   * @return array
   *         The offset and length relative to the enclosing script.
   */
  getScriptInfo: function STP_getScriptInfo(aOffset) {
    for (let { offset, length } of this._trees) {
      if (offset <= aOffset &&  offset + length >= aOffset) {
        return [offset, length];
      }
    }
    return [-1, -1];
  },

  /**
   * Handles a request for all known syntax trees.
   *
   * @param string aFunction
   *        The function name to call on the SyntaxTree instances.
   * @param any aParams
   *        Any kind params to pass to the request function.
   * @return array
   *         The results given by all known syntax trees.
   */
  _call: function STP__call(aFunction, aParams) {
    let results = [];
    let requestId = aFunction + aParams; // Cache all the things!

    if (this._cache.has(requestId)) {
      return this._cache.get(requestId);
    }
    for (let syntaxTree of this._trees) {
      try {
        results.push({
          sourceUrl: syntaxTree.url,
          scriptLength: syntaxTree.length,
          scriptOffset: syntaxTree.offset,
          parseResults: syntaxTree[aFunction](aParams)
        });
      } catch (e) {
        // Can't guarantee that the tree traversal logic is forever perfect :)
        // Language features may be added, in which case the recursive methods
        // need to be updated. If an exception is thrown here, file a bug.
        log("syntax tree", e);
      }
    }
    this._cache.set(requestId, results);
    return results;
  },

  _trees: null,
  _cache: null
};

/**
 * A collection of AST nodes generated by the reflection API.
 *
 * @param object aNodes
 *        The AST nodes.
 * @param string aUrl
 *        The source url.
 * @param number aLength
 *        The total number of chars of the parsed script in the parent source.
 * @param number aOffset [optional]
 *        The char offset of the parsed script in the parent source.
 */
function SyntaxTree(aNodes, aUrl, aLength, aOffset = 0) {
  this.AST = aNodes;
  this.url = aUrl;
  this.length = aLength;
  this.offset = aOffset;
};

SyntaxTree.prototype = {
  /**
   * Searches for all function definitions (declarations and expressions)
   * whose names (or inferred names) contain a string.
   *
   * @param string aSubstring
   *        The string to be contained in the function name (or inferred name).
   *        Can be an empty string to match all functions.
   * @return array
   *         All the matching function declarations and expressions, as
   *         { functionName, functionLocation ... } object hashes.
   */
  getNamedFunctionDefinitions: function ST_getNamedFunctionDefinitions(aSubstring) {
    let lowerCaseToken = aSubstring.toLowerCase();
    let store = [];

    SyntaxTreeVisitor.walk(this.AST, {
      /**
       * Callback invoked for each function declaration node.
       * @param Node aNode
       */
      onFunctionDeclaration: function STW_onFunctionDeclaration(aNode) {
        let functionName = aNode.id.name;
        if (functionName.toLowerCase().contains(lowerCaseToken)) {
          store.push({
            functionName: functionName,
            functionLocation: aNode.loc
          });
        }
      },

      /**
       * Callback invoked for each function expression node.
       * @param Node aNode
       */
      onFunctionExpression: function STW_onFunctionExpression(aNode) {
        let parent = aNode._parent;
        let functionName, inferredName, inferredChain, inferredLocation;

        // Function expressions don't necessarily have a name.
        if (aNode.id) {
          functionName = aNode.id.name;
        }
        // Infer the function's name from an enclosing syntax tree node.
        if (parent) {
          let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
          inferredName = inferredInfo.name;
          inferredChain = inferredInfo.chain;
          inferredLocation = inferredInfo.loc;
        }
        // Current node may be part of a larger assignment expression stack.
        if (parent.type == "AssignmentExpression") {
          this.onFunctionExpression(parent);
        }

        if ((functionName && functionName.toLowerCase().contains(lowerCaseToken)) ||
            (inferredName && inferredName.toLowerCase().contains(lowerCaseToken))) {
          store.push({
            functionName: functionName,
            functionLocation: aNode.loc,
            inferredName: inferredName,
            inferredChain: inferredChain,
            inferredLocation: inferredLocation
          });
        }
      },

      /**
       * Callback invoked for each arrow expression node.
       * @param Node aNode
       */
      onArrowExpression: function STW_onArrowExpression(aNode) {
        let parent = aNode._parent;
        let inferredName, inferredChain, inferredLocation;

        // Infer the function's name from an enclosing syntax tree node.
        let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
        inferredName = inferredInfo.name;
        inferredChain = inferredInfo.chain;
        inferredLocation = inferredInfo.loc;

        // Current node may be part of a larger assignment expression stack.
        if (parent.type == "AssignmentExpression") {
          this.onFunctionExpression(parent);
        }

        if (inferredName && inferredName.toLowerCase().contains(lowerCaseToken)) {
          store.push({
            inferredName: inferredName,
            inferredChain: inferredChain,
            inferredLocation: inferredLocation
          });
        }
      }
    });

    return store;
  },

  /**
   * Gets the "new" or "call" expression at the specified location.
   *
   * @param number aLine
   *        The line in the source.
   * @param number aColumn
   *        The column in the source.
   * @return object
   *         An { functionName, functionLocation } object hash,
   *         or null if nothing is found at the specified location.
   */
  getFunctionAtLocation: function STW_getFunctionAtLocation([aLine, aColumn]) {
    let self = this;
    let func = null;

    SyntaxTreeVisitor.walk(this.AST, {
      /**
       * Callback invoked for each node.
       * @param Node aNode
       */
      onNode: function STW_onNode(aNode) {
        // Make sure the node is part of a branch that's guaranteed to be
        // hovered. Otherwise, return true to abruptly halt walking this
        // syntax tree branch. This is a really efficient optimization.
        return ParserHelpers.isWithinLines(aNode, aLine);
      },

      /**
       * Callback invoked for each identifier node.
       * @param Node aNode
       */
      onIdentifier: function STW_onIdentifier(aNode) {
        // Make sure the identifier itself is hovered.
        let hovered = ParserHelpers.isWithinBounds(aNode, aLine, aColumn);
        if (!hovered) {
          return;
        }

        // Make sure the identifier is part of a "new" expression or
        // "call" expression node.
        let expression = ParserHelpers.getEnclosingFunctionExpression(aNode);
        if (!expression) {
          return;
        }

        // Found an identifier node that is part of a "new" expression or
        // "call" expression node. However, it may be an argument, not a callee.
        if (ParserHelpers.isFunctionCalleeArgument(aNode)) {
          // It's an argument.
          if (self.functionIdentifiersCache.has(aNode.name)) {
            // It's a function as an argument.
            func = {
              functionName: aNode.name,
              functionLocation: aNode.loc || aNode._parent.loc
            };
          }
          return;
        }

        // Found a valid "new" expression or "call" expression node.
        func = {
          functionName: aNode.name,
          functionLocation: ParserHelpers.getFunctionCalleeInfo(expression).loc
        };

        // Abruptly halt walking the syntax tree.
        this.break = true;
      }
    });

    return func;
  },

  /**
   * Gets all the function identifiers in this syntax tree (both the
   * function names and their inferred names).
   *
   * @return array
   *         An array of strings.
   */
  get functionIdentifiersCache() {
    if (this._functionIdentifiersCache) {
      return this._functionIdentifiersCache;
    }
    let functionDefinitions = this.getNamedFunctionDefinitions("");
    let functionIdentifiers = new Set();

    for (let { functionName, inferredName } of functionDefinitions) {
      functionIdentifiers.add(functionName);
      functionIdentifiers.add(inferredName);
    }
    return this._functionIdentifiersCache = functionIdentifiers;
  },

  AST: null,
  url: "",
  length: 0,
  offset: 0
};

/**
 * Parser utility methods.
 */
let ParserHelpers = {
  /**
   * Checks if a node's bounds contains a specified line.
   *
   * @param Node aNode
   *        The node's bounds used as reference.
   * @param number aLine
   *        The line number to check.
   * @return boolean
   *         True if the line and column is contained in the node's bounds.
   */
  isWithinLines: function PH_isWithinLines(aNode, aLine) {
    // Not all nodes have location information attached.
    if (!aNode.loc) {
      return this.isWithinLines(aNode._parent, aLine);
    }
    return aNode.loc.start.line <= aLine && aNode.loc.end.line >= aLine;
  },

  /**
   * Checks if a node's bounds contains a specified line and column.
   *
   * @param Node aNode
   *        The node's bounds used as reference.
   * @param number aLine
   *        The line number to check.
   * @param number aColumn
   *        The column number to check.
   * @return boolean
   *         True if the line and column is contained in the node's bounds.
   */
  isWithinBounds: function PH_isWithinBounds(aNode, aLine, aColumn) {
    // Not all nodes have location information attached.
    if (!aNode.loc) {
      return this.isWithinBounds(aNode._parent, aLine, aColumn);
    }
    return aNode.loc.start.line == aLine && aNode.loc.end.line == aLine &&
           aNode.loc.start.column <= aColumn && aNode.loc.end.column >= aColumn;
  },

  /**
   * Try to infer a function expression's name & other details based on the
   * enclosing VariableDeclarator, AssignmentExpression or ObjectExpression node.
   *
   * @param Node aNode
   *        The function expression node to get the name for.
   * @return object
   *         The inferred function name, or empty string can't infer name,
   *         along with the chain (a generic "context", like a prototype chain)
   *         and location if available.
   */
  inferFunctionExpressionInfo: function PH_inferFunctionExpressionInfo(aNode) {
    let parent = aNode._parent;

    // A function expression may be defined in a variable declarator,
    // e.g. var foo = function(){}, in which case it is possible to infer
    // the variable name.
    if (parent.type == "VariableDeclarator") {
      return {
        name: parent.id.name,
        chain: null,
        loc: parent.loc
      };
    }

    // Function expressions can also be defined in assignment expressions,
    // e.g. foo = function(){} or foo.bar = function(){}, in which case it is
    // possible to infer the assignee name ("foo" and "bar" respectively).
    if (parent.type == "AssignmentExpression") {
      let assigneeChain = this.getAssignmentExpressionAssigneeChain(parent);
      let assigneeLeaf = assigneeChain.pop();
      return {
        name: assigneeLeaf,
        chain: assigneeChain,
        loc: parent.left.loc
      };
    }

    // If a function expression is defined in an object expression,
    // e.g. { foo: function(){} }, then it is possible to infer the name
    // from the corresponding property.
    if (parent.type == "ObjectExpression") {
      let propertyDetails = this.getObjectExpressionPropertyKeyForValue(aNode);
      let propertyChain = this.getObjectExpressionPropertyChain(parent);
      let propertyLeaf = propertyDetails.name;
      return {
        name: propertyLeaf,
        chain: propertyChain,
        loc: propertyDetails.loc
      };
    }

    // Can't infer the function expression's name.
    return {
      name: "",
      chain: null,
      loc: null
    };
  },

  /**
   * Gets details about an object expression's property to which a specified
   * value is assigned. For example, the node returned for the value 42 in
   * "{ foo: { bar: 42 } }" is "bar".
   *
   * @param Node aNode
   *        The value node assigned to a property in an object expression.
   * @return object
   *         The details about the assignee property node.
   */
  getObjectExpressionPropertyKeyForValue:
  function PH_getObjectExpressionPropertyKeyForValue(aNode) {
    let parent = aNode._parent;
    if (parent.type != "ObjectExpression") {
      return null;
    }
    for (let property of parent.properties) {
      if (property.value == aNode) {
        return property.key;
      }
    }
  },

  /**
   * Gets an object expression property chain to its parent variable declarator.
   * For example, the chain to "baz" in "foo = { bar: { baz: { } } }" is
   * ["foo", "bar", "baz"].
   *
   * @param Node aNode
   *        The object expression node to begin the scan from.
   * @param array aStore [optional]
   *        The chain to store the nodes into.
   * @return array
   *         The chain to the parent variable declarator, as strings.
   */
  getObjectExpressionPropertyChain:
  function PH_getObjectExpressionPropertyChain(aNode, aStore = []) {
    switch (aNode.type) {
      case "ObjectExpression":
        this.getObjectExpressionPropertyChain(aNode._parent, aStore);

        let propertyDetails = this.getObjectExpressionPropertyKeyForValue(aNode);
        if (propertyDetails) {
          aStore.push(this.getObjectExpressionPropertyKeyForValue(aNode).name);
        }
        break;
      // Handle "foo.bar = { ... }" since it's commonly used when defining an
      // object's prototype methods; for example: "Foo.prototype = { ... }".
      case "AssignmentExpression":
        this.getAssignmentExpressionAssigneeChain(aNode, aStore);
        break;
      // Additionally handle stuff like "foo = bar.baz({ ... })", because it's
      // commonly used in prototype-based inheritance in many libraries;
      // for example: "Foo.Bar = Baz.extend({ ... })".
      case "NewExpression":
      case "CallExpression":
        this.getObjectExpressionPropertyChain(aNode._parent, aStore);
        break;
      // End of the chain.
      case "VariableDeclarator":
        aStore.push(aNode.id.name);
        break;
    }
    return aStore;
  },

  /**
   * Gets the assignee property chain in an assignment expression.
   * For example, the chain in "foo.bar.baz = 42" is ["foo", "bar", "baz"].
   *
   * @param Node aNode
   *        The assignment expression node to begin the scan from.
   * @param array aStore
   *        The chain to store the nodes into.
   * @param array aStore [optional]
   *        The chain to store the nodes into.
   * @return array
   *         The full assignee chain, as strings.
   */
  getAssignmentExpressionAssigneeChain:
  function PH_getAssignmentExpressionAssigneeChain(aNode, aStore = []) {
    switch (aNode.type) {
      case "AssignmentExpression":
        this.getAssignmentExpressionAssigneeChain(aNode.left, aStore);
        break;
      case "MemberExpression":
        this.getAssignmentExpressionAssigneeChain(aNode.object, aStore);
        this.getAssignmentExpressionAssigneeChain(aNode.property, aStore);
        break;
      case "ThisExpression":
        // Such expressions may appear in an assignee chain, for example
        // "this.foo.bar = baz", however it seems better to ignore such nodes
        // and limit the chain to ["foo", "bar"].
        break;
      case "Identifier":
        aStore.push(aNode.name);
        break;
    }
    return aStore;
  },

  /**
   * Gets the "new" expression or "call" expression containing the specified
   * node. If the node is not enclosed in either of these expression types,
   * null is returned.
   *
   * @param Node aNode
   *        The child node of an enclosing "new" expression or "call" expression.
   * @return Node
   *         The enclosing "new" expression or "call" expression node, or
   *         null if nothing is found.
   */
  getEnclosingFunctionExpression:
  function PH_getEnclosingFunctionExpression(aNode) {
    switch (aNode.type) {
      case "NewExpression":
      case "CallExpression":
        return aNode;
      case "MemberExpression":
      case "Identifier":
        return this.getEnclosingFunctionExpression(aNode._parent);
      default:
        return null;
    }
  },

  /**
   * Gets the name and { line, column } location of a "new" expression or
   * "call" expression's callee node.
   *
   * @param Node aNode
   *        The "new" expression or "call" expression to get the callee info for.
   * @return object
   *         An object containing the name and location as properties, or
   *         null if nothing is found.
   */
  getFunctionCalleeInfo: function PH_getFunctionCalleeInfo(aNode) {
    switch (aNode.type) {
      case "NewExpression":
      case "CallExpression":
        return this.getFunctionCalleeInfo(aNode.callee);
      case "MemberExpression":
        return this.getFunctionCalleeInfo(aNode.property);
      case "Identifier":
        return {
          name: aNode.name,
          loc: aNode.loc || (aNode._parent || {}).loc
        };
      default:
        return null;
    }
  },

  /**
   * Determines if an identifier node is part of a "new" expression or
   * "call" expression's callee arguments.
   *
   * @param Node aNode
   *        The node to determine if part of a function's arguments.
   * @return boolean
   *         True if the identifier is an argument, false otherwise.
   */
  isFunctionCalleeArgument: function PH_isFunctionCalleeArgument(aNode) {
    if (!aNode._parent) {
      return false;
    }
    switch (aNode._parent.type) {
      case "NewExpression":
      case "CallExpression":
        return aNode._parent.arguments.indexOf(aNode) != -1;
      default:
        return this.isFunctionCalleeArgument(aNode._parent);
    }
  }
};

/**
 * A visitor for a syntax tree generated by the reflection API.
 * See https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API.
 *
 * All node types implement the following interface:
 * interface Node {
 *   type: string;
 *   loc: SourceLocation | null;
 * }
 */
let SyntaxTreeVisitor = {
  /**
   * Walks a syntax tree.
   *
   * @param object aTree
   *        The AST nodes generated by the reflection API
   * @param object aCallbacks
   *        A map of all the callbacks to invoke when passing through certain
   *        types of noes (e.g: onFunctionDeclaration, onBlockStatement etc.).
   */
  walk: function STV_walk(aTree, aCallbacks) {
    this[aTree.type](aTree, aCallbacks);
  },

  /**
   * A flag checked on each node in the syntax tree. If true, walking is
   * abruptly halted.
   */
  break: false,

  /**
   * A complete program source tree.
   *
   * interface Program <: Node {
   *   type: "Program";
   *   body: [ Statement ];
   * }
   */
  Program: function STV_Program(aNode, aCallbacks) {
    if (aCallbacks.onProgram) {
      aCallbacks.onProgram(aNode);
    }
    for (let statement of aNode.body) {
      this[statement.type](statement, aNode, aCallbacks);
    }
  },

  /**
   * Any statement.
   *
   * interface Statement <: Node { }
   */
  Statement: function STV_Statement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onStatement) {
      aCallbacks.onStatement(aNode);
    }
  },

  /**
   * An empty statement, i.e., a solitary semicolon.
   *
   * interface EmptyStatement <: Statement {
   *   type: "EmptyStatement";
   * }
   */
  EmptyStatement: function STV_EmptyStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onEmptyStatement) {
      aCallbacks.onEmptyStatement(aNode);
    }
  },

  /**
   * A block statement, i.e., a sequence of statements surrounded by braces.
   *
   * interface BlockStatement <: Statement {
   *   type: "BlockStatement";
   *   body: [ Statement ];
   * }
   */
  BlockStatement: function STV_BlockStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onBlockStatement) {
      aCallbacks.onBlockStatement(aNode);
    }
    for (let statement of aNode.body) {
      this[statement.type](statement, aNode, aCallbacks);
    }
  },

  /**
   * An expression statement, i.e., a statement consisting of a single expression.
   *
   * interface ExpressionStatement <: Statement {
   *   type: "ExpressionStatement";
   *   expression: Expression;
   * }
   */
  ExpressionStatement: function STV_ExpressionStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onExpressionStatement) {
      aCallbacks.onExpressionStatement(aNode);
    }
    this[aNode.expression.type](aNode.expression, aNode, aCallbacks);
  },

  /**
   * An if statement.
   *
   * interface IfStatement <: Statement {
   *   type: "IfStatement";
   *   test: Expression;
   *   consequent: Statement;
   *   alternate: Statement | null;
   * }
   */
  IfStatement: function STV_IfStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onIfStatement) {
      aCallbacks.onIfStatement(aNode);
    }
    this[aNode.test.type](aNode.test, aNode, aCallbacks);
    this[aNode.consequent.type](aNode.consequent, aNode, aCallbacks);
    if (aNode.alternate) {
      this[aNode.alternate.type](aNode.alternate, aNode, aCallbacks);
    }
  },

  /**
   * A labeled statement, i.e., a statement prefixed by a break/continue label.
   *
   * interface LabeledStatement <: Statement {
   *   type: "LabeledStatement";
   *   label: Identifier;
   *   body: Statement;
   * }
   */
  LabeledStatement: function STV_LabeledStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onLabeledStatement) {
      aCallbacks.onLabeledStatement(aNode);
    }
    this[aNode.label.type](aNode.label, aNode, aCallbacks);
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A break statement.
   *
   * interface BreakStatement <: Statement {
   *   type: "BreakStatement";
   *   label: Identifier | null;
   * }
   */
  BreakStatement: function STV_BreakStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onBreakStatement) {
      aCallbacks.onBreakStatement(aNode);
    }
    if (aNode.label) {
      this[aNode.label.type](aNode.label, aNode, aCallbacks);
    }
  },

  /**
   * A continue statement.
   *
   * interface ContinueStatement <: Statement {
   *   type: "ContinueStatement";
   *   label: Identifier | null;
   * }
   */
  ContinueStatement: function STV_ContinueStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onContinueStatement) {
      aCallbacks.onContinueStatement(aNode);
    }
    if (aNode.label) {
      this[aNode.label.type](aNode.label, aNode, aCallbacks);
    }
  },

  /**
   * A with statement.
   *
   * interface WithStatement <: Statement {
   *   type: "WithStatement";
   *   object: Expression;
   *   body: Statement;
   * }
   */
  WithStatement: function STV_WithStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onWithStatement) {
      aCallbacks.onWithStatement(aNode);
    }
    this[aNode.object.type](aNode.object, aNode, aCallbacks);
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A switch statement. The lexical flag is metadata indicating whether the
   * switch statement contains any unnested let declarations (and therefore
   * introduces a new lexical scope).
   *
   * interface SwitchStatement <: Statement {
   *   type: "SwitchStatement";
   *   discriminant: Expression;
   *   cases: [ SwitchCase ];
   *   lexical: boolean;
   * }
   */
  SwitchStatement: function STV_SwitchStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onSwitchStatement) {
      aCallbacks.onSwitchStatement(aNode);
    }
    this[aNode.discriminant.type](aNode.discriminant, aNode, aCallbacks);
    for (let _case of aNode.cases) {
      this[_case.type](_case, aNode, aCallbacks);
    }
  },

  /**
   * A return statement.
   *
   * interface ReturnStatement <: Statement {
   *   type: "ReturnStatement";
   *   argument: Expression | null;
   * }
   */
  ReturnStatement: function STV_ReturnStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onReturnStatement) {
      aCallbacks.onReturnStatement(aNode);
    }
    if (aNode.argument) {
      this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
    }
  },

  /**
   * A throw statement.
   *
   * interface ThrowStatement <: Statement {
   *   type: "ThrowStatement";
   *   argument: Expression;
   * }
   */
  ThrowStatement: function STV_ThrowStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onThrowStatement) {
      aCallbacks.onThrowStatement(aNode);
    }
    this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
  },

  /**
   * A try statement.
   *
   * interface TryStatement <: Statement {
   *   type: "TryStatement";
   *   block: BlockStatement;
   *   handler: CatchClause | null;
   *   guardedHandlers: [ CatchClause ];
   *   finalizer: BlockStatement | null;
   * }
   */
  TryStatement: function STV_TryStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onTryStatement) {
      aCallbacks.onTryStatement(aNode);
    }
    this[aNode.block.type](aNode.block, aNode, aCallbacks);
    if (aNode.handler) {
      this[aNode.handler.type](aNode.handler, aNode, aCallbacks);
    }
    for (let guardedHandler of aNode.guardedHandlers) {
      this[guardedHandler.type](guardedHandler, aNode, aCallbacks);
    }
    if (aNode.finalizer) {
      this[aNode.finalizer.type](aNode.finalizer, aNode, aCallbacks);
    }
  },

  /**
   * A while statement.
   *
   * interface WhileStatement <: Statement {
   *   type: "WhileStatement";
   *   test: Expression;
   *   body: Statement;
   * }
   */
  WhileStatement: function STV_WhileStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onWhileStatement) {
      aCallbacks.onWhileStatement(aNode);
    }
    this[aNode.test.type](aNode.test, aNode, aCallbacks);
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A do/while statement.
   *
   * interface DoWhileStatement <: Statement {
   *   type: "DoWhileStatement";
   *   body: Statement;
   *   test: Expression;
   * }
   */
  DoWhileStatement: function STV_DoWhileStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onDoWhileStatement) {
      aCallbacks.onDoWhileStatement(aNode);
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
    this[aNode.test.type](aNode.test, aNode, aCallbacks);
  },

  /**
   * A for statement.
   *
   * interface ForStatement <: Statement {
   *   type: "ForStatement";
   *   init: VariableDeclaration | Expression | null;
   *   test: Expression | null;
   *   update: Expression | null;
   *   body: Statement;
   * }
   */
  ForStatement: function STV_ForStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onForStatement) {
      aCallbacks.onForStatement(aNode);
    }
    if (aNode.init) {
      this[aNode.init.type](aNode.init, aNode, aCallbacks);
    }
    if (aNode.test) {
      this[aNode.test.type](aNode.test, aNode, aCallbacks);
    }
    if (aNode.update) {
      this[aNode.update.type](aNode.update, aNode, aCallbacks);
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A for/in statement, or, if each is true, a for each/in statement.
   *
   * interface ForInStatement <: Statement {
   *   type: "ForInStatement";
   *   left: VariableDeclaration | Expression;
   *   right: Expression;
   *   body: Statement;
   *   each: boolean;
   * }
   */
  ForInStatement: function STV_ForInStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onForInStatement) {
      aCallbacks.onForInStatement(aNode);
    }
    this[aNode.left.type](aNode.left, aNode, aCallbacks);
    this[aNode.right.type](aNode.right, aNode, aCallbacks);
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A for/of statement.
   *
   * interface ForOfStatement <: Statement {
   *   type: "ForOfStatement";
   *   left: VariableDeclaration | Expression;
   *   right: Expression;
   *   body: Statement;
   * }
   */
  ForOfStatement: function STV_ForOfStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onForOfStatement) {
      aCallbacks.onForOfStatement(aNode);
    }
    this[aNode.left.type](aNode.left, aNode, aCallbacks);
    this[aNode.right.type](aNode.right, aNode, aCallbacks);
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A let statement.
   *
   * interface LetStatement <: Statement {
   *   type: "LetStatement";
   *   head: [ { id: Pattern, init: Expression | null } ];
   *   body: Statement;
   * }
   */
  LetStatement: function STV_LetStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onLetStatement) {
      aCallbacks.onLetStatement(aNode);
    }
    for (let { id, init } of aNode.head) {
      this[id.type](id, aNode, aCallbacks);
      if (init) {
        this[init.type](init, aNode, aCallbacks);
      }
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A debugger statement.
   *
   * interface DebuggerStatement <: Statement {
   *   type: "DebuggerStatement";
   * }
   */
  DebuggerStatement: function STV_DebuggerStatement(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onDebuggerStatement) {
      aCallbacks.onDebuggerStatement(aNode);
    }
  },

  /**
   * Any declaration node. Note that declarations are considered statements;
   * this is because declarations can appear in any statement context in the
   * language recognized by the SpiderMonkey parser.
   *
   * interface Declaration <: Statement { }
   */
  Declaration: function STV_Declaration(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onDeclaration) {
      aCallbacks.onDeclaration(aNode);
    }
  },

  /**
   * A function declaration.
   *
   * interface FunctionDeclaration <: Function, Declaration {
   *   type: "FunctionDeclaration";
   *   id: Identifier;
   *   params: [ Pattern ];
   *   defaults: [ Expression ];
   *   rest: Identifier | null;
   *   body: BlockStatement | Expression;
   *   generator: boolean;
   *   expression: boolean;
   * }
   */
  FunctionDeclaration: function STV_FunctionDeclaration(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onFunctionDeclaration) {
      aCallbacks.onFunctionDeclaration(aNode);
    }
    this[aNode.id.type](aNode.id, aNode, aCallbacks);
    for (let param of aNode.params) {
      this[param.type](param, aNode, aCallbacks);
    }
    for (let _default of aNode.defaults) {
      this[_default.type](_default, aNode, aCallbacks);
    }
    if (aNode.rest) {
      this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A variable declaration, via one of var, let, or const.
   *
   * interface VariableDeclaration <: Declaration {
   *   type: "VariableDeclaration";
   *   declarations: [ VariableDeclarator ];
   *   kind: "var" | "let" | "const";
   * }
   */
  VariableDeclaration: function STV_VariableDeclaration(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onVariableDeclaration) {
      aCallbacks.onVariableDeclaration(aNode);
    }
    for (let declaration of aNode.declarations) {
      this[declaration.type](declaration, aNode, aCallbacks);
    }
  },

  /**
   * A variable declarator.
   *
   * interface VariableDeclarator <: Node {
   *   type: "VariableDeclarator";
   *   id: Pattern;
   *   init: Expression | null;
   * }
   */
  VariableDeclarator: function STV_VariableDeclarator(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onVariableDeclarator) {
      aCallbacks.onVariableDeclarator(aNode);
    }
    this[aNode.id.type](aNode.id, aNode, aCallbacks);
    if (aNode.init) {
      this[aNode.init.type](aNode.init, aNode, aCallbacks);
    }
  },

  /**
   * Any expression node. Since the left-hand side of an assignment may be any
   * expression in general, an expression can also be a pattern.
   *
   * interface Expression <: Node, Pattern { }
   */
  Expression: function STV_Expression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onExpression) {
      aCallbacks.onExpression(aNode);
    }
  },

  /**
   * A this expression.
   *
   * interface ThisExpression <: Expression {
   *   type: "ThisExpression";
   * }
   */
  ThisExpression: function STV_ThisExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onThisExpression) {
      aCallbacks.onThisExpression(aNode);
    }
  },

  /**
   * An array expression.
   *
   * interface ArrayExpression <: Expression {
   *   type: "ArrayExpression";
   *   elements: [ Expression | null ];
   * }
   */
  ArrayExpression: function STV_ArrayExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onArrayExpression) {
      aCallbacks.onArrayExpression(aNode);
    }
    for (let element of aNode.elements) {
      if (element) {
        this[element.type](element, aNode, aCallbacks);
      }
    }
  },

  /**
   * An object expression. A literal property in an object expression can have
   * either a string or number as its value. Ordinary property initializers
   * have a kind value "init"; getters and setters have the kind values "get"
   * and "set", respectively.
   *
   * interface ObjectExpression <: Expression {
   *   type: "ObjectExpression";
   *   properties: [ { key: Literal | Identifier,
   *                   value: Expression,
   *                   kind: "init" | "get" | "set" } ];
   * }
   */
  ObjectExpression: function STV_ObjectExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onObjectExpression) {
      aCallbacks.onObjectExpression(aNode);
    }
    for (let { key, value } of aNode.properties) {
      this[key.type](key, aNode, aCallbacks);
      this[value.type](value, aNode, aCallbacks);
    }
  },

  /**
   * A function expression.
   *
   * interface FunctionExpression <: Function, Expression {
   *   type: "FunctionExpression";
   *   id: Identifier | null;
   *   params: [ Pattern ];
   *   defaults: [ Expression ];
   *   rest: Identifier | null;
   *   body: BlockStatement | Expression;
   *   generator: boolean;
   *   expression: boolean;
   * }
   */
  FunctionExpression: function STV_FunctionExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onFunctionExpression) {
      aCallbacks.onFunctionExpression(aNode);
    }
    if (aNode.id) {
      this[aNode.id.type](aNode.id, aNode, aCallbacks);
    }
    for (let param of aNode.params) {
      this[param.type](param, aNode, aCallbacks);
    }
    for (let _default of aNode.defaults) {
      this[_default.type](_default, aNode, aCallbacks);
    }
    if (aNode.rest) {
      this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * An arrow expression.
   *
   * interface ArrowExpression <: Function, Expression {
   *   type: "ArrowExpression";
   *   params: [ Pattern ];
   *   defaults: [ Expression ];
   *   rest: Identifier | null;
   *   body: BlockStatement | Expression;
   *   generator: boolean;
   *   expression: boolean;
   * }
   */
  ArrowExpression: function STV_ArrowExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onArrowExpression) {
      aCallbacks.onArrowExpression(aNode);
    }
    for (let param of aNode.params) {
      this[param.type](param, aNode, aCallbacks);
    }
    for (let _default of aNode.defaults) {
      this[_default.type](_default, aNode, aCallbacks);
    }
    if (aNode.rest) {
      this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A sequence expression, i.e., a comma-separated sequence of expressions.
   *
   * interface SequenceExpression <: Expression {
   *   type: "SequenceExpression";
   *   expressions: [ Expression ];
   * }
   */
  SequenceExpression: function STV_SequenceExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onSequenceExpression) {
      aCallbacks.onSequenceExpression(aNode);
    }
    for (let expression of aNode.expressions) {
      this[expression.type](expression, aNode, aCallbacks);
    }
  },

  /**
   * A unary operator expression.
   *
   * interface UnaryExpression <: Expression {
   *   type: "UnaryExpression";
   *   operator: UnaryOperator;
   *   prefix: boolean;
   *   argument: Expression;
   * }
   */
  UnaryExpression: function STV_UnaryExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onUnaryExpression) {
      aCallbacks.onUnaryExpression(aNode);
    }
    this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
  },

  /**
   * A binary operator expression.
   *
   * interface BinaryExpression <: Expression {
   *   type: "BinaryExpression";
   *   operator: BinaryOperator;
   *   left: Expression;
   *   right: Expression;
   * }
   */
  BinaryExpression: function STV_BinaryExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onBinaryExpression) {
      aCallbacks.onBinaryExpression(aNode);
    }
    this[aNode.left.type](aNode.left, aNode, aCallbacks);
    this[aNode.right.type](aNode.right, aNode, aCallbacks);
  },

  /**
   * An assignment operator expression.
   *
   * interface AssignmentExpression <: Expression {
   *   type: "AssignmentExpression";
   *   operator: AssignmentOperator;
   *   left: Expression;
   *   right: Expression;
   * }
   */
  AssignmentExpression: function STV_AssignmentExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onAssignmentExpression) {
      aCallbacks.onAssignmentExpression(aNode);
    }
    this[aNode.left.type](aNode.left, aNode, aCallbacks);
    this[aNode.right.type](aNode.right, aNode, aCallbacks);
  },

  /**
   * An update (increment or decrement) operator expression.
   *
   * interface UpdateExpression <: Expression {
   *   type: "UpdateExpression";
   *   operator: UpdateOperator;
   *   argument: Expression;
   *   prefix: boolean;
   * }
   */
  UpdateExpression: function STV_UpdateExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onUpdateExpression) {
      aCallbacks.onUpdateExpression(aNode);
    }
    this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
  },

  /**
   * A logical operator expression.
   *
   * interface LogicalExpression <: Expression {
   *   type: "LogicalExpression";
   *   operator: LogicalOperator;
   *   left: Expression;
   *   right: Expression;
   * }
   */
  LogicalExpression: function STV_LogicalExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onLogicalExpression) {
      aCallbacks.onLogicalExpression(aNode);
    }
    this[aNode.left.type](aNode.left, aNode, aCallbacks);
    this[aNode.right.type](aNode.right, aNode, aCallbacks);
  },

  /**
   * A conditional expression, i.e., a ternary ?/: expression.
   *
   * interface ConditionalExpression <: Expression {
   *   type: "ConditionalExpression";
   *   test: Expression;
   *   alternate: Expression;
   *   consequent: Expression;
   * }
   */
  ConditionalExpression: function STV_ConditionalExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onConditionalExpression) {
      aCallbacks.onConditionalExpression(aNode);
    }
    this[aNode.test.type](aNode.test, aNode, aCallbacks);
    this[aNode.alternate.type](aNode.alternate, aNode, aCallbacks);
    this[aNode.consequent.type](aNode.consequent, aNode, aCallbacks);
  },

  /**
   * A new expression.
   *
   * interface NewExpression <: Expression {
   *   type: "NewExpression";
   *   callee: Expression;
   *   arguments: [ Expression | null ];
   * }
   */
  NewExpression: function STV_NewExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onNewExpression) {
      aCallbacks.onNewExpression(aNode);
    }
    this[aNode.callee.type](aNode.callee, aNode, aCallbacks);
    for (let argument of aNode.arguments) {
      if (argument) {
        this[argument.type](argument, aNode, aCallbacks);
      }
    }
  },

  /**
   * A function or method call expression.
   *
   * interface CallExpression <: Expression {
   *   type: "CallExpression";
   *   callee: Expression;
   *   arguments: [ Expression | null ];
   * }
   */
  CallExpression: function STV_CallExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onCallExpression) {
      aCallbacks.onCallExpression(aNode);
    }
    this[aNode.callee.type](aNode.callee, aNode, aCallbacks);
    for (let argument of aNode.arguments) {
      if (argument) {
        this[argument.type](argument, aNode, aCallbacks);
      }
    }
  },

  /**
   * A member expression. If computed is true, the node corresponds to a
   * computed e1[e2] expression and property is an Expression. If computed is
   * false, the node corresponds to a static e1.x expression and property is an
   * Identifier.
   *
   * interface MemberExpression <: Expression {
   *   type: "MemberExpression";
   *   object: Expression;
   *   property: Identifier | Expression;
   *   computed: boolean;
   * }
   */
  MemberExpression: function STV_MemberExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onMemberExpression) {
      aCallbacks.onMemberExpression(aNode);
    }
    this[aNode.object.type](aNode.object, aNode, aCallbacks);
    this[aNode.property.type](aNode.property, aNode, aCallbacks);
  },

  /**
   * A yield expression.
   *
   * interface YieldExpression <: Expression {
   *   argument: Expression | null;
   * }
   */
  YieldExpression: function STV_YieldExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onYieldExpression) {
      aCallbacks.onYieldExpression(aNode);
    }
    if (aNode.argument) {
      this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
    }
  },

  /**
   * An array comprehension. The blocks array corresponds to the sequence of
   * for and for each blocks. The optional filter expression corresponds to the
   * final if clause, if present.
   *
   * interface ComprehensionExpression <: Expression {
   *   body: Expression;
   *   blocks: [ ComprehensionBlock ];
   *   filter: Expression | null;
   * }
   */
  ComprehensionExpression: function STV_ComprehensionExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onComprehensionExpression) {
      aCallbacks.onComprehensionExpression(aNode);
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
    for (let block of aNode.blocks) {
      this[block.type](block, aNode, aCallbacks);
    }
    if (aNode.filter) {
      this[aNode.filter.type](aNode.filter, aNode, aCallbacks);
    }
  },

  /**
   * A generator expression. As with array comprehensions, the blocks array
   * corresponds to the sequence of for and for each blocks, and the optional
   * filter expression corresponds to the final if clause, if present.
   *
   * interface GeneratorExpression <: Expression {
   *   body: Expression;
   *   blocks: [ ComprehensionBlock ];
   *   filter: Expression | null;
   * }
   */
  GeneratorExpression: function STV_GeneratorExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onGeneratorExpression) {
      aCallbacks.onGeneratorExpression(aNode);
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
    for (let block of aNode.blocks) {
      this[block.type](block, aNode, aCallbacks);
    }
    if (aNode.filter) {
      this[aNode.filter.type](aNode.filter, aNode, aCallbacks);
    }
  },

  /**
   * A graph expression, aka "sharp literal," such as #1={ self: #1# }.
   *
   * interface GraphExpression <: Expression {
   *   index: uint32;
   *   expression: Literal;
   * }
   */
  GraphExpression: function STV_GraphExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onGraphExpression) {
      aCallbacks.onGraphExpression(aNode);
    }
    this[aNode.expression.type](aNode.expression, aNode, aCallbacks);
  },

  /**
   * A graph index expression, aka "sharp variable," such as #1#.
   *
   * interface GraphIndexExpression <: Expression {
   *   index: uint32;
   * }
   */
  GraphIndexExpression: function STV_GraphIndexExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onGraphIndexExpression) {
      aCallbacks.onGraphIndexExpression(aNode);
    }
  },

  /**
   * A let expression.
   *
   * interface LetExpression <: Expression {
   *   type: "LetExpression";
   *   head: [ { id: Pattern, init: Expression | null } ];
   *   body: Expression;
   * }
   */
  LetExpression: function STV_LetExpression(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onLetExpression) {
      aCallbacks.onLetExpression(aNode);
    }
    for (let { id, init } of aNode.head) {
      this[id.type](id, aNode, aCallbacks);
      if (init) {
        this[init.type](init, aNode, aCallbacks);
      }
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * Any pattern.
   *
   * interface Pattern <: Node { }
   */
  Pattern: function STV_Pattern(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onPattern) {
      aCallbacks.onPattern(aNode);
    }
  },

  /**
   * An object-destructuring pattern. A literal property in an object pattern
   * can have either a string or number as its value.
   *
   * interface ObjectPattern <: Pattern {
   *   type: "ObjectPattern";
   *   properties: [ { key: Literal | Identifier, value: Pattern } ];
   * }
   */
  ObjectPattern: function STV_ObjectPattern(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onObjectPattern) {
      aCallbacks.onObjectPattern(aNode);
    }
    for (let { key, value } of aNode.properties) {
      this[key.type](key, aNode, aCallbacks);
      this[value.type](value, aNode, aCallbacks);
    }
  },

  /**
   * An array-destructuring pattern.
   *
   * interface ArrayPattern <: Pattern {
   *   type: "ArrayPattern";
   *   elements: [ Pattern | null ];
   * }
   */
  ArrayPattern: function STV_ArrayPattern(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onArrayPattern) {
      aCallbacks.onArrayPattern(aNode);
    }
    for (let element of aNode.elements) {
      if (element) {
        this[element.type](element, aNode, aCallbacks);
      }
    }
  },

  /**
   * A case (if test is an Expression) or default (if test is null) clause in
   * the body of a switch statement.
   *
   * interface SwitchCase <: Node {
   *   type: "SwitchCase";
   *   test: Expression | null;
   *   consequent: [ Statement ];
   * }
   */
  SwitchCase: function STV_SwitchCase(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onSwitchCase) {
      aCallbacks.onSwitchCase(aNode);
    }
    if (aNode.test) {
      this[aNode.test.type](aNode.test, aNode, aCallbacks);
    }
    for (let consequent of aNode.consequent) {
      this[consequent.type](consequent, aNode, aCallbacks);
    }
  },

  /**
   * A catch clause following a try block. The optional guard property
   * corresponds to the optional expression guard on the bound variable.
   *
   * interface CatchClause <: Node {
   *   type: "CatchClause";
   *   param: Pattern;
   *   guard: Expression | null;
   *   body: BlockStatement;
   * }
   */
  CatchClause: function STV_CatchClause(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onCatchClause) {
      aCallbacks.onCatchClause(aNode);
    }
    this[aNode.param.type](aNode.param, aNode, aCallbacks);
    if (aNode.guard) {
      this[aNode.guard.type](aNode.guard, aNode, aCallbacks);
    }
    this[aNode.body.type](aNode.body, aNode, aCallbacks);
  },

  /**
   * A for or for each block in an array comprehension or generator expression.
   *
   * interface ComprehensionBlock <: Node {
   *   left: Pattern;
   *   right: Expression;
   *   each: boolean;
   * }
   */
  ComprehensionBlock: function STV_ComprehensionBlock(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onComprehensionBlock) {
      aCallbacks.onComprehensionBlock(aNode);
    }
    this[aNode.left.type](aNode.left, aNode, aCallbacks);
    this[aNode.right.type](aNode.right, aNode, aCallbacks);
  },

  /**
   * An identifier. Note that an identifier may be an expression or a
   * destructuring pattern.
   *
   * interface Identifier <: Node, Expression, Pattern {
   *   type: "Identifier";
   *   name: string;
   * }
   */
  Identifier: function STV_Identifier(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onIdentifier) {
      aCallbacks.onIdentifier(aNode);
    }
  },

  /**
   * A literal token. Note that a literal can be an expression.
   *
   * interface Literal <: Node, Expression {
   *   type: "Literal";
   *   value: string | boolean | null | number | RegExp;
   * }
   */
  Literal: function STV_Literal(aNode, aParent, aCallbacks) {
    aNode._parent = aParent;

    if (this.break) {
      return;
    }
    if (aCallbacks.onNode) {
      if (aCallbacks.onNode(aNode, aParent) === false) {
        return;
      }
    }
    if (aCallbacks.onLiteral) {
      aCallbacks.onLiteral(aNode);
    }
  }
};

/**
 * Logs a warning.
 *
 * @param string aStr
 *        The message to be displayed.
 * @param Exception aEx
 *        The thrown exception.
 */
function log(aStr, aEx) {
  let msg = "Warning: " + aStr + ", " + aEx + "\n" + aEx.stack;
  Cu.reportError(msg);
  dump(msg + "\n");
};

XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", function() Reflect);
