js/src/frontend/BinAST.yaml
author Tooru Fujisawa <arai_a@mac.com>
Wed, 13 Mar 2019 04:29:44 +0000
changeset 521653 123ecc5c9586
parent 521651 js/src/frontend/BinSource.yaml@7cbe3fbc4b7f
child 521654 976e93ae2dbc
permissions -rw-r--r--
Bug 1505343 - Part 1: Rename binsource => binast. r=Yoric Differential Revision: https://phabricator.services.mozilla.com/D23097

# -*- Mode: Yaml; tab-width: 8; indent-tabs-mode: nil; yaml-indent-offset: 2 -*-
# vim: set ts=8 sts=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/.

parser:
  class-template:
    template<typename Tok>
  class-name:
    BinASTParser<Tok>
  type-ok:
    ParseNode*
  default-value:
    nullptr
  list:
    append: |
      result->appendWithoutOrderAssumption(item);

# Rules for generating BinASTParser.cpp
cpp:
  header: |
    /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     * vim: set ts=8 sts=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/. */

    // To generate this file, see the documentation in
    // js/src/frontend/binast/README.md.

    #include "frontend/BinASTParser.h"

    #include "mozilla/ArrayUtils.h"
    #include "mozilla/Casting.h"
    #include "mozilla/Maybe.h"
    #include "mozilla/Move.h"
    #include "mozilla/PodOperations.h"
    #include "mozilla/Vector.h"

    #include "frontend/BinAST-macros.h"
    #include "frontend/BinTokenReaderMultipart.h"
    #include "frontend/FullParseHandler.h"
    #include "frontend/ParseNode.h"
    #include "frontend/Parser.h"
    #include "frontend/SharedContext.h"

    #include "vm/RegExpObject.h"

    #include "frontend/ParseContext-inl.h"

    namespace js {
    namespace frontend {

    // Compare a bunch of `uint8_t` values (as returned by the tokenizer_) with
    // a string literal (and ONLY a string literal).
    template<typename Tok, size_t N>
    bool operator==(const typename Tok::Chars& left, const char (&right)[N]) {
      return Tok::equals(left, right);
    }

  footer: |

    // Force class instantiation.
    // This ensures that the symbols are built, without having to export all our
    // code (and its baggage of #include and macros) in the header.
    template class BinASTParser<BinTokenReaderMultipart>;

    } // namespace frontend
    } // namespace js

hpp:
  # Rules for generating BinASTParser.h
  class:
    header: |
      /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       * vim: set ts=8 sts=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/. */

      // To generate this file, see the documentation in
      // js/src/frontend/binast/README.md.

      #ifndef frontend_BinASTParser_h
      #define frontend_BinASTParser_h

      #include "mozilla/Maybe.h"

      #include "frontend/BCEParserHandle.h"
      #include "frontend/BinASTParserPerTokenizer.h"
      #include "frontend/BinToken.h"
      #include "frontend/BinTokenReaderMultipart.h"
      #include "frontend/FullParseHandler.h"
      #include "frontend/ParseContext.h"
      #include "frontend/ParseNode.h"
      #include "frontend/SharedContext.h"

      #include "js/CompileOptions.h"
      #include "js/GCHashTable.h"
      #include "js/GCVector.h"
      #include "js/Result.h"

      namespace js {
      namespace frontend {

      template<typename Tok>
      class BinASTParser : public BinASTParserPerTokenizer<Tok> {
       public:
        using Base = BinASTParserPerTokenizer<Tok>;

        using Tokenizer = Tok;

        using BinFields = typename Tokenizer::BinFields;
        using AutoList = typename Tokenizer::AutoList;
        using AutoTaggedTuple = typename Tokenizer::AutoTaggedTuple;
        using Chars = typename Tokenizer::Chars;

       public:
        // Auto-generated types.
        using AssertedDeclaredKind = binast::AssertedDeclaredKind;
        using BinaryOperator = binast::BinaryOperator;
        using CompoundAssignmentOperator = binast::CompoundAssignmentOperator;
        using UnaryOperator = binast::UnaryOperator;
        using UpdateOperator = binast::UpdateOperator;
        using VariableDeclarationKind = binast::VariableDeclarationKind;

       public:
        // BinASTParserPerTokenizer types.
        using AssertedScopeKind = typename Base::AssertedScopeKind;

       public:
        BinASTParser(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames,
                     const JS::ReadOnlyCompileOptions& options,
                     HandleScriptSourceObject sourceObject,
                     Handle<LazyScript*> lazyScript = nullptr)
            : BinASTParserPerTokenizer<Tok>(cx, alloc, usedNames, options,
                                            sourceObject, lazyScript) {}
        ~BinASTParser() {}

       protected:
        // BinASTParserBase fields.
        using Base::cx_;

        using Base::alloc_;
        using Base::usedNames_;

        using Base::sourceObject_;
        using Base::pc_;
        using Base::handler_;

       protected:
        // BinASTParserPerTokenizer types.
        using AutoVariableDeclarationKind =
            typename Base::AutoVariableDeclarationKind;

       protected:
        // BinASTParserPerTokenizer fields.
        using Base::tokenizer_;
        using Base::variableDeclarationKind_;

       protected:
        // BinASTParserPerTokenizer methods.
        using Base::raiseInvalidClosedVar;
        using Base::raiseMissingVariableInAssertedScope;
        using Base::raiseMissingDirectEvalInAssertedScope;
        using Base::raiseInvalidKind;
        using Base::raiseInvalidVariant;
        using Base::raiseMissingField;
        using Base::raiseEmpty;
        using Base::raiseOOM;
        using Base::raiseError;

        using Base::makeEmptyFunctionNode;
        using Base::buildFunction;
        using Base::buildFunctionBox;
        using Base::addScopeName;
        using Base::captureFunctionName;

        using Base::getDeclaredScope;
        using Base::getBoundScope;
        using Base::checkBinding;
        using Base::checkPositionalParameterIndices;

        using Base::checkFunctionLength;
        using Base::checkClosedVars;

        using Base::prependDirectivesToBody;

        using Base::forceStrictIfNecessary;

       public:
    footer: |
      };

      extern template class BinASTParser<BinTokenReaderMultipart>;

      } // namespace frontend
      } // namespace js

      #endif // frontend_BinASTParser_h

  enums:
    header: |
      /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       * vim: set ts=8 sts=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/. */

      // To generate this file, see the documentation in
      // js/src/frontend/binast/README.md.

      #ifndef frontend_BinASTEnum_h
      #define frontend_BinASTEnum_h

      namespace js {
      namespace frontend {
      namespace binast {
    footer: |
      } // namespace binast
      } // namespace frontend
      } // namespace js

      #endif // frontend_BinASTEnum_h

  # Rules for generating BinToken.h
  tokens:
    kind:
      doc: |
        /**
         * The different kinds of Binary AST nodes, as per the specifications of
         * Binary AST.
         *
         * These kinds match roughly with the `ParseNodeKind` used internally.
         *
         * Usage:
         *
         * ```c++
         * #define WITH_KIND(CPP_NAME, SPEC_NAME) ...
         * FOR_EACH_BIN_KIND(WITH_KIND)
         * ```
         *
         *
         * (sorted by alphabetical order)
         */
    field:
      doc: |
        /**
         * The different fields of Binary AST nodes, as per the specifications of
         * Binary AST.
         *
         * Usage:
         *
         * ```c++
         * #define WITH_FIELD(CPP_NAME, SPEC_NAME) ...
         * FOR_EACH_BIN_FIELD(WITH_FIELD)
         * ```
         *
         * (sorted by alphabetical order)
         */
    variants:
      doc: |
        /**
         * The different variants of Binary AST string enums, as per
         * the specifications of Binary AST, as a single macro and
         * `enum class`.
         *
         * Separate enum classes are also defined in BinASTParser.h.
         *
         * Usage:
         *
         * ```c++
         * #define WITH_VARIANT(CPP_NAME, SPEC_NAME) ...
         * FOR_EACH_BIN_VARIANT(WITH_VARIANT)
         * ```
         *
         * (sorted by alphabetical order)
         */

    header: |
      /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       * vim: set ts=8 sts=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/. */


      // To generate this file, see the documentation in
      // js/src/frontend/binast/README.md.

      #ifndef frontend_BinToken_h
      #define frontend_BinToken_h

      #include <stddef.h>

      /**
       * Definition of Binary AST tokens.
       *
       * In the Binary AST world, an AST is composed of nodes, where a node is
       * defined by:
       * - a Kind (see `BinKind`);
       * - a list of fields, where each field is:
       *    - a Name (see `BinField`);
       *    - a Value, which may be either a node or a primitive value.
       *
       * The mapping between Kind and list of fields is determined entirely by
       * the grammar of Binary AST. The mapping between (Kind, Name) and the
       * structure of Value is also determined entirely by the grammar of
       * Binary AST.
       *
       * As per the specifications of Binary AST, kinds may be added as the
       * language grows, but never removed. The mapping between Kind and list
       * of fields may also change to add new fields or make some fields optional,
       * but may never remove a field. Finally, the mapping between (Kind, Name)
       * and the structure of Value may be modified to add new possible values,
       * but never to remove a value.
       *
       * A Binary AST parser must be able to fail gracefully when confronted with
       * unknown Kinds or Names.
       */

      namespace js {
      namespace frontend {
    footer: |

      /**
       * Return a string describing a `BinKind`.
       */
      const char* describeBinKind(const BinKind& kind);

      /**
       * Return a string describing a `BinField`.
       */
      const char* describeBinField(const BinField& kind);

      /**
       * Return a string describing a `BinVariant`.
       */
      const char* describeBinVariant(const BinVariant& kind);

      } // namespace frontend
      } // namespace js

      #endif // frontend_BinToken_h

