js/src/frontend/BinSource.cpp
author David Teller <dteller@mozilla.com>
Mon, 11 Sep 2017 16:54:48 +0200
changeset 662392 af9317e628a83f91b79435bda551b96ef8a70a3d
permissions -rw-r--r--
Bug 1377007 - Implementing basic binjs-ref parser in SpiderMonkey;r?jorendorff,arai This patch implements a Binary AST parser matching the latest binjs-ref parser at this date. The subset of JS recognized matches ES5, with an AST based on a slightly customized Babylon AST. At this stage, the parser trusts its input, insofar as it does not check directives or bindings. Followup patch will introduce checking of these directives/bindings. MozReview-Commit-ID: 1nt230rt02R

#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/BinSource.h>
#include <frontend/FullParseHandler.h>
#include <frontend/Parser.h>
#include <frontend/ParseNode.h>
#include <frontend/BinTokenReaderTester.h>

#include <vm/RegExpObject.h>

using namespace mozilla;
using NameBag = GCHashSet<JSString*>;
using Names = GCVector<JSString*, 8>;
using UsedNamePtr = js::frontend::UsedNameTracker::UsedNameMap::Ptr;

namespace js {
namespace frontend {

using UniqueNode = mozilla::UniquePtr<ParseNode, ParseNodeDeleter>;
using BinFields = BinTokenReaderTester::BinFields;
using AutoList = BinTokenReaderTester::AutoList;
using AutoTaggedTuple = BinTokenReaderTester::AutoTaggedTuple;
using AutoTuple = BinTokenReaderTester::AutoTuple;
using Chars = BinTokenReaderTester::Chars;

namespace {

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

// Copied from Parser.cpp

template <typename Scope>
static typename Scope::Data*
NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc, uint32_t numBindings)
{
    size_t allocSize = Scope::sizeOfData(numBindings);
    typename Scope::Data* bindings = static_cast<typename Scope::Data*>(alloc.alloc(allocSize));
    if (!bindings) {
        ReportOutOfMemory(cx);
        return nullptr;
    }
    PodZero(bindings);
    return bindings;
}

Maybe<UniqueNode>
BinASTParser::parse(const Vector<char>& data) {
    return this->parse(data.begin(), data.length());
}

Maybe<UniqueNode>
BinASTParser::parse(const char* start, const size_t length) {
    tokenizer.emplace(this->cx, start, length);

    Directives directives(options().strictOption);
    GlobalSharedContext globalsc(this->cx, ScopeKind::Global,
                                 directives, options().extraWarningsOption);
    BinParseContext globalpc(this->cx, this, &globalsc, /* newDirectives = */ nullptr);
    if (!globalpc.init())
        return Nothing();

    ParseContext::VarScope varScope(this->cx, &globalpc, usedNames);
    if (!varScope.init(&globalpc))
        return Nothing();

    UniqueNode result(nullptr, this->nodeFree);
    if (!this->parseProgram(result))
        return Nothing();

    if (!tokenizer->checkStatus())
        return Nothing();

    return Move(Some(Move(result)));
}

bool
BinASTParser::parseProgram(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("Program");

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind == BinKind::BINJS_NULL)
        return true;

    if (kind != BinKind::PROGRAM)
        return this->raiseInvalidKind("Program", kind);

    if (!this->parseBlockStatementAux(kind, fields, out))
        return false;

    return true;
}

bool
BinASTParser::parseBlockStatement(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("BlockStatement");

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    UniqueNode block(nullptr, this->nodeFree);
    switch (kind) {
        case BinKind::BINJS_NULL:
            return true;
        case BinKind::BLOCK_STATEMENT:
            if (!this->parseBlockStatementAux(kind, fields, block))
                return false;
            break;
        default:
            return this->raiseInvalidKind("BlockStatement", kind);
    }

    out = Move(block);
    return true;
}

bool
BinASTParser::parseScope(ScopeData& out)
{
    if (out.isSome())
        return this->raiseAlreadyParsed("Scope");

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind == BinKind::BINJS_NULL)
        return true;

    if (kind != BinKind::BINJS_SCOPE)
        return this->raiseInvalidKind("Scope", kind);

    for (auto field: fields) {
        switch (field) {
            case BinField::BINJS_HAS_DIRECT_EVAL:
                if (!this->readBool(out.hasDirectEval))
                    return false;
                break;
            case BinField::BINJS_LET_DECL_NAMES:
                if (!this->parseStringList(&out.letNames))
                    return false;
                break;
            case BinField::BINJS_CONST_DECL_NAMES:
                if (!this->parseStringList(&out.constNames))
                    return false;
                break;
            case BinField::BINJS_VAR_DECL_NAMES:
                if (!this->parseStringList(&out.varNames))
                    return false;
                break;
            case BinField::BINJS_CAPTURED_NAMES:
                if (!this->parseStringSet(&out.capturedNames))
                    return false;
                break;
            default:
                return this->raiseInvalidField("Scope", field);
        }
    }

   return true;
}

bool
BinASTParser::parseBlockStatementAux(const BinKind name, const BinFields& fields, UniqueNode& out)
{
    UniqueNode body(nullptr, this->nodeFree);
    UniqueNode directives(nullptr, this->nodeFree); // Ignored
    ScopeData scope(this->cx);

    for (auto field: fields) {
        switch (field) {
            case BinField::BINJS_SCOPE:
                if (!this->parseScope(scope))
                    return false;
                break;
            case BinField::BODY:
                if (!this->parseStatementList(body))
                    return false;
                break;
            case BinField::DIRECTIVES:
                if (!this->parseDirectiveList(directives))
                    return false;
                break;
            default:
                return this->raiseInvalidField("BlockStatement", field);
        }
    }

    // Check if all fields have been parsed.
    if (!body || !scope.isSome())
        return this->raiseMissingField("BlockStatement");

    if (scope.hasLexNames()) {
        if (!this->promoteToLexicalScope(body))
            return false;

        if (!this->storeLexicalScope(body, Move(scope)))
            return false;
    }

    out = Move(body);
    return true;
}

bool
BinASTParser::storeLexicalScope(UniqueNode& body, ScopeData&& scope) {
    LexicalScope::Data* bindings = body->pn_u.scope.bindings;
    bindings->length = 0;

    BindingName* cursor = bindings->names;
    for (auto& name: *scope.letNames.get()) {
        JS::Rooted<JSAtom*> atom(cx, AtomizeString(this->cx, name));
        if (!atom)
            return this->raiseOOM();

        bool isCaptured = scope.capturedNames->has(name);
        BindingName binding(atom, isCaptured);
        PodCopy(cursor, &binding, 1);
        cursor++;
        bindings->length++; // Augment progressively in case we need to return early because of an error.
    }
    bindings->constStart = bindings->length;
    for (auto& name: *scope.constNames.get()) {
        JS::Rooted<JSAtom*> atom(cx, AtomizeString(this->cx, name));
        if (!atom)
            return this->raiseOOM();

        bool isCaptured = scope.capturedNames->has(name);
        BindingName binding(atom, isCaptured);
        PodCopy(cursor, &binding, 1);
        cursor++;
        bindings->length++;
    }

    return true;
}

bool
BinASTParser::promoteToLexicalScope(UniqueNode& node) {
    if (node->isKind(PNK_LEXICALSCOPE))
        return true;

    js::UniquePtr<LexicalScope::Data> bindings(NewEmptyBindingData<LexicalScope>(this->cx, this->alloc, /*number*/ 0));
    if (!bindings)
        return this->raiseOOM();

    bindings->constStart = 0;
    UniqueNode result(factory.newLexicalScope(bindings.get(), node.get()), this->nodeFree);
    if (!result)
        return this->raiseOOM();

    Unused << node.release();
    Unused << bindings.release();

    node = Move(result);
    return true;
}

bool
BinASTParser::parseStringSet(MutableHandle<mozilla::Maybe<NameBag>> out) {
    if (out.get()) {
        return this->raiseAlreadyParsed("{String}");
    }
    uint32_t length;
    AutoList guard(*tokenizer);

    NameBag result(this->cx);
    if (!result.init())
        return this->raiseOOM();

    if (!tokenizer->readList(length, guard))
        return false;

    for (uint32_t i = 0; i < length; ++i) {
        RootedString string(this->cx);
        if (!this->readString(&string))
            return false;

        if (!result.put(Move(string)))
            return this->raiseOOM();
    }

    out.set(Move(Some(Move(result))));
    return true;
}

