Bug 1377007 - Implementing basic binjs-ref parser in SpiderMonkey;r?jorendorff,arai draft
authorDavid Teller <dteller@mozilla.com>
Tue, 05 Sep 2017 14:49:39 +0200
changeset 659084 6e39bf6d7d83a6f65a25532c7302c7b0cb3412b4
parent 659083 806dc641de390b0bd8021667f6ad118b44b44993
child 659085 8e28daca586244b02e2ddc8c95c0286e6fefaf40
push id78004
push userdteller@mozilla.com
push dateTue, 05 Sep 2017 13:32:18 +0000
reviewersjorendorff, arai
bugs1377007
milestone57.0a1
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
js/src/frontend/BinSource.cpp
js/src/frontend/BinSource.h
js/src/frontend/ParseContext.h
js/src/frontend/Parser.h
js/src/frontend/SharedContext.h
js/src/moz.build
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinSource.cpp
@@ -0,0 +1,2938 @@
+#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/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;
+
+// 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;
+}
+
+const std::string BINJS_SCOPE = "BINJS:Scope";
+const std::string BINJS_VAR_NAME = "BINJS:VarDeclaredNames";
+const std::string BINJS_LET_NAME = "BINJS:LetDeclaredNames";
+const std::string BINJS_CONST_NAME = "BINJS:ConstDeclaredNames";
+const std::string BINJS_CAPTURED_NAME = "BINJS:CapturedNames";
+const std::string BINJS_DIRECT_EVAL = "BINJS:HasDirectEval";
+
+
+Maybe<UniqueNode>
+BinASTParser::parse(const Vector<char>& data) {
+    return this->parse(data.begin(), data.end());
+}
+
+Maybe<UniqueNode>
+BinASTParser::parse(const char* start, const char* stop) {
+    BinTokenReaderTester reader(this->cx);
+    reader.init(start, start, stop, nullptr);
+
+    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(&reader, result)) {
+        return Nothing();
+    }
+
+    if (!reader.uninit()) {
+        Unused << this->raiseError(&reader, "parse()");
+        return Nothing();
+    }
+
+    return Move(Some(Move(result)));
+}
+
+bool
+BinASTParser::parseProgram(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "Program");
+    }
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "Program");
+    }
+
+    if (kind == BinKind::binjs_null) {
+        return true;
+    }
+    if (kind != BinKind::program) {
+        return this->raiseInvalidKind(reader, "Program", kind);
+    }
+
+    if (!this->parseBlockStatementAux(&sub, kind, fields, out)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseBlockStatement(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        // Already parsed.
+        return this->raiseAlreadyParsed(reader, "BlockStatement");
+    }
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "BlockStatement");
+    }
+
+    UniqueNode block(nullptr, this->nodeFree);
+    switch (kind) {
+        case BinKind::binjs_null:
+            return true;
+        case BinKind::block_statement:
+            if (!this->parseBlockStatementAux(&sub, kind, fields, block)) {
+                return false;
+            }
+            break;
+        default:
+            return this->raiseInvalidKind(reader, "BlockStatement", kind);
+    }
+
+    out = Move(block);
+    return true;
+}
+
+bool
+BinASTParser::parseScope(BinTokenReaderTester* reader, ScopeData& out)
+{
+    if (out.isSome()) {
+        // Already parsed.
+        return this->raiseAlreadyParsed(reader, "Scope");
+    }
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "Scope");
+    }
+
+    if (kind == BinKind::binjs_null) {
+        return true;
+    }
+    if (kind != BinKind::binjs_scope) {
+        return this->raiseInvalidKind(reader, "Scope", kind);
+    }
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::has_direct_eval:
+                if (!this->readBool(&sub, out.hasDirectEval)) {
+                    return false;
+                }
+                break;
+            case BinField::let_decl_names:
+                if (!this->parseStringList(&sub, &out.letNames)) {
+                    return false;
+                }
+                break;
+            case BinField::const_decl_names:
+                if (!this->parseStringList(&sub, &out.constNames)) {
+                    return false;
+                }
+                break;
+            case BinField::var_decl_names:
+                if (!this->parseStringList(&sub, &out.varNames)) {
+                    return false;
+                }
+                break;
+            case BinField::captured_names:
+                if (!this->parseStringSet(&sub, &out.capturedNames)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(&sub, "Scope", field);
+        }
+    }
+
+   return true;
+}
+
+bool
+BinASTParser::parseBlockStatementAux(BinTokenReaderTester* reader,
+    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(reader, scope)) {
+                    return false;
+                }
+                break;
+            case BinField::body:
+                if (!this->parseStatementList(reader, body)) {
+                    return false;
+                }
+                break;
+            case BinField::directives:
+                if (!this->parseDirectiveList(reader, directives)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(reader, "BlockStatement", field);
+        }
+    }
+
+    // Check if all fields have been parsed.
+    if (!body || !scope.isSome()) {
+        return this->raiseMissingField(reader, "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, 0));
+    if (!bindings) {
+        return this->raiseOOM();
+    }
+    bindings->constStart = 0;
+    UniqueNode result(new_<LexicalScopeNode>(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(BinTokenReaderTester* reader, MutableHandle<mozilla::Maybe<NameBag>> out) {
+    if (out.get()) {
+        return this->raiseAlreadyParsed(reader, "{String}");
+    }
+    uint32_t length;
+    BinTokenReaderTester sub(this->cx);
+
+    NameBag result(this->cx);
+    if (!result.init()) {
+        return this->raiseOOM();
+    }
+
+    if (!reader->readList(length, sub)) {
+        return this->raiseTokenError(reader, "{String}");
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+        RootedString string(this->cx);
+        if (!this->readString(&sub, &string)) {
+            return false;
+        }
+
+        if (!result.put(Move(string))) {
+            return this->raiseOOM();
+        }
+    }
+
+    out.set(Move(Some(Move(result))));
+    return true;
+}
+
+bool
+BinASTParser::parseStringList(BinTokenReaderTester* reader, MutableHandle<Maybe<Names>> out) {
+    if (out.get()) {
+        return this->raiseAlreadyParsed(reader, "[String]");
+    }
+    uint32_t length;
+    BinTokenReaderTester sub(this->cx);
+
+    Names result(this->cx);
+
+    if (!reader->readList(length, sub)) {
+        return this->raiseTokenError(reader, "[String]");
+    }
+
+    if (!result.reserve(length)) {
+        return this->raiseOOM();
+    }
+    for (uint32_t i = 0; i < length; ++i) {
+        RootedString string(this->cx);
+        if (!this->readString(&sub, &string)) {
+            return false;
+        }
+
+        if (!result.append(Move(string))) {
+            MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(); // Checked in the call to `reserve`.
+        }
+    }
+
+    out.set(Move(Some(Move(result))));
+    return true;
+}
+
+bool
+BinASTParser::parseStatementList(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "[Statement]");
+    }
+    uint32_t length;
+    BinTokenReaderTester sub(this->cx);
+
+    UniqueNode result(new_<ListNode>(PNK_STATEMENTLIST, TokenPos(0, 0)), this->nodeFree);
+    if (!result) {
+        return this->raiseOOM();
+    }
+
+    if (!reader->readList(length, sub)) {
+        return this->raiseTokenError(reader, "[Statement]");
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode statement(nullptr, this->nodeFree);
+        if (!this->parseStatement(&sub, statement)) {
+            return false;
+        }
+
+        result->append(statement.release()); // `result` knows how to deallocate `statement`.
+    }
+
+    result.swap(out);
+
+    return true;
+}
+
+bool
+BinASTParser::parseStatement(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "Statement");
+    }
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "Statement");
+    }
+
+    switch (kind) {
+        case BinKind::binjs_null:
+            return true;
+        case BinKind::empty_statement: {
+            // For compatibility with the source parser, this is not a NOP but an empty statement.
+            UniqueNode result(new_<UnaryNode>(PNK_SEMI, JSOP_NOP, TokenPos(0, 0), nullptr), this->nodeFree);
+            if (!result) {
+                return this->raiseOOM();
+            }
+            out = Move(result);
+            break;
+        }
+        case BinKind::block_statement: {
+            UniqueNode body(nullptr, this->nodeFree);
+            if (!this->parseBlockStatementAux(&sub, 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(&sub, kind, fields, body)) {
+                return false;
+            }
+            out = Move(body);
+            break;
+        }
+        case BinKind::debugger_statement: {
+            UniqueNode result(new_<DebuggerStatement>(TokenPos(0, 0)), 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(&sub, body)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::object:
+                        if (!this->parseExpression(&sub, expr)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "WithStatement", field);
+                }
+            }
+            if (!body || !expr) {
+                return this->raiseMissingField(reader, "WithStatement");
+            }
+
+            UniqueNode result(new_<BinaryNode>(PNK_WITH, JSOP_NOP, TokenPos(0, 0),
+                                            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(&sub, arg)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "ReturnStatement", field);
+                }
+            }
+
+            // `arg` is optional, so we don't check whether it's `nullptr`.
+            UniqueNode result(new_<UnaryNode>(PNK_RETURN, JSOP_RETURN, TokenPos(0, 0), arg.get()), 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(&sub, label)) {
+                            return false;
+                        }
+                        if (!label || !label->isKind(PNK_NAME)) {
+                            return this->raiseError(&sub, "Label MUST be an identifier");
+                        }
+                        break;
+                    case BinField::body:
+                        if (!this->parseStatement(&sub, body)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(&sub, "LabeledStatement", field);
+                }
+            }
+
+            if (!label || !body) {
+                return this->raiseMissingField(&sub, "LabeledStatement");
+            }
+
+            UniqueNode result(new_<LabeledStatement>(label->name(), body.get(), 0), 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(&sub, label)) {
+                        return false;
+                    }
+                    if (label && !label->isKind(PNK_NAME)) {
+                        return this->raiseError(&sub, "ContinueStatement - Label MUST be an identifier");
+                    }
+                    break;
+                default:
+                    return this->raiseInvalidField(&sub, "ContinueStatement", field);
+            }
+        }
+
+        if (kind == BinKind::break_statement) {
+            UniqueNode result(new_<BreakStatement>(label ? label->name() : nullptr, TokenPos(0, 0)), this->nodeFree);
+            if (!result) {
+                return this->raiseOOM();
+            }
+            out = Move(result);
+        } else {
+            UniqueNode result(new_<ContinueStatement>(label ? label->name() : nullptr, TokenPos(0, 0)), 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(&sub, test)) {
+                        return false;
+                    }
+                    break;
+                }
+                case BinField::consequent: {
+                    if (!this->parseStatement(&sub, consequent)) {
+                        return false;
+                    }
+                    break;
+                }
+                case BinField::alternate: {
+                    if (!this->parseStatement(&sub, alternate)) {
+                        return false;
+                    }
+                    break;
+                }
+                default:
+                    return this->raiseInvalidField(&sub, "IfStatement", field);
+            }
+        }
+
+        if (!test || !consequent) {
+            // Do not test `alternate`, since that value is optional.
+            return this->raiseMissingField(&sub, "IfStatement");
+        }
+
+        UniqueNode result(new_<TernaryNode>(PNK_IF, JSOP_NOP, 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(&sub, discriminant)) {
+                        return false;
+                    }
+                    break;
+                }
+                case BinField::cases: {
+                    if (!this->parseSwitchCaseList(&sub, cases)) {
+                        return false;
+                    }
+                    break;
+                }
+                default:
+                    return this->raiseInvalidField(&sub, "SwitchStatement", field);
+            }
+        }
+
+        if (!discriminant || !cases) {
+            return this->raiseMissingField(&sub, "SwtichStatement");
+        }
+
+        UniqueNode result(new_<BinaryNode>(PNK_SWITCH, JSOP_NOP, TokenPos(0, 0), discriminant.get(), cases.get()), this->nodeFree);
+        if (!result) {
+            return this->raiseOOM();
+        }
+
+        Unused << discriminant.release();
+        Unused << cases.release();
+        out = Move(result);
+        break;
+    }
+    case BinKind::switch_case: {
+
+        UniqueNode test(nullptr, this->nodeFree);
+        UniqueNode consequent(nullptr, this->nodeFree);
+
+        for (auto field: fields) {
+            switch (field) {
+                case BinField::test:
+                    if (!this->parseExpression(&sub, test)) {
+                        return false;
+                    }
+                    break;
+                case BinField::consequent:
+                    if (!this->parseStatementList(&sub, consequent)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    return this->raiseInvalidField(&sub, "SwitchCase", field);
+            }
+        }
+
+        UniqueNode result(new_<CaseClause>(test.get(), consequent.get(), 0), this->nodeFree);
+        if (!result) {
+            return this->raiseOOM();
+        }
+
+        Unused << test.release();
+        Unused << consequent.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(&sub, arg)) {
+                    return false;
+                }
+            } else {
+                return this->raiseInvalidField(&sub, "ThrowStatement", field);
+            }
+        }
+
+        if (!arg) {
+            return this->raiseMissingField(&sub, "ThrowStatement");
+        }
+
+        UniqueNode result(new_<UnaryNode>(PNK_THROW, JSOP_THROW, TokenPos(0, 0), arg.get()), 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(&sub, block)) {
+                        return false;
+                    }
+                    break;
+                case BinField::handler:
+                    if (!this->parseCatchClause(&sub, handler)) {
+                        return false;
+                    }
+                    break;
+                case BinField::finalizer:
+                    if (!this->parseBlockStatement(&sub, finalizer)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    return this->raiseInvalidField(&sub, "TryStatement", field);
+            }
+        }
+
+        if (!block || (!handler && !finalizer)) {
+            return this->raiseMissingField(&sub, "TryStatement");
+        }
+
+        if (!this->promoteToLexicalScope(block)) {
+            return false;
+        }
+
+        if (finalizer) {
+            if (!this->promoteToLexicalScope(finalizer)) {
+                return false;
+            }
+        }
+
+        UniqueNode result(new_<TernaryNode>(PNK_TRY, JSOP_NOP, block.get(), handler.get(), finalizer.get(), TokenPos(0, 0)), 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(&sub, test)) {
+                        return false;
+                    }
+                    break;
+                case BinField::body:
+                    if (!this->parseStatement(&sub, body)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    return this->raiseInvalidField(&sub, "DoWhileStatement", field);
+            }
+        }
+
+        if (!test || !body) {
+            return this->raiseMissingField(&sub, "DoWhileStatement");
+        }
+
+        if (kind == BinKind::while_statement) {
+            UniqueNode result(new_<BinaryNode>(PNK_WHILE, JSOP_NOP, TokenPos(0, 0), test.get(), body.get()), this->nodeFree);
+
+            if (!result) {
+                return this->raiseOOM();
+            }
+            out = Move(result);
+        } else {
+            UniqueNode result(new_<BinaryNode>(PNK_DOWHILE, JSOP_NOP, TokenPos(0, 0), body.get(), test.get()), 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(&sub, init)) {
+                        return false;
+                    }
+                    break;
+                case BinField::test:
+                    if (!this->parseExpression(&sub, test)) {
+                        return false;
+                    }
+                    break;
+                case BinField::update:
+                    if (!this->parseExpression(&sub, update)) {
+                        return false;
+                    }
+                    break;
+                case BinField::binjs_scope:
+                    if (!this->parseScope(&sub, scope)) {
+                        return false;
+                    }
+                    break;
+                case BinField::body:
+                    if (!this->parseStatement(&sub, body)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    return this->raiseInvalidField(&sub, "ForStatement", field);
+            }
+        }
+
+        if (!body) {
+            return this->raiseMissingField(&sub, "ForStatement");
+        }
+
+        UniqueNode forHead(new_<TernaryNode>(PNK_FORHEAD, JSOP_NOP, init.get(), test.get(), update.get(), TokenPos(0, 0)), this->nodeFree);
+        if (!forHead) {
+            return this->raiseOOM();
+        }
+        Unused << init.release();
+        Unused << update.release();
+        Unused << test.release();
+
+        UniqueNode result(new_<BinaryNode>(PNK_FOR, JSOP_NOP, TokenPos(0, 0),
+                                          forHead.get(), body.get()), 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(&sub, left)) {
+                        return false;
+                    }
+                    break;
+                case BinField::right:
+                    if (!this->parseExpression(&sub, right)) {
+                        return false;
+                    }
+                    break;
+                case BinField::body:
+                    if (!this->parseStatement(&sub, body)) {
+                        return false;
+                    }
+                    break;
+                case BinField::binjs_scope:
+                    if (!this->parseScope(&sub, scope)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    return this->raiseInvalidField(&sub, "ForInStatement", field);
+            }
+        }
+
+        if (!left || !right || !body) {
+            return this->raiseMissingField(&sub, "ForInStatement");
+        }
+
+        if (!this->promoteToLexicalScope(body)) {
+            return false;
+        }
+
+
+        UniqueNode forHead(new_<TernaryNode>(PNK_FORIN, JSOP_NOP, left.get(), nullptr, right.get(), TokenPos(0, 0)), this->nodeFree);
+        if (!forHead) {
+            return this->raiseOOM();
+        }
+
+        Unused << left.release();
+        Unused << right.release();
+
+        UniqueNode result(new_<BinaryNode>(PNK_FOR, JSOP_ITER, TokenPos(0, 0),
+                                          forHead.get(), body.get()), 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(&sub, kind, fields, result)) {
+            return false;
+        }
+
+        out = Move(result);
+        break;
+
+    }
+    case BinKind::variable_declaration:
+        if (!this->parseVariableDeclarationAux(&sub, kind, fields, out)) {
+            return false;
+        }
+        break;
+    default:
+        return this->raiseInvalidKind(&sub, "Statement", kind);
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseForHead(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "ForHead");
+    }
+
+    // This can be either a VarDecl or an Expression.
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+    BinKind kind;
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "ForStatement");
+    }
+
+    if (kind == BinKind::binjs_null) {
+        return true;
+    } else if (kind == BinKind::variable_declaration) {
+        return this->parseVariableDeclarationAux(&sub, kind, fields, out);
+    } else /* Parse as expression */ {
+        return this->parseExpressionAux(&sub, kind, fields, out);
+    }
+}
+
+bool
+BinASTParser::parseForInHead(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "ForInHead");
+    }
+
+    // This can be either a VarDecl or a Pattern.
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+    BinKind kind;
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "ForInHead");
+    }
+    if (kind == BinKind::binjs_null) {
+        return true;
+    } else if (kind == BinKind::variable_declaration) {
+        return this->parseVariableDeclarationAux(&sub, kind, fields, out);
+    } else /* Parse as pattern */ {
+        return this->parsePatternAux(&sub, kind, fields, out);
+    }
+}
+
+bool
+BinASTParser::parseFunctionAux(BinTokenReaderTester* reader, 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(reader, "Function");
+    }
+
+    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<std::string> 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(reader, id)) {
+                    return false;
+                }
+                if (id && !id->isKind(PNK_NAME)) {
+                    return this->raiseError(reader, "Function id MUST be an identifier");
+                }
+                break;
+            case BinField::params:
+                if (!this->parsePatternList(reader, params)) {
+                    return false;
+                }
+                break;
+            case BinField::body:
+                if (!this->parseStatement(reader, body)) {
+                    return false;
+                }
+                break;
+            case BinField::binjs_scope:
+                if (!this->parseScope(reader, scope)) {
+                    return false;
+                }
+                break;
+            case BinField::key:
+                if (kind != BinKind::object_method) {
+                    return this->raiseInvalidField(reader, "Functions other than ObjectMethod", field);
+                }
+                if (!this->parseExpression(reader, key)) {
+                    return false;
+                }
+                break;
+            case BinField::computed:
+                if (kind != BinKind::object_method) {
+                    return this->raiseInvalidField(reader, "Functions other than ObjectMethod", field);
+                }
+                if (!this->readBool(reader, computed)) {
+                    return false;
+                }
+                break;
+            case BinField::kind:
+                if (kind != BinKind::object_method) {
+                    return this->raiseInvalidField(reader, "Functions other than ObjectMethod", field);
+                }
+                if (!this->readString(reader, method_kind)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(reader, "Function", field);
+        }
+    }
+
+    if (!params || !body || !scope.isSome()) {
+        return this->raiseMissingField(reader, "Function");
+    }
+
+    if (kind == BinKind::function_declaration && !id) {
+        // The name is compulsory only for function declarations.
+        return this->raiseMissingField(reader, "FunctionDeclaration");
+    }
+
+    if (kind == BinKind::object_method) {
+        if (!key || !computed || !method_kind) {
+            return this->raiseMissingField(reader, "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)) {
+            UniqueNode list(new_<ListNode>(PNK_STATEMENTLIST, JSOP_NOP, body.get()), this->nodeFree);
+            if (!list) {
+                return this->raiseOOM();
+            }
+
+            Unused << body.release(); // Adopted by `list`.
+            body = Move(list);
+        }
+
+        // Promote to lexical scope.
+        if (!this->promoteToLexicalScope(body)) {
+            return false;
+        }
+    }
+    MOZ_ASSERT(body->getKind() == PNK_LEXICALSCOPE);
+    params->append(body.release());
+
+    const JSOp op =
+        kind == BinKind::object_method
+        ? JSOP_LAMBDA
+        : JSOP_NOP;
+    UniqueNode function(new_<CodeNode>(PNK_FUNCTION, op, TokenPos(0, 0)), this->nodeFree);
+    if (!function) {
+        return this->raiseOOM();
+    }
+
+    function->pn_funbox = funbox;
+    function->pn_body   = 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(reader, "ObjectMethod", *method_kind);
+        }
+        result = UniqueNode(this->new_<BinaryNode>(PNK_COLON, op, key.get(), function.get()), 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(BinTokenReaderTester* reader, const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "VariableDeclaration");
+    }
+
+    if (kind == BinKind::binjs_null) {
+        return true;
+    } else if (kind != BinKind::variable_declaration) {
+        return this->raiseInvalidKind(reader, "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<std::string> kindName;
+                if (!this->readString(reader, kindName)) {
+                    return false;
+                }
+                if (kindName.isNothing()) {
+                    return this->raiseMissingField(reader, "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(reader, "VariableDeclaration", *kindName);
+                }
+                break;
+            }
+            case BinField::declarations: {
+                if (result) {
+                    // Already parsed.
+                    return this->raiseAlreadyParsed(reader, "VariableDeclaration");
+                }
+
+                uint32_t length;
+                BinTokenReaderTester sub(this->cx);
+
+                if (!reader->readList(length, sub)) {
+                    return this->raiseTokenError(reader, "VariableDeclaration");
+                }
+
+                if (length == 0) {
+                    return this->raiseEmpty(reader, "VariableDeclaration");
+                }
+
+                UniqueNode root(new_<ListNode>(PNK_NOP /*Placeholder*/, JSOP_NOP/*Placeholder*/, TokenPos(0, 0)), this->nodeFree);
+                if (!root) {
+                    return this->raiseOOM();
+                }
+
+                UniqueNode first(nullptr, this->nodeFree);
+                if (!this->parseVariableDeclarator(&sub, first)) {
+                    return this->raiseOOM();
+                }
+                root->initList(first.release());
+
+
+                for (uint32_t i = 1; i < length; ++i) {
+                    UniqueNode current(nullptr, this->nodeFree);
+                    if (!this->parseVariableDeclarator(&sub, current)) {
+                        return false;
+                    }
+                    if (!current) {
+                        return this->raiseMissingField(&sub, "VariableDeclaration");
+                    }
+                    root->append(current.release());
+                }
+
+                result = Move(root);
+                break;
+            }
+            default:
+                return this->raiseInvalidField(reader, "VariableDeclaration", field);
+        }
+    }
+
+    if (!result || pnk == PNK_LIMIT) {
+        return this->raiseMissingField(reader, "VariableDeclaration");
+    }
+
+    result->setKind(pnk);
+    result->setOp(op);
+
+    MOZ_ASSERT(!result->isKind(PNK_NOP));
+    out = Move(result);
+
+    return true;
+}
+
+
+bool
+BinASTParser::parseExpression(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "Expression");
+    }
+
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+    BinKind kind;
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "Expression");
+    }
+
+    return this->parseExpressionAux(&sub, kind, fields, out);
+}
+
+bool
+BinASTParser::parseExpressionStatementAux(BinTokenReaderTester* reader, const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    MOZ_ASSERT(kind == BinKind::expression_statement);
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "ExpressionStatement");
+    }
+
+    UniqueNode result(nullptr, this->nodeFree);
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::expression:
+                if (!this->parseExpression(reader, result)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(reader, "ExpressionStatement", field);
+        }
+    }
+
+    if (!result) {
+        return this->raiseMissingField(reader, "ExpressionStatement");
+    }
+
+    out = UniqueNode(new_<UnaryNode>(PNK_SEMI, JSOP_NOP, TokenPos(0, 0), result.get()), this->nodeFree);
+
+    if (!out) {
+        return this->raiseOOM();
+    }
+    Unused << result.release(); // Now part of `out`.
+
+    return true;
+}
+
+bool
+BinASTParser::parseVariableDeclarator(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "VariableDeclarator");
+    }
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "VariableDeclarator");
+    }
+
+    if (kind != BinKind::variable_declarator) {
+        return this->raiseInvalidKind(reader, "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(&sub, id)) {
+                    return false;
+                }
+                break;
+            case BinField::init:
+                if (!this->parseExpression(&sub, init)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(reader, "VariableDeclarator", field);
+        }
+    }
+
+    if (!id) {
+        return this->raiseMissingField(reader, "VariableDeclarator");
+    }
+
+    UniqueNode result(nullptr, this->nodeFree);
+
+    // FIXME: Documentation in ParseNode is clearly obsolete.
+    if (id->isKind(PNK_NAME)) {
+        // var foo [= bar]
+        RootedAtom atom(cx, id->pn_atom);
+        result = UniqueNode(new_<NameNode>(PNK_NAME, JSOP_NOP, atom, TokenPos(0, 0)), 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(reader, "VariableDeclarator");
+        }
+        result = UniqueNode(new_<BinaryNode>(PNK_ASSIGN, JSOP_NOP, TokenPos(0, 0), nullptr, nullptr), this->nodeFree);
+        if (!result) {
+            return this->raiseOOM();
+        }
+
+        result->pn_left = id.release();
+        result->pn_right = init.release();
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseExpressionOrElisionList(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "[Expression|null]");
+    }
+
+    uint32_t length;
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->readList(length, sub)) {
+        return this->raiseTokenError(reader, "[Expression|null]");
+    }
+
+    UniqueNode result(new_<ListNode>(PNK_ARRAY, TokenPos(0, 0)), this->nodeFree);
+    if (!result) {
+        return this->raiseOOM();
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode expr(nullptr, this->nodeFree);
+        if (!this->parseExpression(&sub, expr)) {
+            return false;
+        }
+
+        if (!expr) {
+            expr = UniqueNode(new_<NullaryNode>(PNK_ELISION, TokenPos(0, 0)), this->nodeFree);
+            if (!expr) {
+                return this->raiseOOM();
+            }
+        }
+
+        result->append(expr.release());
+    }
+
+    out = Move(result);
+    return true;
+}
+
+
+bool
+BinASTParser::parseSwitchCaseList(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "[SwitchCase]");
+    }
+
+    uint32_t length;
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->readList(length, sub)) {
+        return this->raiseTokenError(reader, "[SwitchCase]");
+    }
+
+    UniqueNode result(new_<ListNode>(PNK_STATEMENTLIST, TokenPos(0, 0)), this->nodeFree);
+    if (!result) {
+        return this->raiseOOM();
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode case_(nullptr, this->nodeFree);
+        if (!this->parseSwitchCase(&sub, case_)) {
+            return false;
+        }
+
+        if (!case_) {
+            return this->raiseEmpty(reader, "[SwitchCase]");
+        }
+
+        result->append(case_.release());
+    }
+
+    if (!this->promoteToLexicalScope(result)) {
+        return false;
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseExpressionAux(BinTokenReaderTester* reader, const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    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(reader, &id)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "Identifier", field);
+                }
+            }
+
+            if (!id) {
+                return this->raiseMissingField(reader, "Identifier");
+            }
+
+            UniqueNode result(new_<NameNode>(PNK_NAME, JSOP_GETNAME, id, TokenPos(0, 0)), 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(reader, value)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "BooleanLiteral", field);
+                }
+            }
+
+            if (!value) {
+                return this->raiseMissingField(reader, "BooleanLiteral");
+            }
+
+            UniqueNode result(new_<BooleanLiteral>(*value, TokenPos(0, 0)), 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(reader, "DirectiveLiteral (not truly a literal)");
+        case BinKind::null_literal: {
+            UniqueNode result(new_<NullLiteral>(TokenPos(0, 0)), 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(reader, value)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "NumericLiteral", field);
+                }
+            }
+
+            if (!value) {
+                return this->raiseMissingField(reader, "NumericLiteral");
+            }
+
+            UniqueNode result(new_<NullaryNode>(PNK_NUMBER, TokenPos(0, 0)), this->nodeFree);
+            if (!result) {
+                return this->raiseOOM();
+            }
+            result->initNumber(*value, DecimalPoint::HasDecimal); // HasDecimal is an arbitrary choise.
+            out = Move(result);
+            break;
+        }
+        case BinKind::regexp_literal: {
+            RootedAtom pattern(this->cx);
+            Maybe<std::string> flags;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::pattern:
+                        if (!this->readString(reader, &pattern)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::flags:
+                        if (!this->readString(reader, flags)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "RegExpLiteral", field);
+                }
+            }
+
+            if (!pattern || !flags) {
+                return this->raiseMissingField(reader, "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(reader, "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();
+            }
+
+
+            ObjectBox* objbox = this->newObjectBox(reobj);
+            if (!objbox) {
+                return this->raiseOOM();
+            }
+            UniqueNode result(new_<RegExpLiteral>(objbox, TokenPos(0, 0)), this->nodeFree);
+            if (!result) {
+                // FIXME: I *think* that `objbox` is traced.
+                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(reader, &value)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "StringLiteral", field);
+                }
+            }
+
+            if (!value) {
+                return this->raiseMissingField(reader, "StringLiteral");
+            }
+
+            UniqueNode result(new_<NullaryNode>(PNK_STRING, JSOP_NOP, TokenPos(0, 0), value), this->nodeFree);
+            if (!result) {
+                return this->raiseOOM();
+            }
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::this_expression: {
+            UniqueNode thisName(nullptr, this->nodeFree);
+            if (this->parseContext->sc()->thisBinding() == ThisBinding::Function) {
+                thisName = UniqueNode(new_<NameNode>(PNK_NAME, JSOP_GETNAME, this->cx->names().dotThis, TokenPos(0, 0)), this->nodeFree);
+                if (!thisName) {
+                    return this->raiseOOM();
+                }
+            }
+
+            UniqueNode result(new_<ThisLiteral>(TokenPos(0, 0), 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(reader, 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(reader, 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(reader, kind, fields, result)) {
+                return false;
+            }
+            result->setOp(JSOP_LAMBDA);
+            out = Move(result);
+            break;
+        }
+        case BinKind::unary_expression:
+        case BinKind::update_expression: {
+
+            UniqueNode expr(nullptr, this->nodeFree);
+            Maybe<std::string> operation;
+            Maybe<bool> prefix; // FIXME: Ignored for unary_expression?
+
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::operator_:
+                        if (!this->readString(reader, operation)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::prefix:
+                        if (!this->readBool(reader, prefix)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::argument:
+                        if (!this->parseExpression(reader, expr)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "UpdateExpression", field);
+                }
+            }
+
+            if (!expr || operation.isNothing() || prefix.isNothing()) {
+                return this->raiseMissingField(reader, "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(reader, "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(reader, "UpdateOperator", *operation);
+                }
+            }
+
+            UniqueNode result(new_<UnaryNode>(pnk, op, TokenPos(0, 0), 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<std::string> operation;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::left:
+                        if (!this->parseExpression(reader, left)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::right:
+                        if (!this->parseExpression(reader, right)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::operator_:
+                        if (!this->readString(reader, operation)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "LogicalExpression | BinaryExpression", field);
+                }
+            }
+
+            if (!left || !right || operation.isNothing()) {
+                return this->raiseMissingField(reader, "LogicalExpression | BinaryExpression");
+            }
+
+            // FIXME: Instead of std::string, we should use atoms and comparison
+            // between atom ptr.
+            // FIXME: We should probably turn associative operations into lists.
+            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(reader, "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 {
+                UniqueNode list(new_<ListNode>(pnk, JSOP_NOP, TokenPos(0, 0)), 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<std::string> operation;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::left:
+                        if (!this->parseExpression(reader, left)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::right:
+                        if (!this->parseExpression(reader, right)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::operator_:
+                        if (!this->readString(reader, operation)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "AssignmentExpression", field);
+                }
+            }
+
+            if (!left || !right || operation.isNothing()) {
+                return this->raiseMissingField(reader, "AssignmentExpression");
+            }
+
+            // FIXME: Instead of std::string, 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(reader, "AssignmentOperator", *operation);
+            }
+
+            UniqueNode list(new_<BinaryNode>(pnk, JSOP_NOP, TokenPos(0, 0), 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(reader, 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(reader, test)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::alternate:
+                        if (!this->parseExpression(reader, alternate)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::consequent:
+                        if (!this->parseExpression(reader, consequent)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "ConditionalExpression", field);
+                }
+            }
+
+            if (!test || !alternate || !consequent) {
+                return this->raiseMissingField(reader, "ConditionalExpression");
+            }
+
+            UniqueNode result(new_<ConditionalExpression>(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(reader, callee)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::arguments:
+                        if (!this->parseExpressionOrElisionList(reader, arguments)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "NewExpression", field);
+                }
+            }
+
+            if (!callee || !arguments) {
+                return this->raiseMissingField(reader, "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(reader, sequence)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "SequenceExpression", field);
+                }
+            }
+
+            if (!sequence) {
+                this->raiseMissingField(reader, "SequenceExpression");
+            }
+
+            sequence->setKind(PNK_COMMA);
+            out = Move(sequence);
+            break;
+        }
+        default:
+            return this->raiseInvalidKind(reader, "Expression", kind);
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseMemberExpressionAux(BinTokenReaderTester* reader, const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "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(reader, object)) {
+                    return false;
+                }
+                break;
+            case BinField::property:
+                if (!this->parseExpression(reader, property)) {
+                    return false;
+                }
+                break;
+            case BinField::computed:
+                if (!this->readBool(reader, isComputed)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(reader, "MemberExpression", field);
+        }
+    }
+
+    if (!object || !property || !isComputed) {
+        return this->raiseMissingField(reader, "MemberExpression");
+    }
+
+    UniqueNode result(nullptr, this->nodeFree);
+    if (!*isComputed) {
+        if (!property->isKind(PNK_NAME)) {
+            return this->raiseError(reader, "MemberExpression (computed implies name)");
+        }
+        PropertyName* name = property->pn_atom->asPropertyName();
+        result = UniqueNode(new_<PropertyAccess>(object.get(), name, 0, 0), this->nodeFree);
+    } else {
+        result = UniqueNode(new_<PropertyByValue>(object.get(), property.get(), 0, 0), this->nodeFree);
+    }
+    if (!result) {
+        return this->raiseOOM();
+    }
+
+    Unused << object.release();
+    Unused << property.release();
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseDirective(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "Directive");
+    }
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "Directive");
+    }
+
+    if (kind != BinKind::directive) {
+        return this->raiseInvalidKind(reader, "Directive", kind);
+    }
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::value:
+            if (!this->parseDirectiveLiteral(&sub, out)) {
+                return false;
+            }
+            break;
+        default:
+            return this->raiseInvalidField(&sub, "Directive", field);
+        }
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseDirectiveLiteral(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "DirectiveLiteral");
+    }
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "DirectiveLiteral");
+    }
+
+    if (kind != BinKind::directive_literal) {
+        return this->raiseInvalidKind(reader, "DirectiveLiteral", kind);
+    }
+
+    RootedAtom value(this->cx);
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::value:
+                if (!this->readString(&sub, &value)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(&sub, "DirectiveLiteral", field);
+        }
+    }
+
+    if (!value) {
+        return this->raiseMissingField(reader, "DirectiveLiteral");
+    }
+
+    UniqueNode result(new_<NullaryNode>(PNK_STRING, JSOP_NOP, TokenPos(0, 0), value), this->nodeFree);
+    if (!result) {
+        return this->raiseOOM();
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseDirectiveList(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "[Directive]");
+    }
+
+    uint32_t length;
+    BinTokenReaderTester sub(this->cx);
+    if (!reader->readList(length, sub)) {
+        return this->raiseTokenError(reader, "[Directive]");
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode directive(nullptr, this->nodeFree); // Ignored
+        if (!this->parseDirective(&sub, directive)) {
+            return false;
+        }
+        if (!directive) {
+            return this->raiseEmpty(&sub, "[Directive]");
+        }
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseSwitchCase(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "SwitchCase");
+    }
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "SwitchCase");
+    }
+
+    if (kind != BinKind::switch_case) {
+        return this->raiseInvalidKind(reader, "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(&sub, test)) {
+                    return false;
+                }
+                break;
+            case BinField::consequent:
+                if (!this->parseStatementList(&sub, statements)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(&sub, "SwitchCase", field);
+        }
+    }
+
+    if (!statements) {
+        return this->raiseMissingField(&sub, "SwitchCase");
+    }
+    MOZ_ASSERT(statements->isKind(PNK_STATEMENTLIST));
+
+    UniqueNode result(nullptr, this->nodeFree);
+
+    result = UniqueNode(new_<CaseClause>(test.get(), statements.get(), 0), this->nodeFree);
+    if (!result) {
+        return this->raiseOOM();
+    }
+
+    Unused << test.release();
+    Unused << statements.release();
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseCatchClause(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "CatchClause");
+    }
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "CatchClause");
+    }
+
+    if (kind == BinKind::binjs_null) {
+        return true;
+    }
+    if (kind != BinKind::catch_clause) {
+        return this->raiseInvalidKind(reader, "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(&sub, param)) {
+                    return false;
+                }
+                break;
+            case BinField::body:
+                if (!this->parseBlockStatement(&sub, body)) {
+                    return false;
+                }
+                break;
+            case BinField::binjs_scope:
+                if (!this->parseScope(&sub, scope)) {
+                    return false;
+                }
+                break;
+            default:
+                return this->raiseInvalidField(&sub, "CatchClause", field);
+        }
+    }
+
+    if (!param || !body) {
+        return this->raiseMissingField(&sub, "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;
+        }
+    }
+
+    UniqueNode catchList(new_<ListNode>(PNK_CATCHLIST, TokenPos(0, 0)), this->nodeFree);
+    if (!catchList) {
+        return this->raiseOOM();
+    }
+
+    catchList->append(catchClause.release());
+
+    out = Move(catchList);
+    return true;
+}
+
+bool
+BinASTParser::parsePatternList(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "[Pattern]");
+    }
+
+    UniqueNode result(this->new_<ListNode>(PNK_PARAMSBODY,
+        TokenPos(0, 0)), this->nodeFree);
+
+    uint32_t length;
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->readList(length, sub)) {
+        return this->raiseTokenError(reader, "[Pattern]");
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode pattern(nullptr, this->nodeFree);
+        if (!this->parsePattern(&sub, pattern)) {
+            return false;
+        }
+
+        if (!pattern) {
+            return this->raiseEmpty(&sub, "[Pattern]");
+        }
+
+        result->append(pattern.release());
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parsePattern(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "Pattern");
+    }
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "Pattern");
+    }
+
+    return this->parsePatternAux(&sub, kind, fields, out);
+}
+
+bool
+BinASTParser::parsePatternAux(BinTokenReaderTester* reader, const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "Pattern | null");
+    }
+
+    switch (kind) {
+        case BinKind::binjs_null:
+            return true;
+        case BinKind::member_expression: {
+            UniqueNode result(nullptr, this->nodeFree);
+            if (!this->parseMemberExpressionAux(reader, 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(reader, &id)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(reader, "Identifier as Pattern", field);
+                }
+            }
+
+            if (!id) {
+                return this->raiseMissingField(reader, "Identifier as Pattern");
+            }
+
+            UniqueNode result(new_<NameNode>(PNK_NAME, JSOP_GETNAME, id, TokenPos(0, 0)), this->nodeFree);
+            if (!result) {
+                return this->raiseOOM();
+            }
+
+            out = Move(result);
+            break;
+        }
+        default:
+            return this->raiseInvalidKind(reader, "Pattern | null", kind);
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseObjectMember(BinTokenReaderTester* reader, UniqueNode& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "ObjectMember");
+    }
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->taggedTuple(kind, fields, &sub)) {
+        return this->raiseTokenError(reader, "ObjectMember");
+    }
+
+    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(&sub, key)) {
+                            return false;
+                        }
+                        if (key) {
+                            if (key->isKind(PNK_NAME)) {
+                                UniqueNode name(new_<NullaryNode>(PNK_OBJECT_PROPERTY_NAME, JSOP_NOP, key->pn_pos, key->pn_atom), this->nodeFree);
+                                key->pn_atom = nullptr;
+                                key = Move(name);
+                            }
+                        }
+                        break;
+                    case BinField::computed:
+                        if (!this->readBool(&sub, computed)) {
+                            return false;
+                        }
+                        break;
+                    case BinField::value:
+                        if (!this->parseExpression(&sub, value)) {
+                            return false;
+                        }
+                        break;
+                    default:
+                        return this->raiseInvalidField(&sub, "ObjectMember", field);
+                }
+            }
+
+            if (!key || !computed || !value) {
+                return this->raiseMissingField(&sub, "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(&sub, "ObjectMember key kind");
+            }
+
+            UniqueNode result(this->new_<BinaryNode>(PNK_COLON, JSOP_INITPROP, key.get(), value.get()), 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(&sub, kind, fields, result)) {
+                return false;
+            }
+            if (!result) {
+                return this->raiseEmpty(&sub, "ObjectMethod");
+            }
+
+            MOZ_ASSERT(result->isKind(PNK_COLON));
+
+            out = Move(result);
+            return true;
+        }
+        default:
+            return this->raiseInvalidKind(&sub, "ObjectMember", kind);
+    }
+}
+
+bool
+BinASTParser::parseObjectMemberList(BinTokenReaderTester* reader, UniqueNode& out) {
+    uint32_t length;
+    BinTokenReaderTester sub(this->cx);
+
+    if (!reader->readList(length, sub)) {
+        return this->raiseTokenError(reader, "[ObjectMember]");
+    }
+
+    UniqueNode result(this->new_<ListNode>(PNK_NOP /*Placeholder*/, TokenPos(0, 0)), this->nodeFree);
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode keyValue(nullptr, this->nodeFree);
+        if (!this->parseObjectMember(&sub, keyValue)) {
+            return false;
+        }
+        if (!keyValue) {
+            return this->raiseEmpty(&sub, "[ObjectMember]");
+        }
+
+        result->append(keyValue.release());
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::readString(BinTokenReaderTester* reader, MutableHandleString out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "String");
+    }
+
+    RootedAtom atom(this->cx);
+    if (!this->readString(reader, &atom)) {
+        return false;
+    }
+
+    out.set(atom);
+    return true;
+}
+
+bool
+BinASTParser::readString(BinTokenReaderTester* reader, MutableHandleAtom out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "String");
+    }
+
+    Maybe<std::string> string;
+    if (!this->readString(reader, string)) {
+        return false;
+    }
+
+    if (!string) {
+        return true;
+    }
+
+    RootedAtom atom(cx, Atomize(this->cx, string->data(), string->length()));
+    if (!atom) {
+        return this->raiseOOM();
+    }
+
+    out.set(Move(atom));
+    return true;
+}
+
+bool
+BinASTParser::readString(BinTokenReaderTester* reader, MutableHandle<PropertyName*> out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "String");
+    }
+
+    RootedAtom atom(cx);
+
+    if (!this->readString(reader, &atom)) {
+        return false;
+    }
+
+    if (!atom) {
+        out.set(nullptr);
+    } else {
+        out.set(atom->asPropertyName());
+    }
+    return true;
+}
+
+bool
+BinASTParser::readString(BinTokenReaderTester* reader, Maybe<std::string>& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "String");
+    }
+
+    if (!reader->readMaybeString(out)) {
+        return this->raiseTokenError(reader, "String");
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::readNumber(BinTokenReaderTester* reader, Maybe<double>& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "Number");
+    }
+
+    return reader->readMaybeF64(&out);
+}
+
+bool
+BinASTParser::readBool(BinTokenReaderTester* reader, Maybe<bool>& out) {
+    if (out) {
+        return this->raiseAlreadyParsed(reader, "Bool");
+    }
+
+    return reader->readMaybeBool(&out);
+}
+
+bool
+BinASTParser::raiseInvalidKind(BinTokenReaderTester* reader, const std::string& superKind, const BinKind kind) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid kind %d", superKind.c_str(), kind);
+    return this->raiseError(reader, "raiseInvalidKind");
+}
+
+bool
+BinASTParser::raiseInvalidField(BinTokenReaderTester* reader, const std::string& kind, const BinField field) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid field %d", kind.c_str(), field);
+    return this->raiseError(reader, "raiseInvalidField");
+}
+
+bool
+BinASTParser::raiseInvalidEnum(BinTokenReaderTester* reader, const std::string& kind, const std::string& value) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid enum value %s", kind.c_str(), value.c_str());
+    return this->raiseError(reader, "raiseInvalidEnum");
+}
+
+bool
+BinASTParser::raiseMissingField(BinTokenReaderTester* reader, const std::string& kind) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, missing field", kind.c_str());
+    return this->raiseError(reader, "raiseMissingField");
+}
+
+bool
+BinASTParser::raiseTokenError(BinTokenReaderTester* reader, const std::string& kind) {
+    return this->raiseError(reader, "raiseTokenError");
+}
+
+bool
+BinASTParser::raiseAlreadyParsed(BinTokenReaderTester* reader, const std::string& kind) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, already parsed", kind.c_str());
+    return this->raiseError(reader, "raiseAlreadyParsed");
+}
+
+bool
+BinASTParser::raiseEmpty(BinTokenReaderTester* reader, const std::string& kind) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, empty data", kind.c_str());
+    return this->raiseError(reader, "raiseEmpty");
+}
+
+bool
+BinASTParser::raiseOOM() {
+    cx->recoverFromOutOfMemory();
+    MOZ_CRASH("Debugging OOM");
+    return false;
+}
+
+
+bool
+BinASTParser::raiseError(BinTokenReaderTester* reader, const std::string& method) {
+    TokenPos pos;
+    reader->latestToken(pos);
+    fprintf(stderr, "DEBUG: BinASTParser::raiseError() from %s at %u -> %u\n",
+        method.c_str(),
+        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 = 0; // FIXME: We should keep the tokenizer somewhere
+    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
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinSource.h
@@ -0,0 +1,331 @@
+#ifndef frontend_BinSource_h
+#define frontend_BinSource_h
+
+/**
+ * A Binary AST parser.
+ *
+ * At the time of this writing, this parser implements the grammar of ES5
+ * and trusts its input (in particular, variable declarations).
+ */
+
+#include <mozilla/Maybe.h>
+
+#include "js/GCHashTable.h"
+#include "js/GCVector.h"
+
+#include "frontend/BinTokenReaderTester.h"
+#include "frontend/ParseContext.h"
+#include "frontend/ParseNode.h"
+#include "frontend/SharedContext.h"
+
+namespace js {
+namespace frontend {
+
+class BinASTParser;
+
+/**
+ * Utility class, used to define UniquePtr<ParseNode>
+ */
+class ParseNodeDeleter {
+public:
+    ParseNodeDeleter(ParseNodeAllocator& alloc_)
+        : alloc(&alloc_)
+    { }
+    ParseNodeDeleter(const ParseNodeDeleter& deleter)
+        : alloc(deleter.alloc)
+    { }
+    void operator=(const ParseNodeDeleter&& other)
+    {
+        alloc = Move(other.alloc);
+    }
+protected:
+    // Actually perform deletion. Designed to be used only by UniquePtr.
+    void operator()(ParseNode* ptr) {
+        MOZ_ASSERT(ptr);
+        alloc->freeTree(ptr);
+    }
+    ParseNodeAllocator* alloc; // Non-owned.
+    friend class mozilla::UniquePtr<ParseNode, ParseNodeDeleter>;
+};
+
+/**
+ * Representation of scope data provided as part of the Binary AST.
+ */
+struct ScopeData MOZ_STACK_CLASS {
+    using NameBag = JS::GCHashSet<JSString*>;
+    using Names = JS::GCVector<JSString*, 8>;
+public:
+    ScopeData(JSContext* cx)
+      : letNames(cx)
+      , constNames(cx)
+      , varNames(cx)
+      , capturedNames(cx)
+    { }
+
+    // `true` iff the scope data has been filled.
+    bool isSome() const {
+        if (hasDirectEval.isSome()) {
+            MOZ_ASSERT(letNames.get().isSome());
+            MOZ_ASSERT(constNames.get().isSome());
+            MOZ_ASSERT(varNames.get().isSome());
+            MOZ_ASSERT(capturedNames.get().isSome());
+            return true;
+        } else {
+            MOZ_ASSERT(!letNames.get().isSome());
+            MOZ_ASSERT(!constNames.get().isSome());
+            MOZ_ASSERT(!varNames.get().isSome());
+            MOZ_ASSERT(!capturedNames.get().isSome());
+            return false;
+        }
+    }
+
+    // `true` iff this scope contains lexically declared names.
+    bool hasLexNames() const {
+        if (!this->isSome()) {
+            return false;
+        }
+        if (!letNames.get()->empty()) {
+            return true;
+        }
+        if (!constNames.get()->empty()) {
+            return true;
+        }
+        return false;
+    }
+    mozilla::Maybe<bool> hasDirectEval;
+    JS::Rooted<mozilla::Maybe<Names>> letNames;
+    JS::Rooted<mozilla::Maybe<Names>> constNames;
+    JS::Rooted<mozilla::Maybe<Names>> varNames;
+    JS::Rooted<mozilla::Maybe<NameBag>> capturedNames;
+};
+
+/**
+ * The parser for a Binary AST.
+ *
+ * By design, this parser never needs to backtrack or look ahead. Errors are not
+ * recoverable.
+ */
+class BinASTParser: private JS::AutoGCRooter, public ErrorReporter
+{
+    using NameBag = JS::GCHashSet<JSString*>;
+    using Names = JS::GCVector<JSString*, 8>;
+
+public:
+    using UniqueNode = UniquePtr<ParseNode, ParseNodeDeleter>;
+
+    BinASTParser(JSContext* cx_, LifoAlloc& alloc_, UsedNameTracker& usedNames_, const JS::ReadOnlyCompileOptions& options)
+        : AutoGCRooter(cx_, BINPARSER)
+        , traceListHead(nullptr)
+        , options_(options)
+        , cx(cx_)
+        , alloc(alloc_)
+        , nodeAlloc(cx_, alloc_)
+        , nodeFree(nodeAlloc)
+        , parseContext(nullptr)
+        , usedNames(usedNames_)
+    {
+         cx_->frontendCollectionPool().addActiveCompilation();
+    }
+
+    /**
+     * Parse a buffer, returning a node (which may be nullptr) in case of success
+     * or Nothing() in case of error.
+     *
+     * The instance of `UniqueNode` MAY NOT survive the `BinASTParser`. If you
+     * wish to own the instance of `UniqueNode`, you need to `release()` the
+     * `UniquePtr`.
+     *
+     * In case of error, the parser reports the JS error.
+     */
+    Maybe<UniqueNode> parse(const char* start, const char* stop);
+    Maybe<UniqueNode> parse(const Vector<char>& data);
+
+private:
+    // --- Raise errors.
+
+    bool raiseTokenError(BinTokenReaderTester*, const std::string& kind);
+    bool raiseInvalidKind(BinTokenReaderTester*, const std::string& superKind, const BinKind kind);
+    bool raiseInvalidField(BinTokenReaderTester*, const std::string& kind, const BinField field);
+    bool raiseInvalidEnum(BinTokenReaderTester*, const std::string& kind, const std::string& value);
+    bool raiseMissingField(BinTokenReaderTester*, const std::string& kind);
+    bool raiseAlreadyParsed(BinTokenReaderTester*, const std::string& kind);
+    bool raiseEmpty(BinTokenReaderTester*, const std::string& kind);
+    bool raiseOOM();
+    bool raiseError(BinTokenReaderTester*, const std::string& method);
+
+    // --- Parse full nodes (methods are sorted by alphabetical order)
+    //
+    // Each of these methods accepts `binjs_null` as a valid node, in which case
+    // it returns the `nullptr` node.
+    // Each of these methods raises `raiseAlreadyParsed` if called with a non-nullptr
+    // value of `out`.
+    bool parseBlockStatement(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseCatchClause(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseDirective(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseDirectiveLiteral(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseExpression(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseForHead(BinTokenReaderTester* rader, UniqueNode& out);
+    bool parseForInHead(BinTokenReaderTester* rader, UniqueNode& out);
+    bool parseObjectMember(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parsePattern(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseProgram(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseScope(BinTokenReaderTester* reader, ScopeData& out);
+    bool parseStatement(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseSwitchCase(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseVariableDeclarator(BinTokenReaderTester* reader, UniqueNode&);
+
+    // --- Parse lists of nodes (methods are sorted by alphabetical order)
+    //
+    // Each of these methods raises `raiseAlreadyParsed` if called with a non-nullptr
+    // value of `out`. The list returned is always non-nullptr.
+
+    bool parseDirectiveList(BinTokenReaderTester* reader, UniqueNode&);
+    // Produces a list of expressions. Empty expressions are replaced with PNK_ELISION.
+    bool parseExpressionOrElisionList(BinTokenReaderTester* reader, UniqueNode& out);
+
+    // Returns a list of PNK_COLON.
+    bool parseObjectMemberList(BinTokenReaderTester* reader, UniqueNode&);
+
+    bool parsePatternList(BinTokenReaderTester* reader, UniqueNode&);
+    bool parseStatementList(BinTokenReaderTester* reader, UniqueNode& out);
+    bool parseStringList(BinTokenReaderTester* reader, MutableHandle<mozilla::Maybe<Names>> out);
+    bool parseStringList(BinTokenReaderTester* reader, MutableHandle<Names> out);
+    bool parseStringSet(BinTokenReaderTester* reader, MutableHandle<mozilla::Maybe<NameBag>>);
+    bool parseStringSet(BinTokenReaderTester* reader, MutableHandle<NameBag>);
+    bool parseSwitchCaseList(BinTokenReaderTester* reader, UniqueNode& out);
+
+    // --- Parse the contents of a node whose kind has already been determined.
+
+    bool parseBlockStatementAux(BinTokenReaderTester* reader, const BinKind kind, const BinTokenReaderTester::BinFields& fields, UniqueNode& out);
+    bool parseExpressionStatementAux(BinTokenReaderTester* reader, const BinKind kind, const BinTokenReaderTester::BinFields& fields, UniqueNode& out);
+    bool parseExpressionAux(BinTokenReaderTester* reader, const BinKind kind, const BinTokenReaderTester::BinFields& fields, UniqueNode& out);
+    bool parseFunctionAux(BinTokenReaderTester* reader, const BinKind kind, const BinTokenReaderTester::BinFields& fields, UniqueNode& out);
+    bool parseMemberExpressionAux(BinTokenReaderTester* reader, const BinKind kind, const BinTokenReaderTester::BinFields& fields, UniqueNode& out);
+    bool parsePatternAux(BinTokenReaderTester* reader, const BinKind kind, const BinTokenReaderTester::BinFields& fields, UniqueNode& out);
+    bool parseVariableDeclarationAux(BinTokenReaderTester* reader, const BinKind kind, const BinTokenReaderTester::BinFields& fields, UniqueNode& out);
+
+    // --- Utilities.
+
+    // Modifies in place a node to ensure that it holds a lexical scope.
+    bool promoteToLexicalScope(UniqueNode& node);
+
+    // Store any lexically-declared variables in a node. The node MUST be a lexical scope.
+    bool storeLexicalScope(UniqueNode& body, ScopeData&& scope);
+
+    // `true` if `name` is used in the subtree
+    bool hasUsedName(HandlePropertyName name);
+
+    bool readString(BinTokenReaderTester* reader, mozilla::Maybe<std::string>&);
+    bool readString(BinTokenReaderTester* reader, MutableHandleString);
+    bool readString(BinTokenReaderTester* reader, MutableHandleAtom);
+    bool readString(BinTokenReaderTester* reader, MutableHandle<PropertyName*>);
+    bool readBool(BinTokenReaderTester* reader, mozilla::Maybe<bool>&);
+    bool readNumber(BinTokenReaderTester* reader, mozilla::Maybe<double>&);
+
+    const ReadOnlyCompileOptions& options() const override {
+        return this->options_;
+    }
+
+    // Names
+
+    // --- GC.
+
+    /* List of objects allocated during parsing, for GC tracing. */
+    ObjectBox* traceListHead;
+    void trace(JSTracer* trc)
+    {
+        ObjectBox::TraceList(trc, this->traceListHead);
+    }
+
+
+public:
+    ObjectBox* newObjectBox(JSObject* obj)
+    {
+        MOZ_ASSERT(obj);
+
+        /*
+         * We use JSContext.tempLifoAlloc to allocate parsed objects and place them
+         * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
+         * arenas containing the entries must be alive until we are done with
+         * scanning, parsing and code generation for the whole script or top-level
+         * function.
+         */
+
+         ObjectBox* objbox = alloc.new_<ObjectBox>(obj, traceListHead);
+         if (!objbox) {
+             ReportOutOfMemory(this->cx);
+             return nullptr;
+          }
+
+          traceListHead = objbox;
+
+          return objbox;
+      }
+
+
+      ParseNode* allocParseNode(size_t size) {
+          MOZ_ASSERT(size == sizeof(ParseNode));
+          return static_cast<ParseNode*>(nodeAlloc.allocNode());
+      }
+
+      ParseNode* cloneNode(const ParseNode& other) {
+          ParseNode* node = allocParseNode(sizeof(ParseNode));
+          if (!node)
+              return nullptr;
+           mozilla::PodAssign(node, &other);
+           return node;
+       }
+
+       JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
+
+private:
+    const ReadOnlyCompileOptions& options_;
+    JSContext* cx;
+    LifoAlloc& alloc;
+
+    ParseNodeAllocator nodeAlloc;
+    ParseNodeDeleter nodeFree;
+
+    ThisBinding thisBinding_;
+    const char* fileName;
+
+    // Needs access to AutoGCRooter.
+    friend void TraceBinParser(JSTracer* trc, AutoGCRooter* parser);
+
+protected:
+
+    virtual void lineNumAndColumnIndex(size_t offset, uint32_t* line, uint32_t* column) const override {
+        *line = 0;
+        *column = offset;
+    }
+    virtual size_t offset() const override {
+        return 0; // FIXME: Find a better definition of offset()
+    }
+    virtual bool hasTokenizationStarted() const override {
+        return true; // FIXME: Improve this.
+    }
+    virtual void reportErrorNoOffset(unsigned errorNumber, ...) override;
+    virtual const char* getFilename() const override {
+        return this->options_.filename();
+    }
+
+    // The current ParseContext, holding directives, etc.
+    ParseContext* parseContext;
+    UsedNameTracker& usedNames;
+    friend class BinParseContext;
+};
+
+class BinParseContext: public ParseContext
+{
+public:
+    BinParseContext(JSContext* cx, BinASTParser* parser, SharedContext* sc, Directives* newDirectives)
+        : ParseContext(cx, parser->parseContext, sc, *parser,
+            parser->usedNames, newDirectives, /*isFull*/ true)
+    { }
+};
+
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_BinSource_h
\ No newline at end of file
--- a/js/src/frontend/ParseContext.h
+++ b/js/src/frontend/ParseContext.h
@@ -1,12 +1,16 @@
 #ifndef frontend_ParseContext_h
 #define frontend_ParseContext_h
 
+#include "ds/Nestable.h"
+
+#include "frontend/BytecodeCompiler.h"
 #include "frontend/ErrorReporter.h"
+#include "frontend/SharedContext.h"
 
 namespace js {
 
 namespace frontend {
 
 class ParserBase;
 
 // A data structure for tracking used names per parsing session in order to
@@ -391,16 +395,17 @@ class ParseContext : public Nestable<Par
 
         inline BindingIter bindings(ParseContext* pc);
     };
 
     class VarScope : public Scope
     {
       public:
         explicit inline VarScope(ParserBase* parser);
+        explicit inline VarScope(JSContext* cx, ParseContext* pc, UsedNameTracker& usedNames);
     };
 
   private:
     // Trace logging of parsing time.
     AutoFrontendTraceLog traceLog_;
 
     // Context shared between parsing and bytecode generation.
     SharedContext* sc_;
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -315,16 +315,23 @@ ParseContext::Scope::Scope(JSContext* cx
 
 inline
 ParseContext::VarScope::VarScope(ParserBase* parser)
   : Scope(parser)
 {
     useAsVarScope(parser->pc);
 }
 
+inline
+ParseContext::VarScope::VarScope(JSContext* cx, ParseContext* pc, UsedNameTracker& usedNames)
+  : Scope(cx, pc, usedNames)
+{
+    useAsVarScope(pc);
+}
+
 template <class ParseHandler, typename CharT>
 class Parser final : public ParserBase, private JS::AutoGCRooter
 {
   private:
     using Node = typename ParseHandler::Node;
 
     /*
      * A class for temporarily stashing errors while parsing continues.
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -10,22 +10,24 @@
 #include "jsatom.h"
 #include "jsopcode.h"
 #include "jspubtd.h"
 #include "jsscript.h"
 #include "jstypes.h"
 
 #include "builtin/ModuleObject.h"
 #include "ds/InlineTable.h"
+#include "frontend/ParseNode.h"
 #include "frontend/TokenStream.h"
 #include "vm/EnvironmentObject.h"
 
 namespace js {
 namespace frontend {
 
+class ParseContext;
 class ParseNode;
 
 enum class StatementKind : uint8_t
 {
     Label,
     Block,
     If,
     Switch,
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -641,20 +641,23 @@ if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_SIMD'] = True
 
 # In-flight WebAssembly atomic operations, sign extension operations,
 # and shared memory objects proposal:
 # https://github.com/WebAssembly/threads
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_WASM_THREAD_OPS'] = True
 
-# Some parts of BinAST are designed only to test evolutions of the
-# specification:
 if CONFIG['NIGHTLY_BUILD']:
+    # Some parts of BinAST are designed only to test evolutions of the
+    # specification:
     UNIFIED_SOURCES += ['frontend/BinTokenReaderTester.cpp']
+    # The rest of BinAST should eventually move to release.
+    UNIFIED_SOURCES += ['frontend/BinSource.cpp']
+
 
 if CONFIG['MOZ_DEBUG'] or CONFIG['NIGHTLY_BUILD']:
     DEFINES['JS_CACHEIR_SPEW'] = True
 
 # Also set in shell/moz.build
 DEFINES['ENABLE_SHARED_ARRAY_BUFFER'] = True
 
 DEFINES['EXPORT_JS_API'] = True