Arguments:
  init: |
    BINJS_TRY_DECL(result, handler_.newList(ParseNodeKind::Arguments,
                                            tokenizer_->pos(start)));
  append:
    handler_.addList(/* list = */ result, /* kid = */ item);

ArrayExpression:
  build: |
    if (elements->empty()) {
      elements->setHasNonConstInitializer();
    }
    auto result = elements;

AssertedBlockScope:
  type-ok:
    Ok
  init: |
    const auto scopeKind = AssertedScopeKind::Block;
  fields:
    declaredNames:
      extra-args: scopeKind
    hasDirectEval:
      after: |
        if (hasDirectEval) {
          pc_->sc()->setHasDirectEval();
          pc_->sc()->setBindingsAccessedDynamically();
        }
  build: |
    if (hasDirectEval && pc_->isFunctionBox() &&
        !pc_->sc()->strict()) {
      // In non-strict mode code, direct calls to eval can
      // add variables to the call object.
      pc_->functionBox()->setHasExtensibleScope();
    }
    auto result = Ok();

AssertedBoundName:
  type-ok:
    Ok
  extra-params: AssertedScopeKind scopeKind
  extra-args: scopeKind
  init: |
    const bool allowDuplicateName = false;
  fields:
    isCaptured:
      after: |
        ParseContext::Scope* scope;
        DeclarationKind declKind;
        MOZ_TRY(getBoundScope(scopeKind, scope, declKind));
  build: |
    MOZ_TRY(addScopeName(scopeKind, name, scope, declKind, isCaptured,
                         allowDuplicateName));
    auto result = Ok();

AssertedDeclaredName:
  inherits: AssertedBoundName
  fields:
    kind:
      after: |
        if (kind_ == AssertedDeclaredKind::NonConstLexical) {
          return raiseError("Let is not supported in this preview release");
        }
        if (kind_ == AssertedDeclaredKind::ConstLexical) {
          return raiseError("Const is not supported in this preview release");
        }
    isCaptured:
      after: |
        ParseContext::Scope* scope;
        DeclarationKind declKind;
        MOZ_TRY(getDeclaredScope(scopeKind, kind_, scope, declKind));

AssertedMaybePositionalParameterName:
  inherits: AssertedBoundName
  extra-params: |
    AssertedScopeKind scopeKind,
    MutableHandle<GCVector<JSAtom*>> positionalParams
  extra-args: |
    scopeKind, positionalParams
  sum-arms:
    AssertedRestParameterName:
      disabled: true
    AssertedParameterName:
      disabled: true

AssertedPositionalParameterName:
  inherits: AssertedMaybePositionalParameterName
  init: |
    bool allowDuplicateName = !pc_->sc()->strict();
  fields:
    name:
      after: |
        // `positionalParams` vector can be shorter than the actual
        // parameter length. Resize on demand.
        // (see also ListOfAssertedMaybePositionalParameterName)
        size_t prevLength = positionalParams.get().length();
        if (index >= prevLength) {
          // This is implementation limit, which is not in the spec.
          if (index >= ARGNO_LIMIT - 1) {
            return raiseError("AssertedPositionalParameterName.index is too big");
          }
          size_t newLength = index + 1;
          BINJS_TRY(positionalParams.get().resize(newLength));
          for (uint32_t i = prevLength; i < newLength; i++) {
            positionalParams.get()[i] = nullptr;
          }
        }

        if (positionalParams.get()[index]) {
          return raiseError("AssertedPositionalParameterName has duplicate entry for the same index");
        }
        positionalParams.get()[index] = name;