bool
BinASTParser::parseStringList(MutableHandle<Maybe<Names>> out) {
    if (out.get())
        return this->raiseAlreadyParsed("[String]");

    uint32_t length;
    AutoList guard(*tokenizer);

    Names result(this->cx);

    if (!tokenizer->readList(length, guard))
        return false;

    if (!result.reserve(length))
        return this->raiseOOM();

    for (uint32_t i = 0; i < length; ++i) {
        RootedString string(this->cx);
        if (!this->readString(&string))
            return false;

        MOZ_ALWAYS_TRUE(result.append(Move(string))); // Checked in the call to `reserve`.
    }

    out.set(Move(Some(Move(result))));
    return true;
}

bool
BinASTParser::parseStatementList(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("[Statement]");

    uint32_t length;
    AutoList guard(*tokenizer);

    TokenPos pos;
    tokenizer->latestTokenPos(pos);
    UniqueNode result(factory.newStatementList(pos), this->nodeFree);
    if (!result)
        return this->raiseOOM();

    if (!tokenizer->readList(length, guard))
        return false;

    for (uint32_t i = 0; i < length; ++i) {
        UniqueNode statement(nullptr, this->nodeFree);
        if (!this->parseStatement(statement))
            return false;

        result->append(statement.release()); // `result` knows how to deallocate `statement`.
    }

    result.swap(out);

    return true;
}