AssertedBoundNamesScope:
  inherits: AssertedBlockScope
  init: |
    const auto scopeKind = AssertedScopeKind::Catch;
  fields:
    boundNames:
      extra-args: scopeKind

AssertedParameterScope:
  inherits: AssertedBlockScope
  extra-params: |
    MutableHandle<GCVector<JSAtom*>> positionalParams
  extra-args: |
    positionalParams
  init: |
    const auto scopeKind = AssertedScopeKind::Parameter;
  fields:
    isSimpleParameterList:
      after: |
        (void) isSimpleParameterList;
    paramNames:
      extra-args: scopeKind, positionalParams

AssertedScriptGlobalScope:
  inherits: AssertedBlockScope
  init: |
    const auto scopeKind = AssertedScopeKind::Global;

AssertedVarScope:
  inherits: AssertedBlockScope
  init: |
    const auto scopeKind = AssertedScopeKind::Var;

AssignmentExpression:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newAssignment(ParseNodeKind::AssignExpr,
                                          binding, expression));

AssignmentTargetIdentifier:
  build: |
    BINJS_TRY(usedNames_.noteUse(cx_, name, pc_->scriptId(),
                                 pc_->innermostScope()->id()));
    BINJS_TRY_DECL(result,
                   handler_.newName(name->asPropertyName(),
                                    tokenizer_->pos(start), cx_));

BindingIdentifier:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newName(name->asPropertyName(),
                                    tokenizer_->pos(start), cx_));

BinaryExpression:
  build: |
    ParseNodeKind pnk;
    switch (operator_) {
      case BinaryOperator::Comma:
        pnk = ParseNodeKind::CommaExpr;
        break;
      case BinaryOperator::LogicalOr:
        pnk = ParseNodeKind::OrExpr;
        break;
      case BinaryOperator::LogicalAnd:
        pnk = ParseNodeKind::AndExpr;
        break;
      case BinaryOperator::BitOr:
        pnk = ParseNodeKind::BitOrExpr;
        break;
      case BinaryOperator::BitXor:
        pnk = ParseNodeKind::BitXorExpr;
        break;
      case BinaryOperator::BitAnd:
        pnk = ParseNodeKind::BitAndExpr;
        break;
      case BinaryOperator::Eq:
        pnk = ParseNodeKind::EqExpr;
        break;
      case BinaryOperator::Neq:
        pnk = ParseNodeKind::NeExpr;
        break;
      case BinaryOperator::StrictEq:
        pnk = ParseNodeKind::StrictEqExpr;
        break;
      case BinaryOperator::StrictNeq:
        pnk = ParseNodeKind::StrictNeExpr;
        break;
      case BinaryOperator::LessThan:
        pnk = ParseNodeKind::LtExpr;
        break;
      case BinaryOperator::LeqThan:
        pnk = ParseNodeKind::LeExpr;
        break;
      case BinaryOperator::GreaterThan:
        pnk = ParseNodeKind::GtExpr;
        break;
      case BinaryOperator::GeqThan:
        pnk = ParseNodeKind::GeExpr;
        break;
      case BinaryOperator::In:
        pnk = ParseNodeKind::InExpr;
        break;
      case BinaryOperator::Instanceof:
        pnk = ParseNodeKind::InstanceOfExpr;
        break;
      case BinaryOperator::Lsh:
        pnk = ParseNodeKind::LshExpr;
        break;
      case BinaryOperator::Rsh:
        pnk = ParseNodeKind::RshExpr;
        break;
      case BinaryOperator::Ursh:
        pnk = ParseNodeKind::UrshExpr;
        break;
      case BinaryOperator::Plus:
        pnk = ParseNodeKind::AddExpr;
        break;
      case BinaryOperator::Minus:
        pnk = ParseNodeKind::SubExpr;
        break;
      case BinaryOperator::Mul:
        pnk = ParseNodeKind::MulExpr;
        break;
      case BinaryOperator::Div:
        pnk = ParseNodeKind::DivExpr;
        break;
      case BinaryOperator::Mod:
        pnk = ParseNodeKind::ModExpr;
        break;
      case BinaryOperator::Pow:
        pnk = ParseNodeKind::PowExpr;
        break;
    }

    ParseNode* result;
    // ParseNodeKind::PowExpr is not left-associative
    if (left->isKind(pnk) && pnk != ParseNodeKind::PowExpr) {
      // Regroup left-associative operations into lists.
      left->template as<ListNode>().appendWithoutOrderAssumption(right);
      result = left;
    } else {
      BINJS_TRY_DECL(list, handler_.newList(pnk, tokenizer_->pos(start)));

      list->appendWithoutOrderAssumption(left);
      list->appendWithoutOrderAssumption(right);
      result = list;
    }

Block:
  init: |
    ParseContext::Statement stmt(pc_, StatementKind::Block);
    ParseContext::Scope currentScope(cx_, pc_, usedNames_);
    BINJS_TRY(currentScope.init(pc_));
  build: |
    MOZ_TRY(checkClosedVars(currentScope));
    BINJS_TRY_DECL(bindings,
                   NewLexicalScopeData(cx_, currentScope, alloc_, pc_));
    BINJS_TRY_DECL(result, handler_.newLexicalScope(*bindings, statements));

BreakStatement:
  fields:
    label:
      block:
        replace: |
          RootedAtom label(cx_);
          MOZ_TRY_VAR(label, tokenizer_->readMaybeAtom());

  build: |
    if (label) {
      if (!IsIdentifier(label)) {
        return raiseError("Invalid identifier");
      }
    }

    auto validity
        = pc_->checkBreakStatement(label ? label->asPropertyName() : nullptr);
    if (validity.isErr()) {
      switch (validity.unwrapErr()) {
        case ParseContext::BreakStatementError::ToughBreak:
          this->error(JSMSG_TOUGH_BREAK);
          return cx_->alreadyReportedError();
        case ParseContext::BreakStatementError::LabelNotFound:
          this->error(JSMSG_LABEL_NOT_FOUND);
          return cx_->alreadyReportedError();
      }
    }

    BINJS_TRY_DECL(result,
                   handler_.newBreakStatement(label ? label->asPropertyName()
                                                    : nullptr,
                                              tokenizer_->pos(start)));

CallExpression:
  build: |
    auto op = JSOP_CALL;

    // Try to optimize funcall and funapply at the bytecode level
    if (PropertyName* prop = handler_.maybeDottedProperty(callee)) {
      if (prop == cx_->names().apply) {
        op = JSOP_FUNAPPLY;
        if (pc_->isFunctionBox()) {
          pc_->functionBox()->usesApply = true;
        }
      } else if (prop == cx_->names().call) {
        op = JSOP_FUNCALL;
      }
    }

    // Check for direct calls to `eval`.
    if (handler_.isEvalName(callee, cx_)) {
      if (!pc_->varScope().lookupDeclaredNameForAdd(cx_->names().eval) &&
          !pc_->innermostScope()->lookupDeclaredNameForAdd(cx_->names().eval)) {
        // This is a direct call to `eval`.
        if (!pc_->sc()->hasDirectEval()) {
          return raiseMissingDirectEvalInAssertedScope();
        }

        op = pc_->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
      }
    }

    BINJS_TRY_DECL(result, handler_.newCall(callee, arguments));
    result->setOp(op);

CatchClause:
  type-ok:
    LexicalScopeNode*
  init: |
    ParseContext::Statement stmt(pc_, StatementKind::Catch);
    ParseContext::Scope currentScope(cx_, pc_, usedNames_);
    BINJS_TRY(currentScope.init(pc_));
  fields:
    binding:
      after: |
        if (!currentScope.lookupDeclaredName(binding->template as<NameNode>().atom())) {
          return raiseError("Missing catch variable in scope");
        }
  build: |
    MOZ_TRY(checkClosedVars(currentScope));
    BINJS_TRY_DECL(bindings,
                   NewLexicalScopeData(cx_, currentScope, alloc_, pc_));
    BINJS_TRY_DECL(result, handler_.newLexicalScope(*bindings, body));
    BINJS_TRY(handler_.setupCatchScope(result, binding, body));

CompoundAssignmentExpression:
  build: |
    ParseNodeKind pnk;
    switch (operator_){
      case CompoundAssignmentOperator::PlusAssign:
        pnk = ParseNodeKind::AddAssignExpr;
        break;
      case CompoundAssignmentOperator::MinusAssign:
        pnk = ParseNodeKind::SubAssignExpr;
        break;
      case CompoundAssignmentOperator::MulAssign:
        pnk = ParseNodeKind::MulAssignExpr;
        break;
      case CompoundAssignmentOperator::DivAssign:
        pnk = ParseNodeKind::DivAssignExpr;
        break;
      case CompoundAssignmentOperator::ModAssign:
        pnk = ParseNodeKind::ModAssignExpr;
        break;
      case CompoundAssignmentOperator::PowAssign:
        pnk = ParseNodeKind::PowAssignExpr;
        break;
      case CompoundAssignmentOperator::LshAssign:
        pnk = ParseNodeKind::LshAssignExpr;
        break;
      case CompoundAssignmentOperator::RshAssign:
        pnk = ParseNodeKind::RshAssignExpr;
        break;
      case CompoundAssignmentOperator::UrshAssign:
        pnk = ParseNodeKind::UrshAssignExpr;
        break;
      case CompoundAssignmentOperator::BitOrAssign:
        pnk = ParseNodeKind::BitOrAssignExpr;
        break;
      case CompoundAssignmentOperator::BitXorAssign:
        pnk = ParseNodeKind::BitXorAssignExpr;
        break;
      case CompoundAssignmentOperator::BitAndAssign:
        pnk = ParseNodeKind::BitAndAssignExpr;
        break;
    }
    BINJS_TRY_DECL(result, handler_.newAssignment(pnk, binding, expression));

ComputedMemberAssignmentTarget:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newPropertyByValue(object, expression,
                                               tokenizer_->offset()));

ComputedMemberExpression:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newPropertyByValue(object, expression,
                                               tokenizer_->offset()));

ConditionalExpression:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newConditional(test, consequent, alternate));

ContinueStatement:
  fields:
    label:
      block:
        replace: |
          RootedAtom label(cx_);
          MOZ_TRY_VAR(label, tokenizer_->readMaybeAtom());

  build: |
    if (label) {
      if (!IsIdentifier(label)) {
        return raiseError("ContinueStatement - Label MUST be an identifier");
      }
    }

    auto validity
        = pc_->checkContinueStatement(label ? label->asPropertyName() : nullptr);
    if (validity.isErr()) {
      switch (validity.unwrapErr()) {
        case ParseContext::ContinueStatementError::NotInALoop:
          this->error(JSMSG_BAD_CONTINUE);
          return cx_->alreadyReportedError();
        case ParseContext::ContinueStatementError::LabelNotFound:
          this->error(JSMSG_LABEL_NOT_FOUND);
          return cx_->alreadyReportedError();
      }
    }

    BINJS_TRY_DECL(result,
                   handler_.newContinueStatement(label ? label->asPropertyName()
                                                       : nullptr,
                                                 tokenizer_->pos(start)));

DataProperty:
  build: |
    if (!handler_.isUsableAsObjectPropertyName(name)) {
      return raiseError("DataProperty key kind");
    }

    ParseNode* result;
    if (name->template is<NameNode>() &&
        name->template as<NameNode>().atom() == cx_->names().proto) {
      BINJS_TRY_VAR(result,
                    handler_.newUnary(ParseNodeKind::MutateProto, start,
                                      expression));
    } else {
      BINJS_TRY_VAR(result,
                    handler_.newObjectMethodOrPropertyDefinition(name,
                                                                 expression,
                                                                 AccessorType::None));
    }

Directive:
  build: |
    TokenPos pos = tokenizer_->pos(start);
    BINJS_TRY_DECL(result, handler_.newStringLiteral(rawValue, pos));

DoWhileStatement:
  init:
    ParseContext::Statement stmt(pc_, StatementKind::DoLoop);
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newDoWhileStatement(body, test,
                                                tokenizer_->pos(start)));

EagerFunctionDeclaration:
  init: |
    const auto syntax = FunctionSyntaxKind::Statement;
  inherits: EagerFunctionExpression

LazyFunctionDeclaration:
  init: |
    const auto syntax = FunctionSyntaxKind::Statement;
  inherits: LazyFunctionExpression