bool
BinASTParser::parseStatement(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("Statement");

    const size_t start = tokenizer->offset();

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    switch (kind) {
        case BinKind::BINJS_NULL:
            return true;
        case BinKind::EMPTY_STATEMENT: {
            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newEmptyStatement(pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        case BinKind::BLOCK_STATEMENT: {
            UniqueNode body(nullptr, this->nodeFree);
            if (!this->parseBlockStatementAux(kind, fields, body))
                return false;

            if (body) {
                if (!this->promoteToLexicalScope(body))
                    return false;
            }
            out = Move(body);
            break;
        }
        case BinKind::EXPRESSION_STATEMENT: {
            UniqueNode body(nullptr, this->nodeFree);
            if (!this->parseExpressionStatementAux(kind, fields, body))
                return false;

            out = Move(body);
            break;
        }
        case BinKind::DEBUGGER_STATEMENT: {
            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newDebuggerStatement(pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        case BinKind::WITH_STATEMENT: {
            UniqueNode body(nullptr, this->nodeFree);
            UniqueNode expr(nullptr, this->nodeFree);
            for (auto field: fields) {
                switch (field) {
                    case BinField::BODY:
                        if (!this->parseStatement(body))
                            return false;
                        break;
                    case BinField::OBJECT:
                        if (!this->parseExpression(expr))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("WithStatement", field);
                }
            }
            if (!body || !expr)
                return this->raiseMissingField("WithStatement");

            UniqueNode result(factory.newWithStatement(start, expr.get(), body.get()), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            Unused << expr.release();
            Unused << body.release();

            out = Move(result);
            break;
        }
        case BinKind::RETURN_STATEMENT: {
            UniqueNode arg(nullptr, this->nodeFree);
            for (auto field: fields) {
                switch (field) {
                    case BinField::ARGUMENT:
                        if (!this->parseExpression(arg))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("ReturnStatement", field);
                }
            }

            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newReturnStatement(arg.get(), pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            Unused << arg.release();
            out = Move(result);
            break;
        }
        case BinKind::LABELED_STATEMENT: {
            UniqueNode label(nullptr, this->nodeFree);
            UniqueNode body(nullptr, this->nodeFree);

            for (auto field: fields) {
                switch (field) {
                    case BinField::LABEL:
                        if (!this->parsePattern(label))
                            return false;

                        if (!label || !label->isKind(PNK_NAME))
                            return this->raiseError("Label MUST be an identifier");

                        break;
                    case BinField::BODY:
                        if (!this->parseStatement(body))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("LabeledStatement", field);
                }
            }

            if (!label || !body)
                return this->raiseMissingField("LabeledStatement");

            UniqueNode result(factory.newLabeledStatement(label->name(), body.get(), start), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            Unused << body.release();
            out = Move(result);
            break;
    }
    case BinKind::BREAK_STATEMENT: MOZ_FALLTHROUGH;
    case BinKind::CONTINUE_STATEMENT: {

        UniqueNode label(nullptr, this->nodeFree);

        for (auto field: fields) {
            switch (field) {
                case BinField::LABEL:
                    if (!this->parsePattern(label))
                        return false;

                    if (label && !label->isKind(PNK_NAME))
                        return this->raiseError("ContinueStatement - Label MUST be an identifier");

                    break;
                default:
                    return this->raiseInvalidField("ContinueStatement", field);
            }
        }

        if (kind == BinKind::BREAK_STATEMENT) {
            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newBreakStatement(label ? label->name() : nullptr, pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
        } else {
            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newContinueStatement(label ? label->name() : nullptr, pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
        }

        break;

    }
    case BinKind::IF_STATEMENT: {

        UniqueNode test(nullptr, this->nodeFree);
        UniqueNode consequent(nullptr, this->nodeFree);
        UniqueNode alternate(nullptr, this->nodeFree); // Optional

        for (auto field: fields) {
            switch (field) {
                case BinField::TEST: {
                    if (!this->parseExpression(test))
                        return false;
                    break;
                }
                case BinField::CONSEQUENT: {
                    if (!this->parseStatement(consequent))
                        return false;
                    break;
                }
                case BinField::ALTERNATE: {
                    if (!this->parseStatement(alternate))
                        return false;
                    break;
                }
                default:
                    return this->raiseInvalidField("IfStatement", field);
            }
        }

        if (!test || !consequent)
            return this->raiseMissingField("IfStatement");

        UniqueNode result(factory.newIfStatement(start, test.get(), consequent.get(), alternate.get()), this->nodeFree);
        if (!result)
            return this->raiseOOM();

        Unused << test.release();
        Unused << consequent.release();
        Unused << alternate.release();

        out = Move(result);
        break;
    }
    case BinKind::SWITCH_STATEMENT: {

        UniqueNode discriminant(nullptr, this->nodeFree);
        UniqueNode cases(nullptr, this->nodeFree);

        for (auto field: fields) {
            switch (field) {
                case BinField::DISCRIMINANT: {
                    if (!this->parseExpression(discriminant))
                        return false;
                    break;
                }
                case BinField::CASES: {
                    if (!this->parseSwitchCaseList(cases))
                        return false;
                    break;
                }
                default:
                    return this->raiseInvalidField("SwitchStatement", field);
            }
        }

        if (!discriminant || !cases)
            return this->raiseMissingField("SwtichStatement");

        UniqueNode result(factory.newSwitchStatement(start, discriminant.get(), cases.get()), this->nodeFree);
        if (!result)
            return this->raiseOOM();

        Unused << discriminant.release();
        Unused << cases.release();
        out = Move(result);
        break;
    }

    case BinKind::THROW_STATEMENT: {

        UniqueNode arg(nullptr, this->nodeFree);
        for (auto field: fields) {
            if (field == BinField::ARGUMENT) {
                if (!this->parseExpression(arg))
                    return false;
            } else {
                return this->raiseInvalidField("ThrowStatement", field);
            }
        }

        if (!arg)
            return this->raiseMissingField("ThrowStatement");

        TokenPos pos(start, tokenizer->offset());
        UniqueNode result(factory.newThrowStatement(arg.get(), pos), this->nodeFree);
        if (!result)
            return this->raiseOOM();

        Unused << arg.release();
        out = Move(result);
        break;
    }

    case BinKind::TRY_STATEMENT: {

        UniqueNode block(nullptr, this->nodeFree);
        UniqueNode handler(nullptr, this->nodeFree);
        UniqueNode finalizer(nullptr, this->nodeFree);

        for (auto field: fields) {
            switch (field) {
                case BinField::BLOCK:
                    if (!this->parseBlockStatement(block))
                        return false;
                    break;
                case BinField::HANDLER:
                    if (!this->parseCatchClause(handler))
                        return false;
                    break;
                case BinField::FINALIZER:
                    if (!this->parseBlockStatement(finalizer))
                        return false;
                    break;
                default:
                    return this->raiseInvalidField("TryStatement", field);
            }
        }

        if (!block || (!handler && !finalizer))
            return this->raiseMissingField("TryStatement");

        if (!this->promoteToLexicalScope(block))
            return false;

        if (finalizer) {
            if (!this->promoteToLexicalScope(finalizer))
                return false;
        }

        UniqueNode result(factory.newTryStatement(start, block.get(), handler.get(), finalizer.get()), this->nodeFree);
        if (!result)
            return this->raiseOOM();

        Unused << block.release();
        Unused << handler.release();
        Unused << finalizer.release();

        out = Move(result);
        break;
    }

    case BinKind::WHILE_STATEMENT: MOZ_FALLTHROUGH;
    case BinKind::DO_WHILE_STATEMENT: {

        UniqueNode test(nullptr, this->nodeFree);
        UniqueNode body(nullptr, this->nodeFree);

        for (auto field: fields) {
            switch (field) {
                case BinField::TEST:
                    if (!this->parseExpression(test))
                        return false;
                    break;
                case BinField::BODY:
                    if (!this->parseStatement(body))
                        return false;
                    break;
                default:
                    return this->raiseInvalidField("DoWhileStatement", field);
            }
        }

        if (!test || !body)
            return this->raiseMissingField("DoWhileStatement");

        if (kind == BinKind::WHILE_STATEMENT) {
            UniqueNode result(factory.newWhileStatement(start, test.get(), body.get()), this->nodeFree);

            if (!result)
                return this->raiseOOM();

            out = Move(result);
        } else {
            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newDoWhileStatement(body.get(), test.get(), pos), this->nodeFree);

            if (!result)
                return this->raiseOOM();

            out = Move(result);
        }

        Unused << test.release();
        Unused << body.release();
        break;
    }

    case BinKind::FOR_STATEMENT: {

        UniqueNode init(nullptr, this->nodeFree); // Optional
        UniqueNode test(nullptr, this->nodeFree); // Optional
        UniqueNode update(nullptr, this->nodeFree); // Optional
        ScopeData scope(this->cx); // Optional
        UniqueNode body(nullptr, this->nodeFree); // Required

        for (auto field: fields) {
            switch (field) {
                case BinField::INIT:
                    if (!this->parseForHead(init))
                        return false;
                    break;
                case BinField::TEST:
                    if (!this->parseExpression(test))
                        return false;
                    break;
                case BinField::UPDATE:
                    if (!this->parseExpression(update))
                        return false;
                    break;
                case BinField::BINJS_SCOPE:
                    if (!this->parseScope(scope))
                        return false;
                    break;
                case BinField::BODY:
                    if (!this->parseStatement(body))
                        return false;
                    break;
                default:
                    return this->raiseInvalidField("ForStatement", field);
            }
        }

        if (!body)
            return this->raiseMissingField("ForStatement");

        TokenPos pos(start, tokenizer->offset());
        UniqueNode forHead(factory.newForHead(init.get(), test.get(), update.get(), pos), this->nodeFree);
        if (!forHead)
            return this->raiseOOM();

        Unused << init.release();
        Unused << update.release();
        Unused << test.release();

        UniqueNode result(factory.newForStatement(start, forHead.get(), body.get(), /*flags*/0),
            this->nodeFree);

        if (!result)
            return this->raiseOOM();

        Unused << forHead.release();
        Unused << body.release();

        out = Move(result);
        break;
    }

    case BinKind::FOR_IN_STATEMENT: {

        UniqueNode left(nullptr, this->nodeFree);
        UniqueNode right(nullptr, this->nodeFree);
        UniqueNode body(nullptr, this->nodeFree);
        ScopeData scope(this->cx); // Optional

        for (auto field: fields) {
            switch (field) {
                case BinField::LEFT:
                    if (!this->parseForInHead(left))
                        return false;
                    break;
                case BinField::RIGHT:
                    if (!this->parseExpression(right))
                        return false;
                    break;
                case BinField::BODY:
                    if (!this->parseStatement(body))
                        return false;
                    break;
                case BinField::BINJS_SCOPE:
                    if (!this->parseScope(scope))
                        return false;
                    break;
                default:
                    return this->raiseInvalidField("ForInStatement", field);
            }
        }

        if (!left || !right || !body)
            return this->raiseMissingField("ForInStatement");

        if (!this->promoteToLexicalScope(body))
            return false;

        TokenPos pos(start, tokenizer->offset());
        UniqueNode forHead(factory.newForInOrOfHead(PNK_FORIN, left.get(), right.get(), pos), this->nodeFree);
        if (!forHead)
            return this->raiseOOM();

        Unused << left.release();
        Unused << right.release();

        UniqueNode result(factory.newForStatement(start, forHead.get(), body.get(), /*flags*/JSITER_ENUMERATE),
            this->nodeFree);
        if (!result)
            return this->raiseOOM();

        Unused << forHead.release();
        Unused << body.release();

        out = Move(result);
        break;
    }

    case BinKind::FUNCTION_DECLARATION: {
        UniqueNode result(nullptr, this->nodeFree);
        if (!this->parseFunctionAux(kind, fields, result))
            return false;

        out = Move(result);
        break;

    }
    case BinKind::VARIABLE_DECLARATION:
        if (!this->parseVariableDeclarationAux(kind, fields, out))
            return false;
        break;
    default:
        return this->raiseInvalidKind("Statement", kind);
    }

    return true;
}

bool
BinASTParser::parseForHead(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("ForHead");

    // This can be either a VarDecl or an Expression.
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);
    BinKind kind;

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind == BinKind::BINJS_NULL)
        return true;
    else if (kind == BinKind::VARIABLE_DECLARATION)
        return this->parseVariableDeclarationAux(kind, fields, out);
    else /* Parse as expression */
        return this->parseExpressionAux(kind, fields, out);
}

bool
BinASTParser::parseForInHead(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("ForInHead");

    // This can be either a VarDecl or a Pattern.
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);
    BinKind kind;

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind == BinKind::BINJS_NULL)
        return true;
    else if (kind == BinKind::VARIABLE_DECLARATION)
        return this->parseVariableDeclarationAux(kind, fields, out);
    else /* Parse as pattern */
        return this->parsePatternAux(kind, fields, out);
}

bool
BinASTParser::parseFunctionAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
    MOZ_ASSERT(kind == BinKind::FUNCTION_DECLARATION || kind == BinKind::FUNCTION_EXPRESSION || kind == BinKind::OBJECT_METHOD);
    if (out)
        return this->raiseAlreadyParsed("Function");

    const size_t start = tokenizer->offset();

    UniqueNode id(nullptr, this->nodeFree);
    UniqueNode params(nullptr, this->nodeFree);
    UniqueNode body(nullptr, this->nodeFree);
    ScopeData scope(this->cx);

    UniqueNode key(nullptr, this->nodeFree); // Methods only
    Maybe<bool> computed;     // Methods only
    Maybe<Chars> method_kind; // Methods only

    // Allocate the function before walking down the tree.
    RootedFunction fun(this->cx,
        NewFunctionWithProto(this->cx,
            /*native*/nullptr,
            /*nargs ?*/0,
            /*flags */ JSFunction::INTERPRETED_NORMAL,
            /*enclosing env*/nullptr,
            /*name*/ nullptr, // Will be known later
            /*proto*/ nullptr,
            /*alloc*/gc::AllocKind::FUNCTION,
            TenuredObject
    ));
    if (!fun)
        return this->raiseOOM();

    FunctionBox* funbox = alloc.new_<FunctionBox>(this->cx,
        this->alloc,
        this->traceListHead,
        fun,
        /*toStringStart*/0,
        /*Directives*/Directives(this->parseContext),
        /*extraWarning*/false,
        GeneratorKind::NotGenerator,
        FunctionAsyncKind::SyncFunction
    );
    if (!funbox)
        return this->raiseOOM();

    this->traceListHead = funbox;

    FunctionSyntaxKind syntax;
    switch (kind) {
        case BinKind::FUNCTION_DECLARATION:
            syntax = Statement;
            break;
        case BinKind::FUNCTION_EXPRESSION:
            syntax = Expression;
            break;
        case BinKind::OBJECT_METHOD:
            // FIXME: At this stage of parsing, we do not know about `get`/`set`.
            syntax = Method;
            break;
        default:
            MOZ_CRASH("Invalid FunctionSyntaxKind");
    }
    funbox->initWithEnclosingParseContext(parseContext, syntax);


    HandlePropertyName dotThis = this->cx->names().dotThis;
    const bool declareThis = this->hasUsedName(dotThis) || funbox->bindingsAccessedDynamically() || funbox->isDerivedClassConstructor();

    if (declareThis) {
        ParseContext::Scope& funScope = this->parseContext->functionScope();
        ParseContext::Scope::AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis);
        MOZ_ASSERT(!p);
        if (!funScope.addDeclaredName(this->parseContext, p, dotThis, DeclarationKind::Var,
                                      DeclaredNameInfo::npos))
            return false;

        funbox->setHasThisBinding();
    }

    // Push a new ParseContext.
    BinParseContext funpc(this->cx, this, funbox, /*newDirectives*/ nullptr);
    if (!funpc.init())
        return false;

    for (auto field: fields) {
        switch (field) {
            case BinField::ID:
                if (!this->parsePattern(id))
                    return false;

                if (id && !id->isKind(PNK_NAME))
                    return this->raiseError("Function id MUST be an identifier");

                break;
            case BinField::PARAMS:
                if (!this->parsePatternList(params))
                    return false;
                break;
            case BinField::BODY:
                if (!this->parseStatement(body))
                    return false;
                break;
            case BinField::BINJS_SCOPE:
                if (!this->parseScope(scope))
                    return false;
                break;
            case BinField::KEY:
                if (kind != BinKind::OBJECT_METHOD)
                    return this->raiseInvalidField("Functions other than ObjectMethod", field);

                if (!this->parseExpression(key))
                    return false;

                break;
            case BinField::COMPUTED:
                if (kind != BinKind::OBJECT_METHOD)
                    return this->raiseInvalidField("Functions other than ObjectMethod", field);

                if (!this->readBool(computed))
                    return false;

                break;
            case BinField::KIND:
                if (kind != BinKind::OBJECT_METHOD)
                    return this->raiseInvalidField("Functions other than ObjectMethod", field);

                if (!this->readString(method_kind))
                    return false;

                break;
            default:
                return this->raiseInvalidField("Function", field);
        }
    }

    if (!params || !body || !scope.isSome())
        return this->raiseMissingField("Function");

    if (kind == BinKind::FUNCTION_DECLARATION && !id) {
        // The name is compulsory only for function declarations.
        return this->raiseMissingField("FunctionDeclaration");
    }

    if (kind == BinKind::OBJECT_METHOD) {
        if (!key || !computed || !method_kind)
            return this->raiseMissingField("ObjectMethod");
    }

    if (id)
        fun->initAtom(id->pn_atom);

    MOZ_ASSERT(params->isArity(PN_LIST));
    params->setOp(JSOP_NOP);

    if (!(body->isKind(PNK_LEXICALSCOPE) && body->pn_u.scope.body->isKind(PNK_STATEMENTLIST))) {
        // Promote to lexical scope + statement list.
        if (!body->isKind(PNK_STATEMENTLIST)) {
            TokenPos pos(start, tokenizer->offset());
            UniqueNode list(factory.newStatementList(pos), this->nodeFree);
            if (!list)
                return this->raiseOOM();

            list->initList(body.release());
            body = Move(list);
        }

        // Promote to lexical scope.
        if (!this->promoteToLexicalScope(body))
            return false;
    }
    MOZ_ASSERT(body->getKind() == PNK_LEXICALSCOPE);
    params->append(body.release());

    TokenPos pos(start, tokenizer->offset());
    UniqueNode function( kind == BinKind::FUNCTION_DECLARATION
            ? factory.newFunctionStatement(pos)
            : factory.newFunctionExpression(pos), this->nodeFree);
    if (!function)
        return this->raiseOOM();

    factory.setFunctionBox(function.get(), funbox);
    factory.setFunctionFormalParametersAndBody(function.get(), params.release());

    UniqueNode result(nullptr, this->nodeFree);
    if (kind == BinKind::OBJECT_METHOD) {
        JSOp op;
        if (*method_kind == "init")
            op = JSOP_INITPROP;
        else if (*method_kind == "get")
            op = JSOP_INITPROP_GETTER;
        else if (*method_kind == "set")
            op = JSOP_INITPROP_SETTER;
        else
            return this->raiseInvalidEnum("ObjectMethod", *method_kind);

        result = UniqueNode(factory.newBinary(PNK_COLON, key.get(), function.get(), op), this->nodeFree);
        if (!result)
            return this->raiseOOM();

        Unused << key.release();
        Unused << function.release();
    } else {
        result = Move(function);
    }

    out = Move(result);
    return true;
}

bool
BinASTParser::parseVariableDeclarationAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("VariableDeclaration");

    const size_t start = tokenizer->offset();

    if (kind == BinKind::BINJS_NULL)
        return true;
    else if (kind != BinKind::VARIABLE_DECLARATION)
        return this->raiseInvalidKind("VariableDeclaration", kind);

    ParseNodeKind pnk = PNK_LIMIT;
    JSOp op = JSOP_NOP;
    UniqueNode result(nullptr, this->nodeFree);

    for (auto field: fields) {
        switch (field) {
            case BinField::KIND: {
                Maybe<Chars> kindName;
                if (!this->readString(kindName))
                    return false;

                if (kindName.isNothing())
                    return this->raiseMissingField("VariableDeclaration");

                if (*kindName == "let") {
                    pnk = PNK_LET;
                    op = JSOP_DEFLET;
                } else if (*kindName == "var") {
                    pnk = PNK_VAR;
                    op = JSOP_DEFVAR;
                } else if (*kindName == "const") {
                    pnk = PNK_CONST;
                    op = JSOP_DEFCONST;
                } else {
                    return this->raiseInvalidEnum("VariableDeclaration", *kindName);
                }
                break;
            }
            case BinField::DECLARATIONS: {
                if (result) {
                    // Already parsed.
                    return this->raiseAlreadyParsed("VariableDeclaration");
                }

                uint32_t length;
                AutoList guard(*tokenizer);

                if (!tokenizer->readList(length, guard))
                    return false;

                if (length == 0)
                    return this->raiseEmpty("VariableDeclaration");

                TokenPos pos(start, tokenizer->offset());
                UniqueNode root(factory.newDeclarationList(PNK_CONST /*Placeholder*/, pos, JSOP_NOP/*Placeholder*/), this->nodeFree);
                if (!root)
                    return this->raiseOOM();

                UniqueNode first(nullptr, this->nodeFree);
                if (!this->parseVariableDeclarator(first))
                    return this->raiseOOM();

                root->initList(first.release());

                for (uint32_t i = 1; i < length; ++i) {
                    UniqueNode current(nullptr, this->nodeFree);
                    if (!this->parseVariableDeclarator(current))
                        return false;

                    if (!current)
                        return this->raiseMissingField("VariableDeclaration");

                    root->append(current.release());
                }

                result = Move(root);
                break;
            }
            default:
                return this->raiseInvalidField("VariableDeclaration", field);
        }
    }

    if (!result || pnk == PNK_LIMIT)
        return this->raiseMissingField("VariableDeclaration");

    result->setKind(pnk);
    result->setOp(op);

    MOZ_ASSERT(!result->isKind(PNK_NOP));
    out = Move(result);

    return true;
}


bool
BinASTParser::parseExpression(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("Expression");

    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);
    BinKind kind;

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    return this->parseExpressionAux(kind, fields, out);
}

bool
BinASTParser::parseExpressionStatementAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
    MOZ_ASSERT(kind == BinKind::EXPRESSION_STATEMENT);
    if (out)
        return this->raiseAlreadyParsed("ExpressionStatement");

    UniqueNode result(nullptr, this->nodeFree);
    for (auto field: fields) {
        switch (field) {
            case BinField::EXPRESSION:
                if (!this->parseExpression(result))
                    return false;

                break;
            default:
                return this->raiseInvalidField("ExpressionStatement", field);
        }
    }

    if (!result)
        return this->raiseMissingField("ExpressionStatement");

    out = UniqueNode(factory.newExprStatement(result.get(), tokenizer->offset()), this->nodeFree);

    if (!out)
        return this->raiseOOM();

    Unused << result.release(); // Now part of `out`.

    return true;
}

bool
BinASTParser::parseVariableDeclarator(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("VariableDeclarator");

    const size_t start = tokenizer->offset();

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);


    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind != BinKind::VARIABLE_DECLARATOR)
        return this->raiseInvalidKind("VariableDeclarator", kind);

    UniqueNode id(nullptr, this->nodeFree);
    UniqueNode init(nullptr, this->nodeFree); // Optional.
    for (auto field: fields) {
        switch (field) {
            case BinField::ID:
                if (!this->parsePattern(id))
                    return false;

                break;
            case BinField::INIT:
                if (!this->parseExpression(init))
                    return false;

                break;
            default:
                return this->raiseInvalidField("VariableDeclarator", field);
        }
    }

    if (!id)
        return this->raiseMissingField("VariableDeclarator");

    UniqueNode result(nullptr, this->nodeFree);

    // FIXME: Documentation in ParseNode is clearly obsolete.
    if (id->isKind(PNK_NAME)) {
        // `var foo [= bar]``
        TokenPos pos(start, tokenizer->offset());
        result = UniqueNode(factory.newName(id->pn_atom->asPropertyName(), pos, cx), this->nodeFree);
        if (!result)
            return this->raiseOOM();

        if (init)
            result->pn_expr = init.release();

    } else {
        // `var pattern = bar`
        if (!init) {
            // Here, `init` is required.
            return this->raiseMissingField("VariableDeclarator");
        }

        result = UniqueNode(factory.newBinary(PNK_ASSIGN, id.get(), init.get()), this->nodeFree);
        if (!result)
            return this->raiseOOM();

        Unused << id.release();
        Unused << init.release();
    }

    out = Move(result);
    return true;
}