FunctionExpressionContents:
  type-ok:
    Ok
  extra-params: |
    uint32_t funLength,
    ListNode** paramsOut,
    ListNode** bodyOut
  extra-args: |
    funLength, paramsOut, bodyOut
  fields:
    isThisCaptured:
      after: |
        // TODO: Use this in BinASTParser::buildFunction.
        (void) isThisCaptured;
    isFunctionNameCaptured:
      after: |
        // Per spec, isFunctionNameCaptured can be true for anonymous
        // function.  Check isFunctionNameCaptured only for named
        // function.
        if (pc_->functionBox()->function()->isNamedLambda() &&
          isFunctionNameCaptured) {
          captureFunctionName();
        }
    parameterScope:
      before: |
        Rooted<GCVector<JSAtom*>> positionalParams(cx_, GCVector<JSAtom*>(cx_));
      extra-args: |
        &positionalParams
    params:
      after: |
        MOZ_TRY(checkFunctionLength(funLength));
        MOZ_TRY(checkPositionalParameterIndices(positionalParams, params));
  build: |
    *paramsOut = params;
    *bodyOut = body;
    auto result = Ok();

FunctionOrMethodContents:
  inherits: FunctionExpressionContents

GetterContents:
  inherits: FunctionExpressionContents
  fields:
    body:
      before: |
        BINJS_TRY_DECL(params, handler_.newParamsBody(tokenizer_->pos(start)));

SetterContents:
  inherits: FunctionExpressionContents
  fields:
    param:
      after: |
        BINJS_TRY_DECL(params, handler_.newParamsBody(param->pn_pos));
        handler_.addList(params, param);
        MOZ_TRY(checkPositionalParameterIndices(positionalParams, params));

EagerFunctionExpression:
  init: |
    const auto syntax = FunctionSyntaxKind::Expression;
  fields:
    isGenerator:
      after: |
        if (isGenerator) {
          return raiseError("Generator is not supported in this preview release");
        }
    isAsync:
      after: |
        if (isAsync) {
          return raiseError("Async function is not supported in this preview release");
        }
    contents:
      before: |
        BINJS_MOZ_TRY_DECL(funbox, buildFunctionBox(
          isGenerator ? GeneratorKind::Generator
                      : GeneratorKind::NotGenerator,
          isAsync ? FunctionAsyncKind::AsyncFunction
                  : FunctionAsyncKind::SyncFunction,
          syntax,
          (syntax != FunctionSyntaxKind::Setter &&
           syntax != FunctionSyntaxKind::Getter) ? name : nullptr));

        forceStrictIfNecessary(funbox, directives);

        // Push a new ParseContext. It will be used to parse `scope`, the arguments,
        // the function.
        BinASTParseContext funpc(cx_, this, funbox, /* newDirectives = */ nullptr);
        BINJS_TRY(funpc.init());
        pc_->functionScope().useAsVarScope(pc_);
        MOZ_ASSERT(pc_->isFunctionBox());

        ParseContext::Scope lexicalScope(cx_, pc_, usedNames_);
        BINJS_TRY(lexicalScope.init(pc_));
        ListNode* params;
        ListNode* body;
      extra-args: |
        length, &params, &body
      after: |
        MOZ_TRY(prependDirectivesToBody(body, directives));
  build: |
    BINJS_TRY_DECL(lexicalScopeData,
                   NewLexicalScopeData(cx_, lexicalScope, alloc_, pc_));
    BINJS_TRY_DECL(bodyScope,
                   handler_.newLexicalScope(*lexicalScopeData, body));
    BINJS_MOZ_TRY_DECL(result,
                       buildFunction(start, kind, name, params, bodyScope));

LazyFunctionExpression:
  init: |
    const auto syntax = FunctionSyntaxKind::Expression;
  fields:
    isGenerator:
      after: |
        if (isGenerator) {
          return raiseError("Generator is not supported in this preview release");
        }
    isAsync:
      after: |
        if (isAsync) {
          return raiseError("Async function is not supported in this preview release");
        }
    contents:
      block:
        replace:
          // Don't parse the contents until we delazify.
  build: |
    BINJS_MOZ_TRY_DECL(funbox, buildFunctionBox(
      isGenerator ? GeneratorKind::Generator
                  : GeneratorKind::NotGenerator,
      isAsync ? FunctionAsyncKind::AsyncFunction
              : FunctionAsyncKind::SyncFunction,
      syntax, name));

    forceStrictIfNecessary(funbox, directives);

    RootedFunction fun(cx_, funbox->function());

    // TODO: This will become incorrect in the face of ES6 features.
    fun->setArgCount(length);

    auto skipStart = contentsSkip.startOffset();
    BINJS_TRY_DECL(lazy, LazyScript::Create(
      cx_, fun, sourceObject_,
      pc_->closedOverBindingsForLazy(),
      pc_->innerFunctionsForLazy,
      skipStart, skipStart + contentsSkip.length(),
      skipStart, 0, skipStart, ParseGoal::Script));

    if (funbox->strict()) {
      lazy->setStrict();
    }
    lazy->setIsBinAST();
    funbox->function()->initLazyScript(lazy);

    BINJS_MOZ_TRY_DECL(result, makeEmptyFunctionNode(skipStart, kind, funbox));

EagerGetter:
  init: |
    const auto syntax = FunctionSyntaxKind::Setter;
    const bool isGenerator = false;
    const bool isAsync = false;
    const auto accessorType = AccessorType::Getter;
    const uint32_t length = 0;
  inherits: EagerMethod

EagerMethod:
  init: |
    const auto syntax = FunctionSyntaxKind::Method;
    const auto accessorType = AccessorType::None;
  inherits: EagerFunctionExpression
  build: |
    BINJS_TRY_DECL(lexicalScopeData,
                   NewLexicalScopeData(cx_, lexicalScope, alloc_, pc_));
    BINJS_TRY_DECL(bodyScope,
                   handler_.newLexicalScope(*lexicalScopeData, body));
    BINJS_MOZ_TRY_DECL(method,
                       buildFunction(start, kind, name, params, bodyScope));
    BINJS_TRY_DECL(result,
                   handler_.newObjectMethodOrPropertyDefinition(name, method,
                                                                accessorType));

EagerSetter:
  init: |
    const auto syntax = FunctionSyntaxKind::Setter;
    const bool isGenerator = false;
    const bool isAsync = false;
    const auto accessorType = AccessorType::Setter;
  inherits: EagerMethod