bool
BinASTParser::parseExpressionOrElisionList(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("[Expression|null]");

    const size_t start = tokenizer->offset();

    uint32_t length;
    AutoList guard(*tokenizer);

    if (!tokenizer->readList(length, guard))
        return false;

    UniqueNode result(factory.newArrayLiteral(start), this->nodeFree);
    if (!result)
        return this->raiseOOM();

    for (uint32_t i = 0; i < length; ++i) {
        UniqueNode expr(nullptr, this->nodeFree);
        if (!this->parseExpression(expr))
            return false;

        if (expr) {
            factory.addArrayElement(result.get(), expr.release());
        } else {
            TokenPos pos(start, tokenizer->offset());
            if (!factory.addElision(result.get(), pos))
                return this->raiseOOM();
        }
    }

    out = Move(result);
    return true;
}


bool
BinASTParser::parseSwitchCaseList(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("[SwitchCase]");

    uint32_t length;
    AutoList guard(*tokenizer);

    if (!tokenizer->readList(length, guard))
        return false;

    TokenPos pos;
    tokenizer->latestTokenPos(pos);
    UniqueNode result(factory.newStatementList(pos), this->nodeFree);
    if (!result)
        return this->raiseOOM();

    for (uint32_t i = 0; i < length; ++i) {
        UniqueNode case_(nullptr, this->nodeFree);
        if (!this->parseSwitchCase(case_))
            return false;

        if (!case_)
            return this->raiseEmpty("[SwitchCase]");

        factory.addCaseStatementToList(result.get(), case_.release());
    }

    if (!this->promoteToLexicalScope(result))
        return false;

    out = Move(result);
    return true;
}

bool
BinASTParser::parseExpressionAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
    const size_t start = tokenizer->offset();

    switch (kind) {
        case BinKind::BINJS_NULL:
            return true;
        case BinKind::IDENTIFIER: {
            Rooted<PropertyName*> id(this->cx);
            for (auto field: fields) {
                switch (field) {
                    case BinField::NAME:
                        if (!this->readString(&id))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("Identifier", field);
                }
            }

            if (!id)
                return this->raiseMissingField("Identifier");

            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newName(id, pos, cx), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        case BinKind::BOOLEAN_LITERAL: {
            Maybe<bool> value;
            for (auto field: fields) {
                switch (field) {
                    case BinField::VALUE:
                        if (!this->readBool(value))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("BooleanLiteral", field);
                }
            }

            if (!value)
                return this->raiseMissingField("BooleanLiteral");

            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newBooleanLiteral(*value, pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        case BinKind::DIRECTIVE_LITERAL:
            // A directive is not truly a literal, so this doesn't make sense
            // here.
            return this->raiseError("DirectiveLiteral (not truly a literal)");
        case BinKind::NULL_LITERAL: {
            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newNullLiteral(pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        case BinKind::NUMERIC_LITERAL: {
            Maybe<double> value;
            for (auto field: fields) {
                switch (field) {
                    case BinField::VALUE:
                        if (!this->readNumber(value))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("NumericLiteral", field);
                }
            }

            if (!value)
                return this->raiseMissingField("NumericLiteral");

            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newNumber(*value, DecimalPoint::HasDecimal, pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        case BinKind::REGEXP_LITERAL: {
            RootedAtom pattern(this->cx);
            Maybe<Chars> flags;
            for (auto field: fields) {
                switch (field) {
                    case BinField::PATTERN:
                        if (!this->readString(&pattern))
                            return false;
                        break;
                    case BinField::FLAGS:
                        if (!this->readString(flags))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("RegExpLiteral", field);
                }
            }

            if (!pattern || !flags)
                return this->raiseMissingField("RegExpLiteral");

            RegExpFlag reflags = NoFlags;
            for (char 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 this->raiseInvalidEnum("RegExpLiteral", *flags);
            }


            Rooted<RegExpObject*> reobj(this->cx);
            reobj = RegExpObject::create(this->cx,
                pattern,
                reflags,
                /* options*/ nullptr,
                /* tokenStream */ nullptr,
                this->alloc,
                TenuredObject);

            if (!reobj)
                return this->raiseOOM();

            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newRegExp(reobj, pos, *this), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        case BinKind::STRING_LITERAL: {
            RootedAtom value(this->cx);
            for (auto field: fields) {
                switch (field) {
                    case BinField::VALUE:
                        if (!this->readString(&value))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("StringLiteral", field);
                }
            }

            if (!value)
                return this->raiseMissingField("StringLiteral");

            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newStringLiteral(value, pos), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        case BinKind::THIS_EXPRESSION: {
            TokenPos pos(start, tokenizer->offset());
            UniqueNode thisName(nullptr, this->nodeFree);
            if (this->parseContext->sc()->thisBinding() == ThisBinding::Function) {
                thisName = UniqueNode(factory.newName(this->cx->names().dotThis, pos, cx), this->nodeFree);
                if (!thisName)
                    return this->raiseOOM();
            }

            UniqueNode result(factory.newThisLiteral(pos, thisName.get()), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            Unused << thisName.release();

            out = Move(result);
            break;
        }
        case BinKind::ARRAY_EXPRESSION: {
            UniqueNode result(nullptr, this->nodeFree);
            if (!this->parseExpressionOrElisionList(result))
                return false;

            result->setKind(PNK_ARRAY);
            result->setOp(JSOP_NEWINIT);
            out = Move(result);
            break;
        }
        case BinKind::OBJECT_EXPRESSION: {
            UniqueNode result(nullptr, this->nodeFree);
            if (!this->parseObjectMemberList(result))
                return false;

            bool first = true;
            for (ParseNode* iter = result.get(); iter != nullptr; iter = iter->pn_next) {
                if (first) {
                    first = false;
                    MOZ_ASSERT(iter->isKind(PNK_COLON));
                    MOZ_ASSERT(iter->pn_left != nullptr);
                    MOZ_ASSERT(iter->pn_right != nullptr);
                }
            }

            result->setKind(PNK_OBJECT);
            result->setOp(JSOP_NEWINIT);
            out = Move(result);
            break;
        }
        case BinKind::FUNCTION_EXPRESSION: {
            UniqueNode result(nullptr, this->nodeFree);
            if (!this->parseFunctionAux(kind, fields, result))
                return false;

            result->setOp(JSOP_LAMBDA);
            out = Move(result);
            break;
        }
        case BinKind::UNARY_EXPRESSION: MOZ_FALLTHROUGH;
        case BinKind::UPDATE_EXPRESSION: {

            UniqueNode expr(nullptr, this->nodeFree);
            Maybe<Chars> operation;
            Maybe<bool> prefix; // FIXME: Ignored for unary_expression?

            for (auto field: fields) {
                switch (field) {
                    case BinField::OPERATOR:
                        if (!this->readString(operation))
                            return false;
                        break;
                    case BinField::PREFIX:
                        if (!this->readBool(prefix))
                            return false;
                        break;
                    case BinField::ARGUMENT:
                        if (!this->parseExpression(expr))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("UpdateExpression", field);
                }
            }

            if (!expr || operation.isNothing() || prefix.isNothing())
                return this->raiseMissingField("UpdateExpression");

            ParseNodeKind pnk = PNK_LIMIT;
            JSOp op = JSOP_NOP;
            if (kind == BinKind::UNARY_EXPRESSION) {
                if (*operation == "-") {
                    pnk = PNK_NEG;
                    op = JSOP_NEG;
                } else if (*operation == "+") {
                    pnk = PNK_POS;
                    op = JSOP_POS;
                } else if (*operation == "!") {
                    pnk = PNK_NOT;
                    op = JSOP_NOT;
                } else if (*operation == "~") {
                    pnk = PNK_BITNOT;
                    op = JSOP_BITNOT;
                } else if (*operation == "typeof") {
                    if (expr->isKind(PNK_NAME))
                        pnk = PNK_TYPEOFNAME;
                    else
                        pnk = PNK_TYPEOFEXPR;
                } else if (*operation == "void") {
                    pnk = PNK_VOID;
                    op = JSOP_VOID;
                } else if (*operation == "delete") {
                    switch (expr->getKind()) {
                        case PNK_NAME:
                            expr->setOp(JSOP_DELNAME);
                            pnk = PNK_DELETENAME;
                            break;
                        case PNK_DOT:
                            pnk = PNK_DELETEPROP;
                            break;
                        case PNK_ELEM:
                            pnk = PNK_DELETEELEM;
                            break;
                        default:
                            pnk = PNK_DELETEEXPR;
                    }
                } else {
                    return this->raiseInvalidEnum("UnaryOperator", *operation);
                }
            } else if (kind == BinKind::UPDATE_EXPRESSION) {
                if (*operation == "++") {
                    if (*prefix)
                        pnk = PNK_PREINCREMENT;
                    else
                        pnk = PNK_POSTINCREMENT;
                } else if (*operation == "--") {
                    if (*prefix)
                        pnk = PNK_PREDECREMENT;
                    else
                        pnk = PNK_POSTDECREMENT;
                } else {
                    return this->raiseInvalidEnum("UpdateOperator", *operation);
                }
            }

            UniqueNode result(factory.newUnary(pnk, op, start, expr.get()), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            Unused << expr.release();
            out = Move(result);
            break;
        }
        case BinKind::BINARY_EXPRESSION: MOZ_FALLTHROUGH;
        case BinKind::LOGICAL_EXPRESSION: {

            UniqueNode left(nullptr, this->nodeFree);
            UniqueNode right(nullptr, this->nodeFree);
            Maybe<Chars> operation;
            for (auto field: fields) {
                switch (field) {
                    case BinField::LEFT:
                        if (!this->parseExpression(left))
                            return false;
                        break;
                    case BinField::RIGHT:
                        if (!this->parseExpression(right))
                            return false;
                        break;
                    case BinField::OPERATOR:
                        if (!this->readString(operation))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("LogicalExpression | BinaryExpression", field);
                }
            }

            if (!left || !right || operation.isNothing())
                return this->raiseMissingField("LogicalExpression | BinaryExpression");

            // FIXME: Instead of Chars, we should use atoms and comparison
            // between atom ptr.
            ParseNodeKind pnk = PNK_LIMIT;
            JSOp op;
            if (*operation == "==") {
                pnk = PNK_EQ;
                op = JSOP_EQ;
            } else if (*operation == "!=") {
                pnk = PNK_NE;
                op = JSOP_NE;
            } else if (*operation == "===") {
                pnk = PNK_STRICTEQ;
                op = JSOP_STRICTEQ;
            } else if (*operation == "!==") {
                pnk = PNK_STRICTNE;
                op = JSOP_STRICTNE;
            } else if (*operation == "<") {
                pnk = PNK_LT;
                op = JSOP_LT;
            } else if (*operation == "<=") {
                pnk = PNK_LE;
                op = JSOP_LE;
            } else if (*operation == ">") {
                pnk = PNK_GT;
                op = JSOP_GT;
            } else if (*operation == ">=") {
                pnk = PNK_GE;
                op = JSOP_GE;
            } else if (*operation == "<<") {
                pnk = PNK_LSH;
                op = JSOP_LSH;
            } else if (*operation == ">>") {
                pnk = PNK_RSH;
                op = JSOP_RSH;
            } else if (*operation == ">>>") {
                pnk = PNK_URSH;
                op = JSOP_URSH;
            } else if (*operation == "+") {
                pnk = PNK_ADD;
                op = JSOP_ADD;
            } else if (*operation == "-") {
                pnk = PNK_SUB;
                op = JSOP_SUB;
            } else if (*operation == "*") {
                pnk = PNK_STAR;
                op = JSOP_MUL;
            } else if (*operation == "/") {
                pnk = PNK_DIV;
                op = JSOP_DIV;
            } else if (*operation == "%") {
                pnk = PNK_MOD;
                op = JSOP_MOD;
            } else if (*operation == "|") {
                pnk = PNK_BITOR;
                op = JSOP_BITOR;
            } else if (*operation == "^") {
                pnk = PNK_BITXOR;
                op = JSOP_BITXOR;
            } else if (*operation == "&") {
                pnk = PNK_BITAND;
                op = JSOP_BITAND;
            } else if (*operation == "in") {
                pnk = PNK_IN;
                op = JSOP_IN;
            } else if (*operation == "instanceof") {
                pnk = PNK_INSTANCEOF;
                op = JSOP_INSTANCEOF;
            } else if (*operation == "||") {
                pnk = PNK_OR;
                op = JSOP_OR;
            } else if (*operation == "&&" ) {
                pnk = PNK_AND;
                op = JSOP_AND;
            } else if (*operation == "**" ) {
                pnk = PNK_POW;
                op = JSOP_POW;
            } else {
                return this->raiseInvalidEnum("BinaryOperator | LogicalOperator", *operation);
            }

            if (left->isKind(pnk) && pnk != PNK_POW /*Not associative*/) {
                // Regroup associative operations into lists.
                left->append(right.release());
                out = Move(left);
            } else {
                TokenPos pos(start, tokenizer->offset());
                UniqueNode list(factory.newList(pnk, pos, op), this->nodeFree);
                if (!list) {
                    return this->raiseOOM();
                }
                list->makeEmpty();
                list->append(left.release());
                list->append(right.release());
                out = Move(list);
            }

            break;
        }
        case BinKind::ASSIGNMENT_EXPRESSION: {
            UniqueNode left(nullptr, this->nodeFree);
            UniqueNode right(nullptr, this->nodeFree);
            Maybe<Chars> operation;
            for (auto field: fields) {
                switch (field) {
                    case BinField::LEFT:
                        if (!this->parseExpression(left))
                            return false;
                        break;
                    case BinField::RIGHT:
                        if (!this->parseExpression(right))
                            return false;
                        break;
                    case BinField::OPERATOR:
                        if (!this->readString(operation))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("AssignmentExpression", field);
                }
            }

            if (!left || !right || operation.isNothing())
                return this->raiseMissingField("AssignmentExpression");

            // FIXME: Instead of Chars, we should use atoms and comparison
            // between atom ptr.
            // FIXME: We should probably turn associative operations into lists.
            ParseNodeKind pnk = PNK_LIMIT;
            JSOp op = JSOP_NOP;
            if (*operation == "=") {
                pnk = PNK_ASSIGN;
            } else if (*operation == "+=") {
                pnk = PNK_ADDASSIGN;
                op = JSOP_ADD;
            } else if (*operation == "-=") {
                pnk = PNK_SUBASSIGN;
                op = JSOP_SUB;
            } else if (*operation == "*=") {
                pnk = PNK_MULASSIGN;
                op = JSOP_MUL;
            } else if (*operation == "/=") {
                pnk = PNK_DIVASSIGN;
                op = JSOP_DIV;
            } else if (*operation == "%=") {
                pnk = PNK_MODASSIGN;
                op = JSOP_MOD;
            } else if (*operation == "<<=") {
                pnk = PNK_LSHASSIGN;
                op = JSOP_LSH;
            } else if (*operation == ">>=") {
                pnk = PNK_RSHASSIGN;
                op = JSOP_RSH;
            } else if (*operation == ">>>=") {
                pnk = PNK_URSHASSIGN;
                op = JSOP_URSH;
            } else if (*operation == "|=") {
                pnk = PNK_BITORASSIGN;
                op = JSOP_BITOR;
            } else if (*operation == "^=") {
                pnk = PNK_BITXORASSIGN;
                op = JSOP_BITXOR;
            } else if (*operation == "&=") {
                pnk = PNK_BITANDASSIGN;
                op = JSOP_BITAND;
            } else {
                return this->raiseInvalidEnum("AssignmentOperator", *operation);
            }

            UniqueNode list(factory.newBinary(pnk, left.get(), right.get()), this->nodeFree);
            if (!list)
                return this->raiseOOM();

            Unused << left.release();
            Unused << right.release();

            out = Move(list);
            break;
        }
        case BinKind::MEMBER_EXPRESSION: {
            UniqueNode result(nullptr, this->nodeFree);
            if (!this->parseMemberExpressionAux(kind, fields, result))
                return false;

            out = Move(result);
            break;
        }
        case BinKind::CONDITIONAL_EXPRESSION: {
            UniqueNode test(nullptr, this->nodeFree);
            UniqueNode alternate(nullptr, this->nodeFree);
            UniqueNode consequent(nullptr, this->nodeFree);

            for (auto field: fields) {
                switch (field) {
                    case BinField::TEST:
                        if (!this->parseExpression(test))
                            return false;
                        break;
                    case BinField::ALTERNATE:
                        if (!this->parseExpression(alternate))
                            return false;
                        break;
                    case BinField::CONSEQUENT:
                        if (!this->parseExpression(consequent))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("ConditionalExpression", field);
                }
            }

            if (!test || !alternate || !consequent)
                return this->raiseMissingField("ConditionalExpression");

            UniqueNode result(factory.newConditional(test.get(), consequent.get(), alternate.get()), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            Unused << test.release();
            Unused << alternate.release();
            Unused << consequent.release();

            out = Move(result);
            break;
        }
        case BinKind::CALL_EXPRESSION: MOZ_FALLTHROUGH;
        case BinKind::NEW_EXPRESSION: {
            UniqueNode callee(nullptr, this->nodeFree);
            UniqueNode arguments(nullptr, this->nodeFree);

            for (auto field: fields) {
                switch (field) {
                    case BinField::CALLEE:
                        if (!this->parseExpression(callee))
                            return false;
                        break;
                    case BinField::ARGUMENTS:
                        if (!this->parseExpressionOrElisionList(arguments))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("NewExpression", field);
                }
            }

            if (!callee || !arguments)
                return this->raiseMissingField("NewExpression");

            ParseNodeKind pnk =
                kind == BinKind::CALL_EXPRESSION
                ? PNK_CALL
                : PNK_NEW;
            arguments->setKind(pnk);
            arguments->prepend(callee.release());

            out = Move(arguments);
            break;
        }
        case BinKind::SEQUENCE_EXPRESSION: {
            UniqueNode sequence(nullptr, this->nodeFree);

            for (auto field: fields) {
                switch (field) {
                    case BinField::EXPRESSIONS:
                        if (!this->parseExpressionOrElisionList(sequence))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("SequenceExpression", field);
                }
            }

            if (!sequence)
                return this->raiseMissingField("SequenceExpression");

            sequence->setKind(PNK_COMMA);
            out = Move(sequence);
            break;
        }
        default:
            return this->raiseInvalidKind("Expression", kind);
    }

    return true;
}

bool
BinASTParser::parseMemberExpressionAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("MemberExpression");

    UniqueNode object(nullptr, this->nodeFree);
    UniqueNode property(nullptr, this->nodeFree);
    Maybe<bool> isComputed;

    for (auto field: fields) {
        switch (field) {
            case BinField::OBJECT:
                if (!this->parseExpression(object))
                    return false;
                break;
            case BinField::PROPERTY:
                if (!this->parseExpression(property))
                    return false;
                break;
            case BinField::COMPUTED:
                if (!this->readBool(isComputed))
                    return false;
                break;
            default:
                return this->raiseInvalidField("MemberExpression", field);
        }
    }

    if (!object || !property || !isComputed)
        return this->raiseMissingField("MemberExpression");

    UniqueNode result(nullptr, this->nodeFree);
    if (!*isComputed) {
        if (!property->isKind(PNK_NAME))
            return this->raiseError("MemberExpression (computed implies name)");

        PropertyName* name = property->pn_atom->asPropertyName();
        result = UniqueNode(factory.newPropertyAccess(object.get(), name, tokenizer->offset()), this->nodeFree);
    } else {
        result = UniqueNode(factory.newPropertyByValue(object.get(), property.get(), tokenizer->offset()), this->nodeFree);
    }
    if (!result)
        return this->raiseOOM();

    Unused << object.release();
    Unused << property.release();

    out = Move(result);
    return true;
}

bool
BinASTParser::parseDirective(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("Directive");

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);
    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind != BinKind::DIRECTIVE)
        return this->raiseInvalidKind("Directive", kind);

    for (auto field: fields) {
        switch (field) {
            case BinField::VALUE:
            if (!this->parseDirectiveLiteral(out))
                return false;
            break;
        default:
            return this->raiseInvalidField("Directive", field);
        }
    }

    return true;
}

bool
BinASTParser::parseDirectiveLiteral(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("DirectiveLiteral");

    const size_t start = tokenizer->offset();

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);
    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind != BinKind::DIRECTIVE_LITERAL)
        return this->raiseInvalidKind("DirectiveLiteral", kind);

    RootedAtom value(this->cx);
    for (auto field: fields) {
        switch (field) {
            case BinField::VALUE:
                if (!this->readString(&value))
                    return false;
                break;
            default:
                return this->raiseInvalidField("DirectiveLiteral", field);
        }
    }

    if (!value)
        return this->raiseMissingField("DirectiveLiteral");

    TokenPos pos(start, tokenizer->offset());
    UniqueNode result(factory.newStringLiteral(value, pos), this->nodeFree);
    if (!result)
        return this->raiseOOM();

    out = Move(result);
    return true;
}

bool
BinASTParser::parseDirectiveList(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("[Directive]");

    uint32_t length;
    AutoList guard(*tokenizer);
    if (!tokenizer->readList(length, guard))
        return false;

    for (uint32_t i = 0; i < length; ++i) {
        UniqueNode directive(nullptr, this->nodeFree); // Ignored
        if (!this->parseDirective(directive))
            return false;

        if (!directive)
            return this->raiseEmpty("[Directive]");
    }

    return true;
}

bool
BinASTParser::parseSwitchCase(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("SwitchCase");

    const size_t start = tokenizer->offset();

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind != BinKind::SWITCH_CASE)
        return this->raiseInvalidKind("SwitchCase", kind);

    UniqueNode test(nullptr, this->nodeFree); // Optional.
    UniqueNode statements(nullptr, this->nodeFree); // Required.

    for (auto field: fields) {
        switch (field) {
            case BinField::TEST:
                if (!this->parseExpression(test))
                    return false;
                break;
            case BinField::CONSEQUENT:
                if (!this->parseStatementList(statements))
                    return false;
                break;
            default:
                return this->raiseInvalidField("SwitchCase", field);
        }
    }

    if (!statements)
        return this->raiseMissingField("SwitchCase");

    MOZ_ASSERT(statements->isKind(PNK_STATEMENTLIST));

    UniqueNode result(nullptr, this->nodeFree);

    result = UniqueNode(factory.newCaseOrDefault(start, test.get(), statements.get()), this->nodeFree);
    if (!result)
        return this->raiseOOM();

    Unused << test.release();
    Unused << statements.release();

    out = Move(result);
    return true;
}

bool
BinASTParser::parseCatchClause(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("CatchClause");

    const size_t start = tokenizer->offset();

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    if (kind == BinKind::BINJS_NULL)
        return true;

    if (kind != BinKind::CATCH_CLAUSE)
        return this->raiseInvalidKind("CatchClause", kind);

    UniqueNode param(nullptr, this->nodeFree);
    UniqueNode body(nullptr, this->nodeFree);
    ScopeData scope(this->cx); // Optional

    for (auto field: fields) {
        switch (field) {
            case BinField::PARAM:
                if (!this->parsePattern(param))
                    return false;
                break;
            case BinField::BODY:
                if (!this->parseBlockStatement(body))
                    return false;
                break;
            case BinField::BINJS_SCOPE:
                if (!this->parseScope(scope))
                    return false;
                break;
            default:
                return this->raiseInvalidField("CatchClause", field);
        }
    }

    if (!param || !body)
        return this->raiseMissingField("CatchClause");

    if (!this->promoteToLexicalScope(body))
        return false;

    UniqueNode catchClause(new_<TernaryNode>(PNK_CATCH, JSOP_NOP, param.get(), nullptr, body.get()), this->nodeFree);
    if (!catchClause)
        return this->raiseOOM();

    Unused << param.release();
    Unused << body.release();

    if (!this->promoteToLexicalScope(catchClause))
        return false;

    if (scope.isSome() && scope.hasLexNames()) {
        if (!this->storeLexicalScope(catchClause, Move(scope)))
            return false;
    }

    TokenPos pos(start, tokenizer->offset());
    UniqueNode catchList(factory.newCatchList(pos), this->nodeFree);
    if (!catchList)
        return this->raiseOOM();

    catchList->append(catchClause.release());

    out = Move(catchList);
    return true;
}

bool
BinASTParser::parsePatternList(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("[Pattern]");

    uint32_t length;
    AutoList guard(*tokenizer);

    if (!tokenizer->readList(length, guard))
        return false;

    TokenPos pos;
    tokenizer->latestTokenPos(pos);
    UniqueNode result(this->new_<ListNode>(PNK_PARAMSBODY, pos), this->nodeFree);

    for (uint32_t i = 0; i < length; ++i) {
        UniqueNode pattern(nullptr, this->nodeFree);
        if (!this->parsePattern(pattern))
            return false;

        if (!pattern)
            return this->raiseEmpty("[Pattern]");

        result->append(pattern.release());
    }

    out = Move(result);
    return true;
}

bool
BinASTParser::parsePattern(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("Pattern");

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    return this->parsePatternAux(kind, fields, out);
}

bool
BinASTParser::parsePatternAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("Pattern | null");

    const size_t start = tokenizer->offset();

    switch (kind) {
        case BinKind::BINJS_NULL:
            return true;
        case BinKind::MEMBER_EXPRESSION: {
            UniqueNode result(nullptr, this->nodeFree);
            if (!this->parseMemberExpressionAux(kind, fields, result))
                return false;

            out = Move(result);
            break;
        }
        case BinKind::IDENTIFIER: {
            RootedAtom id(cx);
            for (auto field: fields) {
                switch (field) {
                    case BinField::NAME:
                        if (!this->readString(&id))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("Identifier as Pattern", field);
                }
            }

            if (!id)
                return this->raiseMissingField("Identifier as Pattern");

            TokenPos pos(start, tokenizer->offset());
            UniqueNode result(factory.newName(id->asPropertyName(), pos, cx), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            out = Move(result);
            break;
        }
        default:
            return this->raiseInvalidKind("Pattern | null", kind);
    }

    return true;
}

bool
BinASTParser::parseObjectMember(UniqueNode& out) {
    if (out)
        return this->raiseAlreadyParsed("ObjectMember");

    BinKind kind;
    BinFields fields(this->cx);
    AutoTaggedTuple guard(*tokenizer);

    if (!tokenizer->readTaggedTuple(kind, fields, guard))
        return false;

    switch (kind) {
        case BinKind::OBJECT_PROPERTY: {
            UniqueNode key(nullptr, this->nodeFree);
            UniqueNode value(nullptr, this->nodeFree);
            Maybe<bool> computed;
            for (auto field: fields) {
                switch (field) {
                    case BinField::KEY:
                        if (!this->parseExpression(key))
                            return false;

                        if (key) {
                            if (key->isKind(PNK_NAME)) {
                                UniqueNode name(factory.newObjectLiteralPropertyName(key->pn_atom, key->pn_pos), this->nodeFree);
                                key->pn_atom = nullptr;
                                key = Move(name);
                            }
                        }
                        break;
                    case BinField::COMPUTED:
                        if (!this->readBool(computed))
                            return false;
                        break;
                    case BinField::VALUE:
                        if (!this->parseExpression(value))
                            return false;
                        break;
                    default:
                        return this->raiseInvalidField("ObjectMember", field);
                }
            }

            if (!key || !computed || !value)
                return this->raiseMissingField("ObjectMember");

            if (!(key->isKind(PNK_NUMBER) || key->isKind(PNK_OBJECT_PROPERTY_NAME)
                || key->isKind(PNK_STRING) || key->isKind(PNK_COMPUTED_NAME)
                || key->isKind(PNK_NAME)))
                return this->raiseError("ObjectMember key kind");

            UniqueNode result(factory.newBinary(PNK_COLON, key.get(), value.get(), JSOP_INITPROP), this->nodeFree);
            if (!result)
                return this->raiseOOM();

            Unused << key.release();
            Unused << value.release();

            out = Move(result);
            return true;
        }
        case BinKind::OBJECT_METHOD: {
            UniqueNode result(nullptr, this->nodeFree);
            if (!this->parseFunctionAux(kind, fields, result))
                return false;

            if (!result)
                return this->raiseEmpty("ObjectMethod");

            MOZ_ASSERT(result->isKind(PNK_COLON));

            out = Move(result);
            return true;
        }
        default:
            return this->raiseInvalidKind("ObjectMember", kind);
    }
}

bool
BinASTParser::parseObjectMemberList(UniqueNode& out) {
    uint32_t length;
    AutoList guard(*tokenizer);

    if (!tokenizer->readList(length, guard))
        return false;

    TokenPos pos;
    tokenizer->latestTokenPos(pos);
    UniqueNode result(factory.newList(PNK_NOP /*Placeholder*/, pos), this->nodeFree);
    for (uint32_t i = 0; i < length; ++i) {
        UniqueNode keyValue(nullptr, this->nodeFree);
        if (!this->parseObjectMember(keyValue))
            return false;

        if (!keyValue)
            return this->raiseEmpty("[ObjectMember]");

        result->append(keyValue.release());
    }

    out = Move(result);
    return true;
}

bool
BinASTParser::readString(MutableHandleString out) {
    if (out)
        return this->raiseAlreadyParsed("String");

    RootedAtom atom(this->cx);
    if (!this->readString(&atom))
        return false;

    out.set(atom);
    return true;
}

bool
BinASTParser::readString(MutableHandleAtom out) {
    if (out)
        return this->raiseAlreadyParsed("String");

    Maybe<Chars> string;
    if (!this->readString(string))
        return false;

    if (!string)
        return true;

    RootedAtom atom(cx, Atomize(this->cx, string->begin(), string->length()));
    if (!atom)
        return this->raiseOOM();

    out.set(Move(atom));
    return true;
}

bool
BinASTParser::readString(MutableHandle<PropertyName*> out) {
    if (out)
        return this->raiseAlreadyParsed("String");

    RootedAtom atom(cx);

    if (!this->readString(&atom))
        return false;

    if (!atom)
        out.set(nullptr);
    else
        out.set(atom->asPropertyName());

    return true;
}

bool
BinASTParser::readString(Maybe<Chars>& out) {
    if (out)
        return this->raiseAlreadyParsed("String");

    if (!tokenizer->readMaybeChars(out))
        return false;

    return true;
}

bool
BinASTParser::readNumber(Maybe<double>& out) {
    if (out)
        return this->raiseAlreadyParsed("Number");

    return tokenizer->readMaybeF64(&out);
}

bool
BinASTParser::readBool(Maybe<bool>& out) {
    if (out)
        return this->raiseAlreadyParsed("Bool");

    return tokenizer->readMaybeBool(&out);
}

bool
BinASTParser::raiseInvalidKind(const char* superKind, const BinKind kind) {
    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid kind %d", superKind, kind);
    return this->raiseError("raiseInvalidKind");
}

bool
BinASTParser::raiseInvalidField(const char* kind, const BinField field) {
    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid field %d", kind, field);
    return this->raiseError("raiseInvalidField");
}

bool
BinASTParser::raiseInvalidEnum(const char* kind, const Chars& value) {
    // We don't trust the actual chars of `value` to be properly formatted anything, so let's not use
    // them anywhere.
    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid enum value (%zu chars)", kind, value.length());
    return this->raiseError("raiseInvalidEnum");
}

bool
BinASTParser::raiseMissingField(const char* kind) {
    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, missing field", kind);
    return this->raiseError("raiseMissingField");
}

bool
BinASTParser::raiseAlreadyParsed(const char* kind) {
    // As this error may appear with the BinTokenReaderTester but not with a more
    // robust format, this method may be turned into an assertion failure in the
    // future.
    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, already parsed", kind);
    return this->raiseError("raiseAlreadyParsed");
}

bool
BinASTParser::raiseEmpty(const char* kind) {
    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, empty data", kind);
    return this->raiseError("raiseEmpty");
}

bool
BinASTParser::raiseOOM() {
    cx->recoverFromOutOfMemory();
    return false;
}


bool
BinASTParser::raiseError(const char* method) {
    TokenPos pos;
    tokenizer->latestTokenPos(pos);
    fprintf(stderr, "BinASTParser::raiseError() from %s at %u -> %u\n",
        method,
        pos.begin,
        pos.end);
    MOZ_CRASH("Debugging...");
    return false;
}

void
BinASTParser::reportErrorNoOffset(unsigned errorNumber, ...) {
    va_list args;
    va_start(args, errorNumber);

    ErrorMetadata metadata;
    metadata.filename = this->getFilename();
    metadata.lineNumber = 0;
    metadata.columnNumber = offset();
    ReportCompileError(cx, Move(metadata), nullptr, JSREPORT_ERROR, errorNumber, args);

    va_end(args);
}

bool
BinASTParser::hasUsedName(HandlePropertyName name) {
    if (UsedNamePtr p = this->usedNames.lookup(name))
        return p->value().isUsedInScript(this->parseContext->scriptId());

    return false;
}

void
TraceBinParser(JSTracer* trc, AutoGCRooter* parser) {
    static_cast<BinASTParser*>(parser)->trace(trc);
}

} // namespace frontend
} // namespace js