EmptyStatement:
  build:
    BINJS_TRY_DECL(result, handler_.newEmptyStatement(tokenizer_->pos(start)));

ExpressionStatement:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newExprStatement(expression, tokenizer_->offset()));

ForInOfBinding:
  init:
    AutoVariableDeclarationKind kindGuard(this);
  build: |
    // Restored by `kindGuard`.
    variableDeclarationKind_ = kind_;
    MOZ_TRY(checkBinding(binding->template as<NameNode>().atom()->asPropertyName()));
    ParseNodeKind pnk;
    switch (kind_) {
      case VariableDeclarationKind::Var:
        pnk = ParseNodeKind::VarStmt;
        break;
      case VariableDeclarationKind::Let:
        return raiseError("Let is not supported in this preview release");
      case VariableDeclarationKind::Const:
        return raiseError("Const is not supported in this preview release");
    }
    BINJS_TRY_DECL(result,
                   handler_.newDeclarationList(pnk, tokenizer_->pos(start)));
    handler_.addList(result, binding);



ForInStatement:
  init: |
    ParseContext::Statement stmt(pc_, StatementKind::ForInLoop);

    // Implicit scope around the `for`, used to store `for (let x in  ...)`
    // or `for (const x in ...)`-style declarations. Detail on the
    // declaration is stored as part of `scope`.
    ParseContext::Scope scope(cx_, pc_, usedNames_);
    BINJS_TRY(scope.init(pc_));
  build: |
    BINJS_TRY_DECL(forHead,
                   handler_.newForInOrOfHead(ParseNodeKind::ForIn, left, right,
                                             tokenizer_->pos(start)));
    ParseNode* result;
    BINJS_TRY_VAR(result,
                  handler_.newForStatement(start, forHead, body,
                                            /* iflags = */ 0));

    if (!scope.isEmpty()) {
      BINJS_TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, pc_));
      BINJS_TRY_VAR(result, handler_.newLexicalScope(*bindings, result));
    }

FormalParameters:
  type-ok:
    ListNode*
  build: |
    auto result = items;
    if (rest) {
      return raiseError("Rest parameter is not supported in this preview release");
    }

ForStatement:
  init: |
    ParseContext::Statement stmt(pc_, StatementKind::ForLoop);

    // Implicit scope around the `for`, used to store `for (let x; ...; ...)`
    // or `for (const x; ...; ...)`-style declarations. Detail on the
    // declaration is stored as part of `BINJS_Scope`.
    ParseContext::Scope scope(cx_, pc_, usedNames_);
    BINJS_TRY(scope.init(pc_));
  build: |
    BINJS_TRY_DECL(forHead,
                   handler_.newForHead(init, test, update,
                                       tokenizer_->pos(start)));
    ParseNode* result;
    BINJS_TRY_VAR(result,
                  handler_.newForStatement(start, forHead, body,
                                           /* iflags = */ 0));

    if (!scope.isEmpty()) {
      BINJS_TRY_DECL(bindings,
                     NewLexicalScopeData(cx_, scope, alloc_, pc_));
      BINJS_TRY_VAR(result, handler_.newLexicalScope(*bindings, result));
    }

FunctionBody:
  inherits: ListOfStatement

IdentifierExpression:
  build: |
    BINJS_TRY(usedNames_.noteUse(cx_, name, pc_->scriptId(),
                                 pc_->innermostScope()->id()));
    BINJS_TRY_DECL(result,
                   handler_.newName(name->asPropertyName(),
                                    tokenizer_->pos(start), cx_));

IfStatement:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newIfStatement(start, test, consequent, alternate));

LabelledStatement:
  fields:
    label:
      after: |
        if (!IsIdentifier(label)) {
          return raiseError("Invalid identifier");
        }
        ParseContext::LabelStatement stmt(pc_, label);
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newLabeledStatement(label->asPropertyName(), body,
                                                start));

ListOfAssertedBoundName:
  extra-params: AssertedScopeKind scopeKind
  extra-args: scopeKind
  type-ok:
    Ok
  init: |
    (void) start;
    auto result = Ok();
  append: |
    // Nothing to do here.

ListOfAssertedMaybePositionalParameterName:
  inherits: ListOfAssertedBoundName
  extra-params: |
    AssertedScopeKind scopeKind,
    MutableHandle<GCVector<JSAtom*>> positionalParams
  extra-args: |
    scopeKind, positionalParams
  init: |
    (void) start;
    auto result = Ok();
    // This list contains also destructuring parameters, and the number of
    // list items can be greater than the actual parameters, or more than
    // ARGNO_LIMIT even if the number of parameters fits into ARGNO_LIMIT.
    // Also, the number of parameters can be greater than this list's length
    // if one of destructuring parameter is empty.
    //
    // We resize `positionalParams` vector on demand, to keep the vector
    // length match to the known maximum positional parameter index + 1.

ListOfAssertedDeclaredName:
  inherits: ListOfAssertedBoundName

ListOfDirective:
  type-ok:
    ListNode*
  init:
    BINJS_TRY_DECL(result, handler_.newStatementList(tokenizer_->pos(start)));
  append:
    handler_.addStatementToList(result, item);

ListOfObjectProperty:
  type-ok:
    ListNode*
  init:
    BINJS_TRY_DECL(result, handler_.newObjectLiteral(start));
  append: |
    if (!item->isConstant())
      result->setHasNonConstInitializer();
    result->appendWithoutOrderAssumption(item);

ListOfOptionalSpreadElementOrExpression:
  type-ok:
    ListNode*
  init:
    BINJS_TRY_DECL(result, handler_.newArrayLiteral(start));
  append: |
    if (item) {
      handler_.addArrayElement(result, item); // Infallible.
    } else {
      BINJS_TRY(handler_.addElision(result, tokenizer_->pos(start)));
    }

ListOfParameter:
  type-ok:
    ListNode*
  init: |
    BINJS_TRY_DECL(result, handler_.newParamsBody(tokenizer_->pos(start)));
  append:
    handler_.addList(/* list = */ result, /* kid = */ item);

ListOfStatement:
  type-ok:
    ListNode*
  init:
    BINJS_TRY_DECL(result, handler_.newStatementList(tokenizer_->pos(start)));
  append:
    handler_.addStatementToList(result, item);


#ListOfSpreadElementOrExpression:
#  type-ok:
#    ListNode*
#  init:
#    BINJS_TRY_DECL(result, handler_.newParamsBody(tokenizer_->pos()));
#  append:
#    result->appendWithoutOrderAssumption(item);

ListOfSwitchCase:
  type-ok:
    ListNode*
  init:
    BINJS_TRY_DECL(result, handler_.newStatementList(tokenizer_->pos(start)));
  append:
    handler_.addCaseStatementToList(result, item);

ListOfVariableDeclarator:
  extra-params: ParseNodeKind declarationListKind
  type-ok:
    ListNode*
  init: |
    BINJS_TRY_DECL(result,
                   handler_.newDeclarationList(declarationListKind,
                   tokenizer_->pos(start)));

LiteralBooleanExpression:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newBooleanLiteral(value, tokenizer_->pos(start)));

LiteralNumericExpression:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newNumber(value, DecimalPoint::HasDecimal, tokenizer_->pos(start)));

LiteralNullExpression:
  build: |
    BINJS_TRY_DECL(result, handler_.newNullLiteral(tokenizer_->pos(start)));

LiteralPropertyName:
  build: |
    ParseNode* result;
    uint32_t index;
    if (value->isIndex(&index)) {
      BINJS_TRY_VAR(result,
                    handler_.newNumber(index, NoDecimal,
                                       TokenPos(start, tokenizer_->offset())));
    } else {
      BINJS_TRY_VAR(result,
                    handler_.newObjectLiteralPropertyName(value,
                                                          tokenizer_->pos(start)));
    }

LiteralRegExpExpression:
  fields:
    flags:
      block:
        replace: |
          Chars flags(cx_);
          MOZ_TRY(tokenizer_->readChars(flags));
  build: |
    RegExpFlag reflags = NoFlags;
    for (auto c : flags) {
      if (c == 'g' && !(reflags & GlobalFlag)) {
        reflags = RegExpFlag(reflags | GlobalFlag);
      } else if (c == 'i' && !(reflags & IgnoreCaseFlag)) {
        reflags = RegExpFlag(reflags | IgnoreCaseFlag);
      } else if (c == 'm' && !(reflags & MultilineFlag)) {
        reflags = RegExpFlag(reflags | MultilineFlag);
      } else if (c == 'y' && !(reflags & StickyFlag)) {
        reflags = RegExpFlag(reflags | StickyFlag);
      } else if (c == 'u' && !(reflags & UnicodeFlag)) {
        reflags = RegExpFlag(reflags | UnicodeFlag);
      } else {
        return raiseError("Invalid regexp flags");
      }
    }


    Rooted<RegExpObject*> reobj(cx_);
    BINJS_TRY_VAR(reobj,
                  RegExpObject::create(cx_, pattern, reflags, TenuredObject));

    BINJS_TRY_DECL(result,
                   handler_.newRegExp(reobj, tokenizer_->pos(start), *this));

LiteralStringExpression:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newStringLiteral(value, tokenizer_->pos(start)));

NewExpression:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newNewExpression(tokenizer_->pos(start).begin,
                                             callee, arguments));

ObjectExpression:
  build:
    auto result = properties;

OptionalCatchClause:
  type-ok:
    LexicalScopeNode*

Parameter:
  sum-arms:
    BindingIdentifier:
      after: |
        if (!pc_->positionalFormalParameterNames().append(
                result->template as<NameNode>().atom())) {
          return raiseOOM();
        }
        if (pc_->isFunctionBox()) {
          pc_->functionBox()->length++;
        }

ReturnStatement:
  init: |
    if (!pc_->isFunctionBox()) {
      // Return statements are permitted only inside functions.
      return raiseInvalidKind("Toplevel Statement", kind);
    }

    pc_->functionBox()->usesReturn = true;
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newReturnStatement(expression,
                                               tokenizer_->pos(start)));

Script:
  fields:
    directives:
      after: |
        forceStrictIfNecessary(pc_->sc(), directives);
  build: |
    MOZ_TRY(checkClosedVars(pc_->varScope()));
    MOZ_TRY(prependDirectivesToBody(/* body = */ statements, directives));
    auto result = statements;

ShorthandProperty:
  build: |
    MOZ_ASSERT(name->isKind(ParseNodeKind::Name));
    MOZ_ASSERT(!handler_.isUsableAsObjectPropertyName(name));
    BINJS_TRY_DECL(propName,
                   handler_.newObjectLiteralPropertyName(name->template as<NameNode>().name(),
                                                         tokenizer_->pos(start)));

    BINJS_TRY_DECL(result,
                   handler_.newShorthandPropertyDefinition(propName, name));

SwitchCase:
  type-ok:
    CaseClause*
  build: |
    BINJS_TRY_DECL(result, handler_.newCaseOrDefault(start, test, consequent));

SwitchDefault:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newCaseOrDefault(start, nullptr, consequent));

SwitchStatement:
  fields:
    discriminant:
      after: |
        ParseContext::Statement stmt(pc_, StatementKind::Switch);
  build: |
    BINJS_TRY_DECL(scope, handler_.newLexicalScope(nullptr, cases));
    BINJS_TRY_DECL(result,
                   handler_.newSwitchStatement(start, discriminant, scope,
                                               /* hasDefault = */ false));

SwitchStatementWithDefault:
  fields:
    discriminant:
      after: |
        ParseContext::Statement stmt(pc_, StatementKind::Switch);
  build: |
    // Concatenate `preDefaultCase`, `defaultCase`, `postDefaultCase`
    auto cases = preDefaultCases;
    handler_.addList(cases, defaultCase);
    ParseNode* iter = postDefaultCases->head();
    while (iter) {
      ParseNode* next = iter->pn_next;
      handler_.addList(cases, iter);
      iter = next;
    }
    BINJS_TRY_DECL(scope, handler_.newLexicalScope(nullptr, cases));
    BINJS_TRY_DECL(result,
                   handler_.newSwitchStatement(start, discriminant, scope,
                                               /* hasDefault = */ true));

StaticMemberAssignmentTarget:
  init:
    size_t nameStart;
  fields:
    property:
      block:
        before: |
          nameStart = tokenizer_->offset();
  build: |
    BINJS_TRY_DECL(name,
                   handler_.newPropertyName(property->asPropertyName(),
                                            tokenizer_->pos(nameStart)));
    BINJS_TRY_DECL(result, handler_.newPropertyAccess(object, name));

StaticMemberExpression:
  init:
    size_t nameStart;
  fields:
    property:
      block:
        before: |
          nameStart = tokenizer_->offset();
  build: |
    BINJS_TRY_DECL(name,
                   handler_.newPropertyName(property->asPropertyName(),
                   tokenizer_->pos(nameStart)));
    BINJS_TRY_DECL(result, handler_.newPropertyAccess(object, name));

ThisExpression:
  build: |
    if (pc_->isFunctionBox()) {
      pc_->functionBox()->usesThis = true;
    }

    TokenPos pos = tokenizer_->pos(start);
    ParseNode* thisName(nullptr);
    if (pc_->sc()->thisBinding() == ThisBinding::Function) {
      HandlePropertyName dotThis = cx_->names().dotThis;
      BINJS_TRY(usedNames_.noteUse(cx_, dotThis, pc_->scriptId(),
                                   pc_->innermostScope()->id()));
      BINJS_TRY_VAR(thisName, handler_.newName(dotThis, pos, cx_));
    }

    BINJS_TRY_DECL(result, handler_.newThisLiteral(pos, thisName));

ThrowStatement:
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newThrowStatement(expression,
                                              tokenizer_->pos(start)));

TryCatchStatement:
  fields:
    body:
      block:
        declare:
          ParseNode* body;
        before: |
          ParseContext::Statement stmt(pc_, StatementKind::Try);
          ParseContext::Scope scope(cx_, pc_, usedNames_);
          BINJS_TRY(scope.init(pc_));
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newTryStatement(start, body, catchClause,
                                            /* finallyBlock = */ nullptr));

TryFinallyStatement:
  fields:
    body:
      block:
        declare:
          ParseNode* body;
        before: |
          ParseContext::Statement stmt(pc_, StatementKind::Try);
          ParseContext::Scope scope(cx_, pc_, usedNames_);
          BINJS_TRY(scope.init(pc_));
    finalizer:
      block:
        declare:
          ParseNode* finalizer;
        before: |
          ParseContext::Statement stmt(pc_, StatementKind::Finally);
          ParseContext::Scope scope(cx_, pc_, usedNames_);
          BINJS_TRY(scope.init(pc_));
  build: |
    BINJS_TRY_DECL(result,
                   handler_.newTryStatement(start, body, catchClause,
                                            finalizer));

UnaryExpression:
  build: |
    ParseNodeKind pnk;
    switch (operator_) {
      case UnaryOperator::Minus:
        pnk = ParseNodeKind::NegExpr;
        break;
      case UnaryOperator::Plus:
        pnk = ParseNodeKind::PosExpr;
        break;
      case UnaryOperator::Not:
        pnk = ParseNodeKind::NotExpr;
        break;
      case UnaryOperator::BitNot:
        pnk = ParseNodeKind::BitNotExpr;
        break;
      case UnaryOperator::Typeof: {
        if (operand->isKind(ParseNodeKind::Name)) {
          pnk = ParseNodeKind::TypeOfNameExpr;
        } else {
          pnk = ParseNodeKind::TypeOfExpr;
        }
        break;
      }
      case UnaryOperator::Void:
        pnk = ParseNodeKind::VoidExpr;
        break;
      case UnaryOperator::Delete: {
        switch (operand->getKind()) {
          case ParseNodeKind::Name:
            operand->setOp(JSOP_DELNAME);
            pnk = ParseNodeKind::DeleteNameExpr;
            BINJS_TRY(this->strictModeError(JSMSG_DEPRECATED_DELETE_OPERAND));
            pc_->sc()->setBindingsAccessedDynamically();
            break;
          case ParseNodeKind::DotExpr:
            pnk = ParseNodeKind::DeletePropExpr;
            break;
          case ParseNodeKind::ElemExpr:
            pnk = ParseNodeKind::DeleteElemExpr;
            break;
          default:
            pnk = ParseNodeKind::DeleteExpr;
        }
        break;
      }
    }
    BINJS_TRY_DECL(result, handler_.newUnary(pnk, start, operand));

UpdateExpression:
  build: |
    ParseNodeKind pnk;
    switch (operator_) {
      case UpdateOperator::Incr:
        pnk = isPrefix ? ParseNodeKind::PreIncrementExpr
                       : ParseNodeKind::PostIncrementExpr;
        break;
      case UpdateOperator::Decr:
        pnk = isPrefix ? ParseNodeKind::PreDecrementExpr
                       : ParseNodeKind::PostDecrementExpr;
        break;
    }
    BINJS_TRY_DECL(result, handler_.newUnary(pnk, start, operand));

VariableDeclaration:
  init:
    AutoVariableDeclarationKind kindGuard(this);

  fields:
    kind:
      after: |
        // Restored by `kindGuard`.
        variableDeclarationKind_ = kind_;
        ParseNodeKind declarationListKind;
        switch (kind_) {
        case VariableDeclarationKind::Var:
          declarationListKind = ParseNodeKind::VarStmt;
          break;
        case VariableDeclarationKind::Let:
          return raiseError("Let is not supported in this preview release");
        case VariableDeclarationKind::Const:
          return raiseError("Const is not supported in this preview release");
        }
    declarators:
      extra-args: declarationListKind

  build: |
    // By specification, the list may not be empty.
    if (declarators->empty()) {
      return raiseEmpty("VariableDeclaration");
    }

    auto result = declarators;

VariableDeclarator:
  build: |
    ParseNode* result;
    if (binding->isKind(ParseNodeKind::Name)) {
      // `var foo [= bar]``
      NameNode* bindingNameNode = &binding->template as<NameNode>();
      MOZ_TRY(checkBinding(bindingNameNode->atom()->asPropertyName()));
      if (init) {
        BINJS_TRY_VAR(result,
                      handler_.finishInitializerAssignment(bindingNameNode, init));
      } else {
        result = bindingNameNode;
      }
    } else {
      // `var pattern = bar`
      if (!init) {
        // Here, `init` is required.
        return raiseMissingField("VariableDeclarator (with non-trivial pattern)",
                                 BinField::Init);
      }

      MOZ_CRASH("Unimplemented: AssertedScope check for BindingPattern variable declaration");
      BINJS_TRY_VAR(result,
                    handler_.newAssignment(ParseNodeKind::AssignExpr, binding,
                                           init));
    }

WhileStatement:
  init:
    ParseContext::Statement stmt(pc_, StatementKind::WhileLoop);
  build:
    BINJS_TRY_DECL(result, handler_.newWhileStatement(start, test, body));

WithStatement:
  fields:
    body:
      before: |
        ParseContext::Statement stmt(pc_, StatementKind::With);
  build: |
    pc_->sc()->setBindingsAccessedDynamically();
    BINJS_TRY_DECL(result, handler_.newWithStatement(start, object, body));