Bug 1377007 - Implementing basic binjs-ref parser in SpiderMonkey;r?jorendorff,arai draft
authorDavid Teller <dteller@mozilla.com>
Mon, 11 Sep 2017 16:54:48 +0200
changeset 662392 af9317e628a83f91b79435bda551b96ef8a70a3d
parent 662391 178993d3350337b89f33069b351dfa559d352968
child 730851 ce85e5a23d411dd21322d6c1c527d67a68131f7b
push id79066
push userdteller@mozilla.com
push dateMon, 11 Sep 2017 16:33:21 +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/FullParseHandler.h
js/src/frontend/ParseContext.h
js/src/frontend/ParseNode.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,2695 @@
+#include <mozilla/ArrayUtils.h>
+#include <mozilla/Casting.h>
+#include <mozilla/Maybe.h>
+#include <mozilla/Move.h>
+#include <mozilla/PodOperations.h>
+#include <mozilla/Vector.h>
+
+#include <frontend/BinSource.h>
+#include <frontend/FullParseHandler.h>
+#include <frontend/Parser.h>
+#include <frontend/ParseNode.h>
+#include <frontend/BinTokenReaderTester.h>
+
+#include <vm/RegExpObject.h>
+
+using namespace mozilla;
+using NameBag = GCHashSet<JSString*>;
+using Names = GCVector<JSString*, 8>;
+using UsedNamePtr = js::frontend::UsedNameTracker::UsedNameMap::Ptr;
+
+namespace js {
+namespace frontend {
+
+using UniqueNode = mozilla::UniquePtr<ParseNode, ParseNodeDeleter>;
+using BinFields = BinTokenReaderTester::BinFields;
+using AutoList = BinTokenReaderTester::AutoList;
+using AutoTaggedTuple = BinTokenReaderTester::AutoTaggedTuple;
+using AutoTuple = BinTokenReaderTester::AutoTuple;
+using Chars = BinTokenReaderTester::Chars;
+
+namespace {
+
+    // Compare a bunch of chars (as returned by the tokenizer) with
+    // a string literal (and ONLY a string literal).
+    template<size_t N>
+    bool operator==(const Chars& left, const char (&right)[N]) {
+        return BinTokenReaderTester::equals(left, right);
+    }
+}
+
+// Copied from Parser.cpp
+
+template <typename Scope>
+static typename Scope::Data*
+NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc, uint32_t numBindings)
+{
+    size_t allocSize = Scope::sizeOfData(numBindings);
+    typename Scope::Data* bindings = static_cast<typename Scope::Data*>(alloc.alloc(allocSize));
+    if (!bindings) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
+    PodZero(bindings);
+    return bindings;
+}
+
+Maybe<UniqueNode>
+BinASTParser::parse(const Vector<char>& data) {
+    return this->parse(data.begin(), data.length());
+}
+
+Maybe<UniqueNode>
+BinASTParser::parse(const char* start, const size_t length) {
+    tokenizer.emplace(this->cx, start, length);
+
+    Directives directives(options().strictOption);
+    GlobalSharedContext globalsc(this->cx, ScopeKind::Global,
+                                 directives, options().extraWarningsOption);
+    BinParseContext globalpc(this->cx, this, &globalsc, /* newDirectives = */ nullptr);
+    if (!globalpc.init())
+        return Nothing();
+
+    ParseContext::VarScope varScope(this->cx, &globalpc, usedNames);
+    if (!varScope.init(&globalpc))
+        return Nothing();
+
+    UniqueNode result(nullptr, this->nodeFree);
+    if (!this->parseProgram(result))
+        return Nothing();
+
+    if (!tokenizer->checkStatus())
+        return Nothing();
+
+    return Move(Some(Move(result)));
+}
+
+bool
+BinASTParser::parseProgram(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("Program");
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind == BinKind::BINJS_NULL)
+        return true;
+
+    if (kind != BinKind::PROGRAM)
+        return this->raiseInvalidKind("Program", kind);
+
+    if (!this->parseBlockStatementAux(kind, fields, out))
+        return false;
+
+    return true;
+}
+
+bool
+BinASTParser::parseBlockStatement(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("BlockStatement");
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    UniqueNode block(nullptr, this->nodeFree);
+    switch (kind) {
+        case BinKind::BINJS_NULL:
+            return true;
+        case BinKind::BLOCK_STATEMENT:
+            if (!this->parseBlockStatementAux(kind, fields, block))
+                return false;
+            break;
+        default:
+            return this->raiseInvalidKind("BlockStatement", kind);
+    }
+
+    out = Move(block);
+    return true;
+}
+
+bool
+BinASTParser::parseScope(ScopeData& out)
+{
+    if (out.isSome())
+        return this->raiseAlreadyParsed("Scope");
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind == BinKind::BINJS_NULL)
+        return true;
+
+    if (kind != BinKind::BINJS_SCOPE)
+        return this->raiseInvalidKind("Scope", kind);
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::BINJS_HAS_DIRECT_EVAL:
+                if (!this->readBool(out.hasDirectEval))
+                    return false;
+                break;
+            case BinField::BINJS_LET_DECL_NAMES:
+                if (!this->parseStringList(&out.letNames))
+                    return false;
+                break;
+            case BinField::BINJS_CONST_DECL_NAMES:
+                if (!this->parseStringList(&out.constNames))
+                    return false;
+                break;
+            case BinField::BINJS_VAR_DECL_NAMES:
+                if (!this->parseStringList(&out.varNames))
+                    return false;
+                break;
+            case BinField::BINJS_CAPTURED_NAMES:
+                if (!this->parseStringSet(&out.capturedNames))
+                    return false;
+                break;
+            default:
+                return this->raiseInvalidField("Scope", field);
+        }
+    }
+
+   return true;
+}
+
+bool
+BinASTParser::parseBlockStatementAux(const BinKind name, const BinFields& fields, UniqueNode& out)
+{
+    UniqueNode body(nullptr, this->nodeFree);
+    UniqueNode directives(nullptr, this->nodeFree); // Ignored
+    ScopeData scope(this->cx);
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::BINJS_SCOPE:
+                if (!this->parseScope(scope))
+                    return false;
+                break;
+            case BinField::BODY:
+                if (!this->parseStatementList(body))
+                    return false;
+                break;
+            case BinField::DIRECTIVES:
+                if (!this->parseDirectiveList(directives))
+                    return false;
+                break;
+            default:
+                return this->raiseInvalidField("BlockStatement", field);
+        }
+    }
+
+    // Check if all fields have been parsed.
+    if (!body || !scope.isSome())
+        return this->raiseMissingField("BlockStatement");
+
+    if (scope.hasLexNames()) {
+        if (!this->promoteToLexicalScope(body))
+            return false;
+
+        if (!this->storeLexicalScope(body, Move(scope)))
+            return false;
+    }
+
+    out = Move(body);
+    return true;
+}
+
+bool
+BinASTParser::storeLexicalScope(UniqueNode& body, ScopeData&& scope) {
+    LexicalScope::Data* bindings = body->pn_u.scope.bindings;
+    bindings->length = 0;
+
+    BindingName* cursor = bindings->names;
+    for (auto& name: *scope.letNames.get()) {
+        JS::Rooted<JSAtom*> atom(cx, AtomizeString(this->cx, name));
+        if (!atom)
+            return this->raiseOOM();
+
+        bool isCaptured = scope.capturedNames->has(name);
+        BindingName binding(atom, isCaptured);
+        PodCopy(cursor, &binding, 1);
+        cursor++;
+        bindings->length++; // Augment progressively in case we need to return early because of an error.
+    }
+    bindings->constStart = bindings->length;
+    for (auto& name: *scope.constNames.get()) {
+        JS::Rooted<JSAtom*> atom(cx, AtomizeString(this->cx, name));
+        if (!atom)
+            return this->raiseOOM();
+
+        bool isCaptured = scope.capturedNames->has(name);
+        BindingName binding(atom, isCaptured);
+        PodCopy(cursor, &binding, 1);
+        cursor++;
+        bindings->length++;
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::promoteToLexicalScope(UniqueNode& node) {
+    if (node->isKind(PNK_LEXICALSCOPE))
+        return true;
+
+    js::UniquePtr<LexicalScope::Data> bindings(NewEmptyBindingData<LexicalScope>(this->cx, this->alloc, /*number*/ 0));
+    if (!bindings)
+        return this->raiseOOM();
+
+    bindings->constStart = 0;
+    UniqueNode result(factory.newLexicalScope(bindings.get(), node.get()), this->nodeFree);
+    if (!result)
+        return this->raiseOOM();
+
+    Unused << node.release();
+    Unused << bindings.release();
+
+    node = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseStringSet(MutableHandle<mozilla::Maybe<NameBag>> out) {
+    if (out.get()) {
+        return this->raiseAlreadyParsed("{String}");
+    }
+    uint32_t length;
+    AutoList guard(*tokenizer);
+
+    NameBag result(this->cx);
+    if (!result.init())
+        return this->raiseOOM();
+
+    if (!tokenizer->readList(length, guard))
+        return false;
+
+    for (uint32_t i = 0; i < length; ++i) {
+        RootedString string(this->cx);
+        if (!this->readString(&string))
+            return false;
+
+        if (!result.put(Move(string)))
+            return this->raiseOOM();
+    }
+
+    out.set(Move(Some(Move(result))));
+    return true;
+}
+
+bool
+BinASTParser::parseStringList(MutableHandle<Maybe<Names>> out) {
+    if (out.get())
+        return this->raiseAlreadyParsed("[String]");
+
+    uint32_t length;
+    AutoList guard(*tokenizer);
+
+    Names result(this->cx);
+
+    if (!tokenizer->readList(length, guard))
+        return false;
+
+    if (!result.reserve(length))
+        return this->raiseOOM();
+
+    for (uint32_t i = 0; i < length; ++i) {
+        RootedString string(this->cx);
+        if (!this->readString(&string))
+            return false;
+
+        MOZ_ALWAYS_TRUE(result.append(Move(string))); // Checked in the call to `reserve`.
+    }
+
+    out.set(Move(Some(Move(result))));
+    return true;
+}
+
+bool
+BinASTParser::parseStatementList(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("[Statement]");
+
+    uint32_t length;
+    AutoList guard(*tokenizer);
+
+    TokenPos pos;
+    tokenizer->latestTokenPos(pos);
+    UniqueNode result(factory.newStatementList(pos), this->nodeFree);
+    if (!result)
+        return this->raiseOOM();
+
+    if (!tokenizer->readList(length, guard))
+        return false;
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode statement(nullptr, this->nodeFree);
+        if (!this->parseStatement(statement))
+            return false;
+
+        result->append(statement.release()); // `result` knows how to deallocate `statement`.
+    }
+
+    result.swap(out);
+
+    return true;
+}
+
+bool
+BinASTParser::parseStatement(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("Statement");
+
+    const size_t start = tokenizer->offset();
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    switch (kind) {
+        case BinKind::BINJS_NULL:
+            return true;
+        case BinKind::EMPTY_STATEMENT: {
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newEmptyStatement(pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::BLOCK_STATEMENT: {
+            UniqueNode body(nullptr, this->nodeFree);
+            if (!this->parseBlockStatementAux(kind, fields, body))
+                return false;
+
+            if (body) {
+                if (!this->promoteToLexicalScope(body))
+                    return false;
+            }
+            out = Move(body);
+            break;
+        }
+        case BinKind::EXPRESSION_STATEMENT: {
+            UniqueNode body(nullptr, this->nodeFree);
+            if (!this->parseExpressionStatementAux(kind, fields, body))
+                return false;
+
+            out = Move(body);
+            break;
+        }
+        case BinKind::DEBUGGER_STATEMENT: {
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newDebuggerStatement(pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::WITH_STATEMENT: {
+            UniqueNode body(nullptr, this->nodeFree);
+            UniqueNode expr(nullptr, this->nodeFree);
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::BODY:
+                        if (!this->parseStatement(body))
+                            return false;
+                        break;
+                    case BinField::OBJECT:
+                        if (!this->parseExpression(expr))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("WithStatement", field);
+                }
+            }
+            if (!body || !expr)
+                return this->raiseMissingField("WithStatement");
+
+            UniqueNode result(factory.newWithStatement(start, expr.get(), body.get()), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            Unused << expr.release();
+            Unused << body.release();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::RETURN_STATEMENT: {
+            UniqueNode arg(nullptr, this->nodeFree);
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::ARGUMENT:
+                        if (!this->parseExpression(arg))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("ReturnStatement", field);
+                }
+            }
+
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newReturnStatement(arg.get(), pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            Unused << arg.release();
+            out = Move(result);
+            break;
+        }
+        case BinKind::LABELED_STATEMENT: {
+            UniqueNode label(nullptr, this->nodeFree);
+            UniqueNode body(nullptr, this->nodeFree);
+
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::LABEL:
+                        if (!this->parsePattern(label))
+                            return false;
+
+                        if (!label || !label->isKind(PNK_NAME))
+                            return this->raiseError("Label MUST be an identifier");
+
+                        break;
+                    case BinField::BODY:
+                        if (!this->parseStatement(body))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("LabeledStatement", field);
+                }
+            }
+
+            if (!label || !body)
+                return this->raiseMissingField("LabeledStatement");
+
+            UniqueNode result(factory.newLabeledStatement(label->name(), body.get(), start), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            Unused << body.release();
+            out = Move(result);
+            break;
+    }
+    case BinKind::BREAK_STATEMENT: MOZ_FALLTHROUGH;
+    case BinKind::CONTINUE_STATEMENT: {
+
+        UniqueNode label(nullptr, this->nodeFree);
+
+        for (auto field: fields) {
+            switch (field) {
+                case BinField::LABEL:
+                    if (!this->parsePattern(label))
+                        return false;
+
+                    if (label && !label->isKind(PNK_NAME))
+                        return this->raiseError("ContinueStatement - Label MUST be an identifier");
+
+                    break;
+                default:
+                    return this->raiseInvalidField("ContinueStatement", field);
+            }
+        }
+
+        if (kind == BinKind::BREAK_STATEMENT) {
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newBreakStatement(label ? label->name() : nullptr, pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+        } else {
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newContinueStatement(label ? label->name() : nullptr, pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+        }
+
+        break;
+
+    }
+    case BinKind::IF_STATEMENT: {
+
+        UniqueNode test(nullptr, this->nodeFree);
+        UniqueNode consequent(nullptr, this->nodeFree);
+        UniqueNode alternate(nullptr, this->nodeFree); // Optional
+
+        for (auto field: fields) {
+            switch (field) {
+                case BinField::TEST: {
+                    if (!this->parseExpression(test))
+                        return false;
+                    break;
+                }
+                case BinField::CONSEQUENT: {
+                    if (!this->parseStatement(consequent))
+                        return false;
+                    break;
+                }
+                case BinField::ALTERNATE: {
+                    if (!this->parseStatement(alternate))
+                        return false;
+                    break;
+                }
+                default:
+                    return this->raiseInvalidField("IfStatement", field);
+            }
+        }
+
+        if (!test || !consequent)
+            return this->raiseMissingField("IfStatement");
+
+        UniqueNode result(factory.newIfStatement(start, test.get(), consequent.get(), alternate.get()), this->nodeFree);
+        if (!result)
+            return this->raiseOOM();
+
+        Unused << test.release();
+        Unused << consequent.release();
+        Unused << alternate.release();
+
+        out = Move(result);
+        break;
+    }
+    case BinKind::SWITCH_STATEMENT: {
+
+        UniqueNode discriminant(nullptr, this->nodeFree);
+        UniqueNode cases(nullptr, this->nodeFree);
+
+        for (auto field: fields) {
+            switch (field) {
+                case BinField::DISCRIMINANT: {
+                    if (!this->parseExpression(discriminant))
+                        return false;
+                    break;
+                }
+                case BinField::CASES: {
+                    if (!this->parseSwitchCaseList(cases))
+                        return false;
+                    break;
+                }
+                default:
+                    return this->raiseInvalidField("SwitchStatement", field);
+            }
+        }
+
+        if (!discriminant || !cases)
+            return this->raiseMissingField("SwtichStatement");
+
+        UniqueNode result(factory.newSwitchStatement(start, discriminant.get(), cases.get()), this->nodeFree);
+        if (!result)
+            return this->raiseOOM();
+
+        Unused << discriminant.release();
+        Unused << cases.release();
+        out = Move(result);
+        break;
+    }
+
+    case BinKind::THROW_STATEMENT: {
+
+        UniqueNode arg(nullptr, this->nodeFree);
+        for (auto field: fields) {
+            if (field == BinField::ARGUMENT) {
+                if (!this->parseExpression(arg))
+                    return false;
+            } else {
+                return this->raiseInvalidField("ThrowStatement", field);
+            }
+        }
+
+        if (!arg)
+            return this->raiseMissingField("ThrowStatement");
+
+        TokenPos pos(start, tokenizer->offset());
+        UniqueNode result(factory.newThrowStatement(arg.get(), pos), this->nodeFree);
+        if (!result)
+            return this->raiseOOM();
+
+        Unused << arg.release();
+        out = Move(result);
+        break;
+    }
+
+    case BinKind::TRY_STATEMENT: {
+
+        UniqueNode block(nullptr, this->nodeFree);
+        UniqueNode handler(nullptr, this->nodeFree);
+        UniqueNode finalizer(nullptr, this->nodeFree);
+
+        for (auto field: fields) {
+            switch (field) {
+                case BinField::BLOCK:
+                    if (!this->parseBlockStatement(block))
+                        return false;
+                    break;
+                case BinField::HANDLER:
+                    if (!this->parseCatchClause(handler))
+                        return false;
+                    break;
+                case BinField::FINALIZER:
+                    if (!this->parseBlockStatement(finalizer))
+                        return false;
+                    break;
+                default:
+                    return this->raiseInvalidField("TryStatement", field);
+            }
+        }
+
+        if (!block || (!handler && !finalizer))
+            return this->raiseMissingField("TryStatement");
+
+        if (!this->promoteToLexicalScope(block))
+            return false;
+
+        if (finalizer) {
+            if (!this->promoteToLexicalScope(finalizer))
+                return false;
+        }
+
+        UniqueNode result(factory.newTryStatement(start, block.get(), handler.get(), finalizer.get()), this->nodeFree);
+        if (!result)
+            return this->raiseOOM();
+
+        Unused << block.release();
+        Unused << handler.release();
+        Unused << finalizer.release();
+
+        out = Move(result);
+        break;
+    }
+
+    case BinKind::WHILE_STATEMENT: MOZ_FALLTHROUGH;
+    case BinKind::DO_WHILE_STATEMENT: {
+
+        UniqueNode test(nullptr, this->nodeFree);
+        UniqueNode body(nullptr, this->nodeFree);
+
+        for (auto field: fields) {
+            switch (field) {
+                case BinField::TEST:
+                    if (!this->parseExpression(test))
+                        return false;
+                    break;
+                case BinField::BODY:
+                    if (!this->parseStatement(body))
+                        return false;
+                    break;
+                default:
+                    return this->raiseInvalidField("DoWhileStatement", field);
+            }
+        }
+
+        if (!test || !body)
+            return this->raiseMissingField("DoWhileStatement");
+
+        if (kind == BinKind::WHILE_STATEMENT) {
+            UniqueNode result(factory.newWhileStatement(start, test.get(), body.get()), this->nodeFree);
+
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+        } else {
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newDoWhileStatement(body.get(), test.get(), pos), this->nodeFree);
+
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+        }
+
+        Unused << test.release();
+        Unused << body.release();
+        break;
+    }
+
+    case BinKind::FOR_STATEMENT: {
+
+        UniqueNode init(nullptr, this->nodeFree); // Optional
+        UniqueNode test(nullptr, this->nodeFree); // Optional
+        UniqueNode update(nullptr, this->nodeFree); // Optional
+        ScopeData scope(this->cx); // Optional
+        UniqueNode body(nullptr, this->nodeFree); // Required
+
+        for (auto field: fields) {
+            switch (field) {
+                case BinField::INIT:
+                    if (!this->parseForHead(init))
+                        return false;
+                    break;
+                case BinField::TEST:
+                    if (!this->parseExpression(test))
+                        return false;
+                    break;
+                case BinField::UPDATE:
+                    if (!this->parseExpression(update))
+                        return false;
+                    break;
+                case BinField::BINJS_SCOPE:
+                    if (!this->parseScope(scope))
+                        return false;
+                    break;
+                case BinField::BODY:
+                    if (!this->parseStatement(body))
+                        return false;
+                    break;
+                default:
+                    return this->raiseInvalidField("ForStatement", field);
+            }
+        }
+
+        if (!body)
+            return this->raiseMissingField("ForStatement");
+
+        TokenPos pos(start, tokenizer->offset());
+        UniqueNode forHead(factory.newForHead(init.get(), test.get(), update.get(), pos), this->nodeFree);
+        if (!forHead)
+            return this->raiseOOM();
+
+        Unused << init.release();
+        Unused << update.release();
+        Unused << test.release();
+
+        UniqueNode result(factory.newForStatement(start, forHead.get(), body.get(), /*flags*/0),
+            this->nodeFree);
+
+        if (!result)
+            return this->raiseOOM();
+
+        Unused << forHead.release();
+        Unused << body.release();
+
+        out = Move(result);
+        break;
+    }
+
+    case BinKind::FOR_IN_STATEMENT: {
+
+        UniqueNode left(nullptr, this->nodeFree);
+        UniqueNode right(nullptr, this->nodeFree);
+        UniqueNode body(nullptr, this->nodeFree);
+        ScopeData scope(this->cx); // Optional
+
+        for (auto field: fields) {
+            switch (field) {
+                case BinField::LEFT:
+                    if (!this->parseForInHead(left))
+                        return false;
+                    break;
+                case BinField::RIGHT:
+                    if (!this->parseExpression(right))
+                        return false;
+                    break;
+                case BinField::BODY:
+                    if (!this->parseStatement(body))
+                        return false;
+                    break;
+                case BinField::BINJS_SCOPE:
+                    if (!this->parseScope(scope))
+                        return false;
+                    break;
+                default:
+                    return this->raiseInvalidField("ForInStatement", field);
+            }
+        }
+
+        if (!left || !right || !body)
+            return this->raiseMissingField("ForInStatement");
+
+        if (!this->promoteToLexicalScope(body))
+            return false;
+
+        TokenPos pos(start, tokenizer->offset());
+        UniqueNode forHead(factory.newForInOrOfHead(PNK_FORIN, left.get(), right.get(), pos), this->nodeFree);
+        if (!forHead)
+            return this->raiseOOM();
+
+        Unused << left.release();
+        Unused << right.release();
+
+        UniqueNode result(factory.newForStatement(start, forHead.get(), body.get(), /*flags*/JSITER_ENUMERATE),
+            this->nodeFree);
+        if (!result)
+            return this->raiseOOM();
+
+        Unused << forHead.release();
+        Unused << body.release();
+
+        out = Move(result);
+        break;
+    }
+
+    case BinKind::FUNCTION_DECLARATION: {
+        UniqueNode result(nullptr, this->nodeFree);
+        if (!this->parseFunctionAux(kind, fields, result))
+            return false;
+
+        out = Move(result);
+        break;
+
+    }
+    case BinKind::VARIABLE_DECLARATION:
+        if (!this->parseVariableDeclarationAux(kind, fields, out))
+            return false;
+        break;
+    default:
+        return this->raiseInvalidKind("Statement", kind);
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseForHead(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("ForHead");
+
+    // This can be either a VarDecl or an Expression.
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+    BinKind kind;
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind == BinKind::BINJS_NULL)
+        return true;
+    else if (kind == BinKind::VARIABLE_DECLARATION)
+        return this->parseVariableDeclarationAux(kind, fields, out);
+    else /* Parse as expression */
+        return this->parseExpressionAux(kind, fields, out);
+}
+
+bool
+BinASTParser::parseForInHead(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("ForInHead");
+
+    // This can be either a VarDecl or a Pattern.
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+    BinKind kind;
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind == BinKind::BINJS_NULL)
+        return true;
+    else if (kind == BinKind::VARIABLE_DECLARATION)
+        return this->parseVariableDeclarationAux(kind, fields, out);
+    else /* Parse as pattern */
+        return this->parsePatternAux(kind, fields, out);
+}
+
+bool
+BinASTParser::parseFunctionAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    MOZ_ASSERT(kind == BinKind::FUNCTION_DECLARATION || kind == BinKind::FUNCTION_EXPRESSION || kind == BinKind::OBJECT_METHOD);
+    if (out)
+        return this->raiseAlreadyParsed("Function");
+
+    const size_t start = tokenizer->offset();
+
+    UniqueNode id(nullptr, this->nodeFree);
+    UniqueNode params(nullptr, this->nodeFree);
+    UniqueNode body(nullptr, this->nodeFree);
+    ScopeData scope(this->cx);
+
+    UniqueNode key(nullptr, this->nodeFree); // Methods only
+    Maybe<bool> computed;     // Methods only
+    Maybe<Chars> method_kind; // Methods only
+
+    // Allocate the function before walking down the tree.
+    RootedFunction fun(this->cx,
+        NewFunctionWithProto(this->cx,
+            /*native*/nullptr,
+            /*nargs ?*/0,
+            /*flags */ JSFunction::INTERPRETED_NORMAL,
+            /*enclosing env*/nullptr,
+            /*name*/ nullptr, // Will be known later
+            /*proto*/ nullptr,
+            /*alloc*/gc::AllocKind::FUNCTION,
+            TenuredObject
+    ));
+    if (!fun)
+        return this->raiseOOM();
+
+    FunctionBox* funbox = alloc.new_<FunctionBox>(this->cx,
+        this->alloc,
+        this->traceListHead,
+        fun,
+        /*toStringStart*/0,
+        /*Directives*/Directives(this->parseContext),
+        /*extraWarning*/false,
+        GeneratorKind::NotGenerator,
+        FunctionAsyncKind::SyncFunction
+    );
+    if (!funbox)
+        return this->raiseOOM();
+
+    this->traceListHead = funbox;
+
+    FunctionSyntaxKind syntax;
+    switch (kind) {
+        case BinKind::FUNCTION_DECLARATION:
+            syntax = Statement;
+            break;
+        case BinKind::FUNCTION_EXPRESSION:
+            syntax = Expression;
+            break;
+        case BinKind::OBJECT_METHOD:
+            // FIXME: At this stage of parsing, we do not know about `get`/`set`.
+            syntax = Method;
+            break;
+        default:
+            MOZ_CRASH("Invalid FunctionSyntaxKind");
+    }
+    funbox->initWithEnclosingParseContext(parseContext, syntax);
+
+
+    HandlePropertyName dotThis = this->cx->names().dotThis;
+    const bool declareThis = this->hasUsedName(dotThis) || funbox->bindingsAccessedDynamically() || funbox->isDerivedClassConstructor();
+
+    if (declareThis) {
+        ParseContext::Scope& funScope = this->parseContext->functionScope();
+        ParseContext::Scope::AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis);
+        MOZ_ASSERT(!p);
+        if (!funScope.addDeclaredName(this->parseContext, p, dotThis, DeclarationKind::Var,
+                                      DeclaredNameInfo::npos))
+            return false;
+
+        funbox->setHasThisBinding();
+    }
+
+    // Push a new ParseContext.
+    BinParseContext funpc(this->cx, this, funbox, /*newDirectives*/ nullptr);
+    if (!funpc.init())
+        return false;
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::ID:
+                if (!this->parsePattern(id))
+                    return false;
+
+                if (id && !id->isKind(PNK_NAME))
+                    return this->raiseError("Function id MUST be an identifier");
+
+                break;
+            case BinField::PARAMS:
+                if (!this->parsePatternList(params))
+                    return false;
+                break;
+            case BinField::BODY:
+                if (!this->parseStatement(body))
+                    return false;
+                break;
+            case BinField::BINJS_SCOPE:
+                if (!this->parseScope(scope))
+                    return false;
+                break;
+            case BinField::KEY:
+                if (kind != BinKind::OBJECT_METHOD)
+                    return this->raiseInvalidField("Functions other than ObjectMethod", field);
+
+                if (!this->parseExpression(key))
+                    return false;
+
+                break;
+            case BinField::COMPUTED:
+                if (kind != BinKind::OBJECT_METHOD)
+                    return this->raiseInvalidField("Functions other than ObjectMethod", field);
+
+                if (!this->readBool(computed))
+                    return false;
+
+                break;
+            case BinField::KIND:
+                if (kind != BinKind::OBJECT_METHOD)
+                    return this->raiseInvalidField("Functions other than ObjectMethod", field);
+
+                if (!this->readString(method_kind))
+                    return false;
+
+                break;
+            default:
+                return this->raiseInvalidField("Function", field);
+        }
+    }
+
+    if (!params || !body || !scope.isSome())
+        return this->raiseMissingField("Function");
+
+    if (kind == BinKind::FUNCTION_DECLARATION && !id) {
+        // The name is compulsory only for function declarations.
+        return this->raiseMissingField("FunctionDeclaration");
+    }
+
+    if (kind == BinKind::OBJECT_METHOD) {
+        if (!key || !computed || !method_kind)
+            return this->raiseMissingField("ObjectMethod");
+    }
+
+    if (id)
+        fun->initAtom(id->pn_atom);
+
+    MOZ_ASSERT(params->isArity(PN_LIST));
+    params->setOp(JSOP_NOP);
+
+    if (!(body->isKind(PNK_LEXICALSCOPE) && body->pn_u.scope.body->isKind(PNK_STATEMENTLIST))) {
+        // Promote to lexical scope + statement list.
+        if (!body->isKind(PNK_STATEMENTLIST)) {
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode list(factory.newStatementList(pos), this->nodeFree);
+            if (!list)
+                return this->raiseOOM();
+
+            list->initList(body.release());
+            body = Move(list);
+        }
+
+        // Promote to lexical scope.
+        if (!this->promoteToLexicalScope(body))
+            return false;
+    }
+    MOZ_ASSERT(body->getKind() == PNK_LEXICALSCOPE);
+    params->append(body.release());
+
+    TokenPos pos(start, tokenizer->offset());
+    UniqueNode function( kind == BinKind::FUNCTION_DECLARATION
+            ? factory.newFunctionStatement(pos)
+            : factory.newFunctionExpression(pos), this->nodeFree);
+    if (!function)
+        return this->raiseOOM();
+
+    factory.setFunctionBox(function.get(), funbox);
+    factory.setFunctionFormalParametersAndBody(function.get(), params.release());
+
+    UniqueNode result(nullptr, this->nodeFree);
+    if (kind == BinKind::OBJECT_METHOD) {
+        JSOp op;
+        if (*method_kind == "init")
+            op = JSOP_INITPROP;
+        else if (*method_kind == "get")
+            op = JSOP_INITPROP_GETTER;
+        else if (*method_kind == "set")
+            op = JSOP_INITPROP_SETTER;
+        else
+            return this->raiseInvalidEnum("ObjectMethod", *method_kind);
+
+        result = UniqueNode(factory.newBinary(PNK_COLON, key.get(), function.get(), op), this->nodeFree);
+        if (!result)
+            return this->raiseOOM();
+
+        Unused << key.release();
+        Unused << function.release();
+    } else {
+        result = Move(function);
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseVariableDeclarationAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("VariableDeclaration");
+
+    const size_t start = tokenizer->offset();
+
+    if (kind == BinKind::BINJS_NULL)
+        return true;
+    else if (kind != BinKind::VARIABLE_DECLARATION)
+        return this->raiseInvalidKind("VariableDeclaration", kind);
+
+    ParseNodeKind pnk = PNK_LIMIT;
+    JSOp op = JSOP_NOP;
+    UniqueNode result(nullptr, this->nodeFree);
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::KIND: {
+                Maybe<Chars> kindName;
+                if (!this->readString(kindName))
+                    return false;
+
+                if (kindName.isNothing())
+                    return this->raiseMissingField("VariableDeclaration");
+
+                if (*kindName == "let") {
+                    pnk = PNK_LET;
+                    op = JSOP_DEFLET;
+                } else if (*kindName == "var") {
+                    pnk = PNK_VAR;
+                    op = JSOP_DEFVAR;
+                } else if (*kindName == "const") {
+                    pnk = PNK_CONST;
+                    op = JSOP_DEFCONST;
+                } else {
+                    return this->raiseInvalidEnum("VariableDeclaration", *kindName);
+                }
+                break;
+            }
+            case BinField::DECLARATIONS: {
+                if (result) {
+                    // Already parsed.
+                    return this->raiseAlreadyParsed("VariableDeclaration");
+                }
+
+                uint32_t length;
+                AutoList guard(*tokenizer);
+
+                if (!tokenizer->readList(length, guard))
+                    return false;
+
+                if (length == 0)
+                    return this->raiseEmpty("VariableDeclaration");
+
+                TokenPos pos(start, tokenizer->offset());
+                UniqueNode root(factory.newDeclarationList(PNK_CONST /*Placeholder*/, pos, JSOP_NOP/*Placeholder*/), this->nodeFree);
+                if (!root)
+                    return this->raiseOOM();
+
+                UniqueNode first(nullptr, this->nodeFree);
+                if (!this->parseVariableDeclarator(first))
+                    return this->raiseOOM();
+
+                root->initList(first.release());
+
+                for (uint32_t i = 1; i < length; ++i) {
+                    UniqueNode current(nullptr, this->nodeFree);
+                    if (!this->parseVariableDeclarator(current))
+                        return false;
+
+                    if (!current)
+                        return this->raiseMissingField("VariableDeclaration");
+
+                    root->append(current.release());
+                }
+
+                result = Move(root);
+                break;
+            }
+            default:
+                return this->raiseInvalidField("VariableDeclaration", field);
+        }
+    }
+
+    if (!result || pnk == PNK_LIMIT)
+        return this->raiseMissingField("VariableDeclaration");
+
+    result->setKind(pnk);
+    result->setOp(op);
+
+    MOZ_ASSERT(!result->isKind(PNK_NOP));
+    out = Move(result);
+
+    return true;
+}
+
+
+bool
+BinASTParser::parseExpression(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("Expression");
+
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+    BinKind kind;
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    return this->parseExpressionAux(kind, fields, out);
+}
+
+bool
+BinASTParser::parseExpressionStatementAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    MOZ_ASSERT(kind == BinKind::EXPRESSION_STATEMENT);
+    if (out)
+        return this->raiseAlreadyParsed("ExpressionStatement");
+
+    UniqueNode result(nullptr, this->nodeFree);
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::EXPRESSION:
+                if (!this->parseExpression(result))
+                    return false;
+
+                break;
+            default:
+                return this->raiseInvalidField("ExpressionStatement", field);
+        }
+    }
+
+    if (!result)
+        return this->raiseMissingField("ExpressionStatement");
+
+    out = UniqueNode(factory.newExprStatement(result.get(), tokenizer->offset()), this->nodeFree);
+
+    if (!out)
+        return this->raiseOOM();
+
+    Unused << result.release(); // Now part of `out`.
+
+    return true;
+}
+
+bool
+BinASTParser::parseVariableDeclarator(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("VariableDeclarator");
+
+    const size_t start = tokenizer->offset();
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind != BinKind::VARIABLE_DECLARATOR)
+        return this->raiseInvalidKind("VariableDeclarator", kind);
+
+    UniqueNode id(nullptr, this->nodeFree);
+    UniqueNode init(nullptr, this->nodeFree); // Optional.
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::ID:
+                if (!this->parsePattern(id))
+                    return false;
+
+                break;
+            case BinField::INIT:
+                if (!this->parseExpression(init))
+                    return false;
+
+                break;
+            default:
+                return this->raiseInvalidField("VariableDeclarator", field);
+        }
+    }
+
+    if (!id)
+        return this->raiseMissingField("VariableDeclarator");
+
+    UniqueNode result(nullptr, this->nodeFree);
+
+    // FIXME: Documentation in ParseNode is clearly obsolete.
+    if (id->isKind(PNK_NAME)) {
+        // `var foo [= bar]``
+        TokenPos pos(start, tokenizer->offset());
+        result = UniqueNode(factory.newName(id->pn_atom->asPropertyName(), pos, cx), this->nodeFree);
+        if (!result)
+            return this->raiseOOM();
+
+        if (init)
+            result->pn_expr = init.release();
+
+    } else {
+        // `var pattern = bar`
+        if (!init) {
+            // Here, `init` is required.
+            return this->raiseMissingField("VariableDeclarator");
+        }
+
+        result = UniqueNode(factory.newBinary(PNK_ASSIGN, id.get(), init.get()), this->nodeFree);
+        if (!result)
+            return this->raiseOOM();
+
+        Unused << id.release();
+        Unused << init.release();
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseExpressionOrElisionList(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("[Expression|null]");
+
+    const size_t start = tokenizer->offset();
+
+    uint32_t length;
+    AutoList guard(*tokenizer);
+
+    if (!tokenizer->readList(length, guard))
+        return false;
+
+    UniqueNode result(factory.newArrayLiteral(start), this->nodeFree);
+    if (!result)
+        return this->raiseOOM();
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode expr(nullptr, this->nodeFree);
+        if (!this->parseExpression(expr))
+            return false;
+
+        if (expr) {
+            factory.addArrayElement(result.get(), expr.release());
+        } else {
+            TokenPos pos(start, tokenizer->offset());
+            if (!factory.addElision(result.get(), pos))
+                return this->raiseOOM();
+        }
+    }
+
+    out = Move(result);
+    return true;
+}
+
+
+bool
+BinASTParser::parseSwitchCaseList(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("[SwitchCase]");
+
+    uint32_t length;
+    AutoList guard(*tokenizer);
+
+    if (!tokenizer->readList(length, guard))
+        return false;
+
+    TokenPos pos;
+    tokenizer->latestTokenPos(pos);
+    UniqueNode result(factory.newStatementList(pos), this->nodeFree);
+    if (!result)
+        return this->raiseOOM();
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode case_(nullptr, this->nodeFree);
+        if (!this->parseSwitchCase(case_))
+            return false;
+
+        if (!case_)
+            return this->raiseEmpty("[SwitchCase]");
+
+        factory.addCaseStatementToList(result.get(), case_.release());
+    }
+
+    if (!this->promoteToLexicalScope(result))
+        return false;
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseExpressionAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    const size_t start = tokenizer->offset();
+
+    switch (kind) {
+        case BinKind::BINJS_NULL:
+            return true;
+        case BinKind::IDENTIFIER: {
+            Rooted<PropertyName*> id(this->cx);
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::NAME:
+                        if (!this->readString(&id))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("Identifier", field);
+                }
+            }
+
+            if (!id)
+                return this->raiseMissingField("Identifier");
+
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newName(id, pos, cx), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::BOOLEAN_LITERAL: {
+            Maybe<bool> value;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::VALUE:
+                        if (!this->readBool(value))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("BooleanLiteral", field);
+                }
+            }
+
+            if (!value)
+                return this->raiseMissingField("BooleanLiteral");
+
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newBooleanLiteral(*value, pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::DIRECTIVE_LITERAL:
+            // A directive is not truly a literal, so this doesn't make sense
+            // here.
+            return this->raiseError("DirectiveLiteral (not truly a literal)");
+        case BinKind::NULL_LITERAL: {
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newNullLiteral(pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::NUMERIC_LITERAL: {
+            Maybe<double> value;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::VALUE:
+                        if (!this->readNumber(value))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("NumericLiteral", field);
+                }
+            }
+
+            if (!value)
+                return this->raiseMissingField("NumericLiteral");
+
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newNumber(*value, DecimalPoint::HasDecimal, pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::REGEXP_LITERAL: {
+            RootedAtom pattern(this->cx);
+            Maybe<Chars> flags;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::PATTERN:
+                        if (!this->readString(&pattern))
+                            return false;
+                        break;
+                    case BinField::FLAGS:
+                        if (!this->readString(flags))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("RegExpLiteral", field);
+                }
+            }
+
+            if (!pattern || !flags)
+                return this->raiseMissingField("RegExpLiteral");
+
+            RegExpFlag reflags = NoFlags;
+            for (char c: *flags) {
+                if (c == 'g' && !(reflags & GlobalFlag))
+                    reflags = RegExpFlag(reflags | GlobalFlag);
+                else if (c == 'i' && !(reflags & IgnoreCaseFlag))
+                    reflags = RegExpFlag(reflags | IgnoreCaseFlag);
+                else if (c == 'm' && !(reflags & MultilineFlag))
+                    reflags = RegExpFlag(reflags | MultilineFlag);
+                else if (c == 'y' && !(reflags & StickyFlag))
+                    reflags = RegExpFlag(reflags | StickyFlag);
+                else if (c == 'u' && !(reflags & UnicodeFlag))
+                    reflags = RegExpFlag(reflags | UnicodeFlag);
+                else
+                    return this->raiseInvalidEnum("RegExpLiteral", *flags);
+            }
+
+
+            Rooted<RegExpObject*> reobj(this->cx);
+            reobj = RegExpObject::create(this->cx,
+                pattern,
+                reflags,
+                /* options*/ nullptr,
+                /* tokenStream */ nullptr,
+                this->alloc,
+                TenuredObject);
+
+            if (!reobj)
+                return this->raiseOOM();
+
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newRegExp(reobj, pos, *this), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::STRING_LITERAL: {
+            RootedAtom value(this->cx);
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::VALUE:
+                        if (!this->readString(&value))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("StringLiteral", field);
+                }
+            }
+
+            if (!value)
+                return this->raiseMissingField("StringLiteral");
+
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newStringLiteral(value, pos), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::THIS_EXPRESSION: {
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode thisName(nullptr, this->nodeFree);
+            if (this->parseContext->sc()->thisBinding() == ThisBinding::Function) {
+                thisName = UniqueNode(factory.newName(this->cx->names().dotThis, pos, cx), this->nodeFree);
+                if (!thisName)
+                    return this->raiseOOM();
+            }
+
+            UniqueNode result(factory.newThisLiteral(pos, thisName.get()), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            Unused << thisName.release();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::ARRAY_EXPRESSION: {
+            UniqueNode result(nullptr, this->nodeFree);
+            if (!this->parseExpressionOrElisionList(result))
+                return false;
+
+            result->setKind(PNK_ARRAY);
+            result->setOp(JSOP_NEWINIT);
+            out = Move(result);
+            break;
+        }
+        case BinKind::OBJECT_EXPRESSION: {
+            UniqueNode result(nullptr, this->nodeFree);
+            if (!this->parseObjectMemberList(result))
+                return false;
+
+            bool first = true;
+            for (ParseNode* iter = result.get(); iter != nullptr; iter = iter->pn_next) {
+                if (first) {
+                    first = false;
+                    MOZ_ASSERT(iter->isKind(PNK_COLON));
+                    MOZ_ASSERT(iter->pn_left != nullptr);
+                    MOZ_ASSERT(iter->pn_right != nullptr);
+                }
+            }
+
+            result->setKind(PNK_OBJECT);
+            result->setOp(JSOP_NEWINIT);
+            out = Move(result);
+            break;
+        }
+        case BinKind::FUNCTION_EXPRESSION: {
+            UniqueNode result(nullptr, this->nodeFree);
+            if (!this->parseFunctionAux(kind, fields, result))
+                return false;
+
+            result->setOp(JSOP_LAMBDA);
+            out = Move(result);
+            break;
+        }
+        case BinKind::UNARY_EXPRESSION: MOZ_FALLTHROUGH;
+        case BinKind::UPDATE_EXPRESSION: {
+
+            UniqueNode expr(nullptr, this->nodeFree);
+            Maybe<Chars> operation;
+            Maybe<bool> prefix; // FIXME: Ignored for unary_expression?
+
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::OPERATOR:
+                        if (!this->readString(operation))
+                            return false;
+                        break;
+                    case BinField::PREFIX:
+                        if (!this->readBool(prefix))
+                            return false;
+                        break;
+                    case BinField::ARGUMENT:
+                        if (!this->parseExpression(expr))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("UpdateExpression", field);
+                }
+            }
+
+            if (!expr || operation.isNothing() || prefix.isNothing())
+                return this->raiseMissingField("UpdateExpression");
+
+            ParseNodeKind pnk = PNK_LIMIT;
+            JSOp op = JSOP_NOP;
+            if (kind == BinKind::UNARY_EXPRESSION) {
+                if (*operation == "-") {
+                    pnk = PNK_NEG;
+                    op = JSOP_NEG;
+                } else if (*operation == "+") {
+                    pnk = PNK_POS;
+                    op = JSOP_POS;
+                } else if (*operation == "!") {
+                    pnk = PNK_NOT;
+                    op = JSOP_NOT;
+                } else if (*operation == "~") {
+                    pnk = PNK_BITNOT;
+                    op = JSOP_BITNOT;
+                } else if (*operation == "typeof") {
+                    if (expr->isKind(PNK_NAME))
+                        pnk = PNK_TYPEOFNAME;
+                    else
+                        pnk = PNK_TYPEOFEXPR;
+                } else if (*operation == "void") {
+                    pnk = PNK_VOID;
+                    op = JSOP_VOID;
+                } else if (*operation == "delete") {
+                    switch (expr->getKind()) {
+                        case PNK_NAME:
+                            expr->setOp(JSOP_DELNAME);
+                            pnk = PNK_DELETENAME;
+                            break;
+                        case PNK_DOT:
+                            pnk = PNK_DELETEPROP;
+                            break;
+                        case PNK_ELEM:
+                            pnk = PNK_DELETEELEM;
+                            break;
+                        default:
+                            pnk = PNK_DELETEEXPR;
+                    }
+                } else {
+                    return this->raiseInvalidEnum("UnaryOperator", *operation);
+                }
+            } else if (kind == BinKind::UPDATE_EXPRESSION) {
+                if (*operation == "++") {
+                    if (*prefix)
+                        pnk = PNK_PREINCREMENT;
+                    else
+                        pnk = PNK_POSTINCREMENT;
+                } else if (*operation == "--") {
+                    if (*prefix)
+                        pnk = PNK_PREDECREMENT;
+                    else
+                        pnk = PNK_POSTDECREMENT;
+                } else {
+                    return this->raiseInvalidEnum("UpdateOperator", *operation);
+                }
+            }
+
+            UniqueNode result(factory.newUnary(pnk, op, start, expr.get()), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            Unused << expr.release();
+            out = Move(result);
+            break;
+        }
+        case BinKind::BINARY_EXPRESSION: MOZ_FALLTHROUGH;
+        case BinKind::LOGICAL_EXPRESSION: {
+
+            UniqueNode left(nullptr, this->nodeFree);
+            UniqueNode right(nullptr, this->nodeFree);
+            Maybe<Chars> operation;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::LEFT:
+                        if (!this->parseExpression(left))
+                            return false;
+                        break;
+                    case BinField::RIGHT:
+                        if (!this->parseExpression(right))
+                            return false;
+                        break;
+                    case BinField::OPERATOR:
+                        if (!this->readString(operation))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("LogicalExpression | BinaryExpression", field);
+                }
+            }
+
+            if (!left || !right || operation.isNothing())
+                return this->raiseMissingField("LogicalExpression | BinaryExpression");
+
+            // FIXME: Instead of Chars, we should use atoms and comparison
+            // between atom ptr.
+            ParseNodeKind pnk = PNK_LIMIT;
+            JSOp op;
+            if (*operation == "==") {
+                pnk = PNK_EQ;
+                op = JSOP_EQ;
+            } else if (*operation == "!=") {
+                pnk = PNK_NE;
+                op = JSOP_NE;
+            } else if (*operation == "===") {
+                pnk = PNK_STRICTEQ;
+                op = JSOP_STRICTEQ;
+            } else if (*operation == "!==") {
+                pnk = PNK_STRICTNE;
+                op = JSOP_STRICTNE;
+            } else if (*operation == "<") {
+                pnk = PNK_LT;
+                op = JSOP_LT;
+            } else if (*operation == "<=") {
+                pnk = PNK_LE;
+                op = JSOP_LE;
+            } else if (*operation == ">") {
+                pnk = PNK_GT;
+                op = JSOP_GT;
+            } else if (*operation == ">=") {
+                pnk = PNK_GE;
+                op = JSOP_GE;
+            } else if (*operation == "<<") {
+                pnk = PNK_LSH;
+                op = JSOP_LSH;
+            } else if (*operation == ">>") {
+                pnk = PNK_RSH;
+                op = JSOP_RSH;
+            } else if (*operation == ">>>") {
+                pnk = PNK_URSH;
+                op = JSOP_URSH;
+            } else if (*operation == "+") {
+                pnk = PNK_ADD;
+                op = JSOP_ADD;
+            } else if (*operation == "-") {
+                pnk = PNK_SUB;
+                op = JSOP_SUB;
+            } else if (*operation == "*") {
+                pnk = PNK_STAR;
+                op = JSOP_MUL;
+            } else if (*operation == "/") {
+                pnk = PNK_DIV;
+                op = JSOP_DIV;
+            } else if (*operation == "%") {
+                pnk = PNK_MOD;
+                op = JSOP_MOD;
+            } else if (*operation == "|") {
+                pnk = PNK_BITOR;
+                op = JSOP_BITOR;
+            } else if (*operation == "^") {
+                pnk = PNK_BITXOR;
+                op = JSOP_BITXOR;
+            } else if (*operation == "&") {
+                pnk = PNK_BITAND;
+                op = JSOP_BITAND;
+            } else if (*operation == "in") {
+                pnk = PNK_IN;
+                op = JSOP_IN;
+            } else if (*operation == "instanceof") {
+                pnk = PNK_INSTANCEOF;
+                op = JSOP_INSTANCEOF;
+            } else if (*operation == "||") {
+                pnk = PNK_OR;
+                op = JSOP_OR;
+            } else if (*operation == "&&" ) {
+                pnk = PNK_AND;
+                op = JSOP_AND;
+            } else if (*operation == "**" ) {
+                pnk = PNK_POW;
+                op = JSOP_POW;
+            } else {
+                return this->raiseInvalidEnum("BinaryOperator | LogicalOperator", *operation);
+            }
+
+            if (left->isKind(pnk) && pnk != PNK_POW /*Not associative*/) {
+                // Regroup associative operations into lists.
+                left->append(right.release());
+                out = Move(left);
+            } else {
+                TokenPos pos(start, tokenizer->offset());
+                UniqueNode list(factory.newList(pnk, pos, op), this->nodeFree);
+                if (!list) {
+                    return this->raiseOOM();
+                }
+                list->makeEmpty();
+                list->append(left.release());
+                list->append(right.release());
+                out = Move(list);
+            }
+
+            break;
+        }
+        case BinKind::ASSIGNMENT_EXPRESSION: {
+            UniqueNode left(nullptr, this->nodeFree);
+            UniqueNode right(nullptr, this->nodeFree);
+            Maybe<Chars> operation;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::LEFT:
+                        if (!this->parseExpression(left))
+                            return false;
+                        break;
+                    case BinField::RIGHT:
+                        if (!this->parseExpression(right))
+                            return false;
+                        break;
+                    case BinField::OPERATOR:
+                        if (!this->readString(operation))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("AssignmentExpression", field);
+                }
+            }
+
+            if (!left || !right || operation.isNothing())
+                return this->raiseMissingField("AssignmentExpression");
+
+            // FIXME: Instead of Chars, we should use atoms and comparison
+            // between atom ptr.
+            // FIXME: We should probably turn associative operations into lists.
+            ParseNodeKind pnk = PNK_LIMIT;
+            JSOp op = JSOP_NOP;
+            if (*operation == "=") {
+                pnk = PNK_ASSIGN;
+            } else if (*operation == "+=") {
+                pnk = PNK_ADDASSIGN;
+                op = JSOP_ADD;
+            } else if (*operation == "-=") {
+                pnk = PNK_SUBASSIGN;
+                op = JSOP_SUB;
+            } else if (*operation == "*=") {
+                pnk = PNK_MULASSIGN;
+                op = JSOP_MUL;
+            } else if (*operation == "/=") {
+                pnk = PNK_DIVASSIGN;
+                op = JSOP_DIV;
+            } else if (*operation == "%=") {
+                pnk = PNK_MODASSIGN;
+                op = JSOP_MOD;
+            } else if (*operation == "<<=") {
+                pnk = PNK_LSHASSIGN;
+                op = JSOP_LSH;
+            } else if (*operation == ">>=") {
+                pnk = PNK_RSHASSIGN;
+                op = JSOP_RSH;
+            } else if (*operation == ">>>=") {
+                pnk = PNK_URSHASSIGN;
+                op = JSOP_URSH;
+            } else if (*operation == "|=") {
+                pnk = PNK_BITORASSIGN;
+                op = JSOP_BITOR;
+            } else if (*operation == "^=") {
+                pnk = PNK_BITXORASSIGN;
+                op = JSOP_BITXOR;
+            } else if (*operation == "&=") {
+                pnk = PNK_BITANDASSIGN;
+                op = JSOP_BITAND;
+            } else {
+                return this->raiseInvalidEnum("AssignmentOperator", *operation);
+            }
+
+            UniqueNode list(factory.newBinary(pnk, left.get(), right.get()), this->nodeFree);
+            if (!list)
+                return this->raiseOOM();
+
+            Unused << left.release();
+            Unused << right.release();
+
+            out = Move(list);
+            break;
+        }
+        case BinKind::MEMBER_EXPRESSION: {
+            UniqueNode result(nullptr, this->nodeFree);
+            if (!this->parseMemberExpressionAux(kind, fields, result))
+                return false;
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::CONDITIONAL_EXPRESSION: {
+            UniqueNode test(nullptr, this->nodeFree);
+            UniqueNode alternate(nullptr, this->nodeFree);
+            UniqueNode consequent(nullptr, this->nodeFree);
+
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::TEST:
+                        if (!this->parseExpression(test))
+                            return false;
+                        break;
+                    case BinField::ALTERNATE:
+                        if (!this->parseExpression(alternate))
+                            return false;
+                        break;
+                    case BinField::CONSEQUENT:
+                        if (!this->parseExpression(consequent))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("ConditionalExpression", field);
+                }
+            }
+
+            if (!test || !alternate || !consequent)
+                return this->raiseMissingField("ConditionalExpression");
+
+            UniqueNode result(factory.newConditional(test.get(), consequent.get(), alternate.get()), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            Unused << test.release();
+            Unused << alternate.release();
+            Unused << consequent.release();
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::CALL_EXPRESSION: MOZ_FALLTHROUGH;
+        case BinKind::NEW_EXPRESSION: {
+            UniqueNode callee(nullptr, this->nodeFree);
+            UniqueNode arguments(nullptr, this->nodeFree);
+
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::CALLEE:
+                        if (!this->parseExpression(callee))
+                            return false;
+                        break;
+                    case BinField::ARGUMENTS:
+                        if (!this->parseExpressionOrElisionList(arguments))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("NewExpression", field);
+                }
+            }
+
+            if (!callee || !arguments)
+                return this->raiseMissingField("NewExpression");
+
+            ParseNodeKind pnk =
+                kind == BinKind::CALL_EXPRESSION
+                ? PNK_CALL
+                : PNK_NEW;
+            arguments->setKind(pnk);
+            arguments->prepend(callee.release());
+
+            out = Move(arguments);
+            break;
+        }
+        case BinKind::SEQUENCE_EXPRESSION: {
+            UniqueNode sequence(nullptr, this->nodeFree);
+
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::EXPRESSIONS:
+                        if (!this->parseExpressionOrElisionList(sequence))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("SequenceExpression", field);
+                }
+            }
+
+            if (!sequence)
+                return this->raiseMissingField("SequenceExpression");
+
+            sequence->setKind(PNK_COMMA);
+            out = Move(sequence);
+            break;
+        }
+        default:
+            return this->raiseInvalidKind("Expression", kind);
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseMemberExpressionAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("MemberExpression");
+
+    UniqueNode object(nullptr, this->nodeFree);
+    UniqueNode property(nullptr, this->nodeFree);
+    Maybe<bool> isComputed;
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::OBJECT:
+                if (!this->parseExpression(object))
+                    return false;
+                break;
+            case BinField::PROPERTY:
+                if (!this->parseExpression(property))
+                    return false;
+                break;
+            case BinField::COMPUTED:
+                if (!this->readBool(isComputed))
+                    return false;
+                break;
+            default:
+                return this->raiseInvalidField("MemberExpression", field);
+        }
+    }
+
+    if (!object || !property || !isComputed)
+        return this->raiseMissingField("MemberExpression");
+
+    UniqueNode result(nullptr, this->nodeFree);
+    if (!*isComputed) {
+        if (!property->isKind(PNK_NAME))
+            return this->raiseError("MemberExpression (computed implies name)");
+
+        PropertyName* name = property->pn_atom->asPropertyName();
+        result = UniqueNode(factory.newPropertyAccess(object.get(), name, tokenizer->offset()), this->nodeFree);
+    } else {
+        result = UniqueNode(factory.newPropertyByValue(object.get(), property.get(), tokenizer->offset()), this->nodeFree);
+    }
+    if (!result)
+        return this->raiseOOM();
+
+    Unused << object.release();
+    Unused << property.release();
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseDirective(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("Directive");
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind != BinKind::DIRECTIVE)
+        return this->raiseInvalidKind("Directive", kind);
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::VALUE:
+            if (!this->parseDirectiveLiteral(out))
+                return false;
+            break;
+        default:
+            return this->raiseInvalidField("Directive", field);
+        }
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseDirectiveLiteral(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("DirectiveLiteral");
+
+    const size_t start = tokenizer->offset();
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind != BinKind::DIRECTIVE_LITERAL)
+        return this->raiseInvalidKind("DirectiveLiteral", kind);
+
+    RootedAtom value(this->cx);
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::VALUE:
+                if (!this->readString(&value))
+                    return false;
+                break;
+            default:
+                return this->raiseInvalidField("DirectiveLiteral", field);
+        }
+    }
+
+    if (!value)
+        return this->raiseMissingField("DirectiveLiteral");
+
+    TokenPos pos(start, tokenizer->offset());
+    UniqueNode result(factory.newStringLiteral(value, pos), this->nodeFree);
+    if (!result)
+        return this->raiseOOM();
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseDirectiveList(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("[Directive]");
+
+    uint32_t length;
+    AutoList guard(*tokenizer);
+    if (!tokenizer->readList(length, guard))
+        return false;
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode directive(nullptr, this->nodeFree); // Ignored
+        if (!this->parseDirective(directive))
+            return false;
+
+        if (!directive)
+            return this->raiseEmpty("[Directive]");
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseSwitchCase(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("SwitchCase");
+
+    const size_t start = tokenizer->offset();
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind != BinKind::SWITCH_CASE)
+        return this->raiseInvalidKind("SwitchCase", kind);
+
+    UniqueNode test(nullptr, this->nodeFree); // Optional.
+    UniqueNode statements(nullptr, this->nodeFree); // Required.
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::TEST:
+                if (!this->parseExpression(test))
+                    return false;
+                break;
+            case BinField::CONSEQUENT:
+                if (!this->parseStatementList(statements))
+                    return false;
+                break;
+            default:
+                return this->raiseInvalidField("SwitchCase", field);
+        }
+    }
+
+    if (!statements)
+        return this->raiseMissingField("SwitchCase");
+
+    MOZ_ASSERT(statements->isKind(PNK_STATEMENTLIST));
+
+    UniqueNode result(nullptr, this->nodeFree);
+
+    result = UniqueNode(factory.newCaseOrDefault(start, test.get(), statements.get()), this->nodeFree);
+    if (!result)
+        return this->raiseOOM();
+
+    Unused << test.release();
+    Unused << statements.release();
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parseCatchClause(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("CatchClause");
+
+    const size_t start = tokenizer->offset();
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    if (kind == BinKind::BINJS_NULL)
+        return true;
+
+    if (kind != BinKind::CATCH_CLAUSE)
+        return this->raiseInvalidKind("CatchClause", kind);
+
+    UniqueNode param(nullptr, this->nodeFree);
+    UniqueNode body(nullptr, this->nodeFree);
+    ScopeData scope(this->cx); // Optional
+
+    for (auto field: fields) {
+        switch (field) {
+            case BinField::PARAM:
+                if (!this->parsePattern(param))
+                    return false;
+                break;
+            case BinField::BODY:
+                if (!this->parseBlockStatement(body))
+                    return false;
+                break;
+            case BinField::BINJS_SCOPE:
+                if (!this->parseScope(scope))
+                    return false;
+                break;
+            default:
+                return this->raiseInvalidField("CatchClause", field);
+        }
+    }
+
+    if (!param || !body)
+        return this->raiseMissingField("CatchClause");
+
+    if (!this->promoteToLexicalScope(body))
+        return false;
+
+    UniqueNode catchClause(new_<TernaryNode>(PNK_CATCH, JSOP_NOP, param.get(), nullptr, body.get()), this->nodeFree);
+    if (!catchClause)
+        return this->raiseOOM();
+
+    Unused << param.release();
+    Unused << body.release();
+
+    if (!this->promoteToLexicalScope(catchClause))
+        return false;
+
+    if (scope.isSome() && scope.hasLexNames()) {
+        if (!this->storeLexicalScope(catchClause, Move(scope)))
+            return false;
+    }
+
+    TokenPos pos(start, tokenizer->offset());
+    UniqueNode catchList(factory.newCatchList(pos), this->nodeFree);
+    if (!catchList)
+        return this->raiseOOM();
+
+    catchList->append(catchClause.release());
+
+    out = Move(catchList);
+    return true;
+}
+
+bool
+BinASTParser::parsePatternList(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("[Pattern]");
+
+    uint32_t length;
+    AutoList guard(*tokenizer);
+
+    if (!tokenizer->readList(length, guard))
+        return false;
+
+    TokenPos pos;
+    tokenizer->latestTokenPos(pos);
+    UniqueNode result(this->new_<ListNode>(PNK_PARAMSBODY, pos), this->nodeFree);
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode pattern(nullptr, this->nodeFree);
+        if (!this->parsePattern(pattern))
+            return false;
+
+        if (!pattern)
+            return this->raiseEmpty("[Pattern]");
+
+        result->append(pattern.release());
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::parsePattern(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("Pattern");
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    return this->parsePatternAux(kind, fields, out);
+}
+
+bool
+BinASTParser::parsePatternAux(const BinKind kind, const BinFields& fields, UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("Pattern | null");
+
+    const size_t start = tokenizer->offset();
+
+    switch (kind) {
+        case BinKind::BINJS_NULL:
+            return true;
+        case BinKind::MEMBER_EXPRESSION: {
+            UniqueNode result(nullptr, this->nodeFree);
+            if (!this->parseMemberExpressionAux(kind, fields, result))
+                return false;
+
+            out = Move(result);
+            break;
+        }
+        case BinKind::IDENTIFIER: {
+            RootedAtom id(cx);
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::NAME:
+                        if (!this->readString(&id))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("Identifier as Pattern", field);
+                }
+            }
+
+            if (!id)
+                return this->raiseMissingField("Identifier as Pattern");
+
+            TokenPos pos(start, tokenizer->offset());
+            UniqueNode result(factory.newName(id->asPropertyName(), pos, cx), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            out = Move(result);
+            break;
+        }
+        default:
+            return this->raiseInvalidKind("Pattern | null", kind);
+    }
+
+    return true;
+}
+
+bool
+BinASTParser::parseObjectMember(UniqueNode& out) {
+    if (out)
+        return this->raiseAlreadyParsed("ObjectMember");
+
+    BinKind kind;
+    BinFields fields(this->cx);
+    AutoTaggedTuple guard(*tokenizer);
+
+    if (!tokenizer->readTaggedTuple(kind, fields, guard))
+        return false;
+
+    switch (kind) {
+        case BinKind::OBJECT_PROPERTY: {
+            UniqueNode key(nullptr, this->nodeFree);
+            UniqueNode value(nullptr, this->nodeFree);
+            Maybe<bool> computed;
+            for (auto field: fields) {
+                switch (field) {
+                    case BinField::KEY:
+                        if (!this->parseExpression(key))
+                            return false;
+
+                        if (key) {
+                            if (key->isKind(PNK_NAME)) {
+                                UniqueNode name(factory.newObjectLiteralPropertyName(key->pn_atom, key->pn_pos), this->nodeFree);
+                                key->pn_atom = nullptr;
+                                key = Move(name);
+                            }
+                        }
+                        break;
+                    case BinField::COMPUTED:
+                        if (!this->readBool(computed))
+                            return false;
+                        break;
+                    case BinField::VALUE:
+                        if (!this->parseExpression(value))
+                            return false;
+                        break;
+                    default:
+                        return this->raiseInvalidField("ObjectMember", field);
+                }
+            }
+
+            if (!key || !computed || !value)
+                return this->raiseMissingField("ObjectMember");
+
+            if (!(key->isKind(PNK_NUMBER) || key->isKind(PNK_OBJECT_PROPERTY_NAME)
+                || key->isKind(PNK_STRING) || key->isKind(PNK_COMPUTED_NAME)
+                || key->isKind(PNK_NAME)))
+                return this->raiseError("ObjectMember key kind");
+
+            UniqueNode result(factory.newBinary(PNK_COLON, key.get(), value.get(), JSOP_INITPROP), this->nodeFree);
+            if (!result)
+                return this->raiseOOM();
+
+            Unused << key.release();
+            Unused << value.release();
+
+            out = Move(result);
+            return true;
+        }
+        case BinKind::OBJECT_METHOD: {
+            UniqueNode result(nullptr, this->nodeFree);
+            if (!this->parseFunctionAux(kind, fields, result))
+                return false;
+
+            if (!result)
+                return this->raiseEmpty("ObjectMethod");
+
+            MOZ_ASSERT(result->isKind(PNK_COLON));
+
+            out = Move(result);
+            return true;
+        }
+        default:
+            return this->raiseInvalidKind("ObjectMember", kind);
+    }
+}
+
+bool
+BinASTParser::parseObjectMemberList(UniqueNode& out) {
+    uint32_t length;
+    AutoList guard(*tokenizer);
+
+    if (!tokenizer->readList(length, guard))
+        return false;
+
+    TokenPos pos;
+    tokenizer->latestTokenPos(pos);
+    UniqueNode result(factory.newList(PNK_NOP /*Placeholder*/, pos), this->nodeFree);
+    for (uint32_t i = 0; i < length; ++i) {
+        UniqueNode keyValue(nullptr, this->nodeFree);
+        if (!this->parseObjectMember(keyValue))
+            return false;
+
+        if (!keyValue)
+            return this->raiseEmpty("[ObjectMember]");
+
+        result->append(keyValue.release());
+    }
+
+    out = Move(result);
+    return true;
+}
+
+bool
+BinASTParser::readString(MutableHandleString out) {
+    if (out)
+        return this->raiseAlreadyParsed("String");
+
+    RootedAtom atom(this->cx);
+    if (!this->readString(&atom))
+        return false;
+
+    out.set(atom);
+    return true;
+}
+
+bool
+BinASTParser::readString(MutableHandleAtom out) {
+    if (out)
+        return this->raiseAlreadyParsed("String");
+
+    Maybe<Chars> string;
+    if (!this->readString(string))
+        return false;
+
+    if (!string)
+        return true;
+
+    RootedAtom atom(cx, Atomize(this->cx, string->begin(), string->length()));
+    if (!atom)
+        return this->raiseOOM();
+
+    out.set(Move(atom));
+    return true;
+}
+
+bool
+BinASTParser::readString(MutableHandle<PropertyName*> out) {
+    if (out)
+        return this->raiseAlreadyParsed("String");
+
+    RootedAtom atom(cx);
+
+    if (!this->readString(&atom))
+        return false;
+
+    if (!atom)
+        out.set(nullptr);
+    else
+        out.set(atom->asPropertyName());
+
+    return true;
+}
+
+bool
+BinASTParser::readString(Maybe<Chars>& out) {
+    if (out)
+        return this->raiseAlreadyParsed("String");
+
+    if (!tokenizer->readMaybeChars(out))
+        return false;
+
+    return true;
+}
+
+bool
+BinASTParser::readNumber(Maybe<double>& out) {
+    if (out)
+        return this->raiseAlreadyParsed("Number");
+
+    return tokenizer->readMaybeF64(&out);
+}
+
+bool
+BinASTParser::readBool(Maybe<bool>& out) {
+    if (out)
+        return this->raiseAlreadyParsed("Bool");
+
+    return tokenizer->readMaybeBool(&out);
+}
+
+bool
+BinASTParser::raiseInvalidKind(const char* superKind, const BinKind kind) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid kind %d", superKind, kind);
+    return this->raiseError("raiseInvalidKind");
+}
+
+bool
+BinASTParser::raiseInvalidField(const char* kind, const BinField field) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid field %d", kind, field);
+    return this->raiseError("raiseInvalidField");
+}
+
+bool
+BinASTParser::raiseInvalidEnum(const char* kind, const Chars& value) {
+    // We don't trust the actual chars of `value` to be properly formatted anything, so let's not use
+    // them anywhere.
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, invalid enum value (%zu chars)", kind, value.length());
+    return this->raiseError("raiseInvalidEnum");
+}
+
+bool
+BinASTParser::raiseMissingField(const char* kind) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, missing field", kind);
+    return this->raiseError("raiseMissingField");
+}
+
+bool
+BinASTParser::raiseAlreadyParsed(const char* kind) {
+    // As this error may appear with the BinTokenReaderTester but not with a more
+    // robust format, this method may be turned into an assertion failure in the
+    // future.
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, already parsed", kind);
+    return this->raiseError("raiseAlreadyParsed");
+}
+
+bool
+BinASTParser::raiseEmpty(const char* kind) {
+    JS_ReportErrorASCII(cx, "BinAST parse error: in %s, empty data", kind);
+    return this->raiseError("raiseEmpty");
+}
+
+bool
+BinASTParser::raiseOOM() {
+    cx->recoverFromOutOfMemory();
+    return false;
+}
+
+
+bool
+BinASTParser::raiseError(const char* method) {
+    TokenPos pos;
+    tokenizer->latestTokenPos(pos);
+    fprintf(stderr, "BinASTParser::raiseError() from %s at %u -> %u\n",
+        method,
+        pos.begin,
+        pos.end);
+    MOZ_CRASH("Debugging...");
+    return false;
+}
+
+void
+BinASTParser::reportErrorNoOffset(unsigned errorNumber, ...) {
+    va_list args;
+    va_start(args, errorNumber);
+
+    ErrorMetadata metadata;
+    metadata.filename = this->getFilename();
+    metadata.lineNumber = 0;
+    metadata.columnNumber = offset();
+    ReportCompileError(cx, Move(metadata), nullptr, JSREPORT_ERROR, errorNumber, args);
+
+    va_end(args);
+}
+
+bool
+BinASTParser::hasUsedName(HandlePropertyName name) {
+    if (UsedNamePtr p = this->usedNames.lookup(name))
+        return p->value().isUsedInScript(this->parseContext->scriptId());
+
+    return false;
+}
+
+void
+TraceBinParser(JSTracer* trc, AutoGCRooter* parser) {
+    static_cast<BinASTParser*>(parser)->trace(trc);
+}
+
+} // namespace frontend
+} // namespace js
+
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinSource.h
@@ -0,0 +1,345 @@
+#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/FullParseHandler.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>;
+    using Tokenizer = BinTokenReaderTester;
+    using Chars = Tokenizer::Chars;
+
+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_)
+        , factory(cx_, alloc, nullptr)
+    {
+         cx_->frontendCollectionPool().addActiveCompilation();
+         factory.setIsSourceText(false);
+    }
+    ~BinASTParser()
+    {
+        cx->frontendCollectionPool().removeActiveCompilation();
+    }
+
+    /**
+     * 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 size_t length);
+    Maybe<UniqueNode> parse(const Vector<char>& data);
+
+private:
+    // --- Raise errors.
+
+    MOZ_MUST_USE bool raiseInvalidKind(const char* superKind, const BinKind kind);
+    MOZ_MUST_USE bool raiseInvalidField(const char* kind, const BinField field);
+    MOZ_MUST_USE bool raiseInvalidEnum(const char* kind, const Chars& value);
+    MOZ_MUST_USE bool raiseMissingField(const char* kind);
+    MOZ_MUST_USE bool raiseAlreadyParsed(const char* kind);
+    MOZ_MUST_USE bool raiseEmpty(const char* kind);
+    MOZ_MUST_USE bool raiseOOM();
+    MOZ_MUST_USE bool raiseError(const char* 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`.
+    MOZ_MUST_USE bool parseBlockStatement(UniqueNode& out);
+    MOZ_MUST_USE bool parseCatchClause(UniqueNode& out);
+    MOZ_MUST_USE bool parseDirective(UniqueNode& out);
+    MOZ_MUST_USE bool parseDirectiveLiteral(UniqueNode& out);
+    MOZ_MUST_USE bool parseExpression(UniqueNode& out);
+    MOZ_MUST_USE bool parseForHead(UniqueNode& out);
+    MOZ_MUST_USE bool parseForInHead(UniqueNode& out);
+    MOZ_MUST_USE bool parseObjectMember(UniqueNode& out);
+    MOZ_MUST_USE bool parsePattern(UniqueNode& out);
+    MOZ_MUST_USE bool parseProgram(UniqueNode& out);
+    MOZ_MUST_USE bool parseScope(ScopeData& out);
+    MOZ_MUST_USE bool parseStatement(UniqueNode& out);
+    MOZ_MUST_USE bool parseSwitchCase(UniqueNode& out);
+    MOZ_MUST_USE bool parseVariableDeclarator(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.
+
+    MOZ_MUST_USE bool parseDirectiveList(UniqueNode&);
+    // Produces a list of expressions. Empty expressions are replaced with PNK_ELISION.
+    MOZ_MUST_USE bool parseExpressionOrElisionList(UniqueNode& out);
+
+    // Returns a list of PNK_COLON.
+    MOZ_MUST_USE bool parseObjectMemberList(UniqueNode&);
+
+    MOZ_MUST_USE bool parsePatternList(UniqueNode&);
+    MOZ_MUST_USE bool parseStatementList(UniqueNode& out);
+    MOZ_MUST_USE bool parseStringList(MutableHandle<mozilla::Maybe<Names>> out);
+    MOZ_MUST_USE bool parseStringList(MutableHandle<Names> out);
+    MOZ_MUST_USE bool parseStringSet(MutableHandle<mozilla::Maybe<NameBag>>);
+    MOZ_MUST_USE bool parseStringSet(MutableHandle<NameBag>);
+    MOZ_MUST_USE bool parseSwitchCaseList(UniqueNode& out);
+
+    // --- Parse the contents of a node whose kind has already been determined.
+
+    MOZ_MUST_USE bool parseBlockStatementAux(const BinKind kind, const Tokenizer::BinFields& fields, UniqueNode& out);
+    MOZ_MUST_USE bool parseExpressionStatementAux(const BinKind kind, const Tokenizer::BinFields& fields, UniqueNode& out);
+    MOZ_MUST_USE bool parseExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields, UniqueNode& out);
+    MOZ_MUST_USE bool parseFunctionAux(const BinKind kind, const Tokenizer::BinFields& fields, UniqueNode& out);
+    MOZ_MUST_USE bool parseMemberExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields, UniqueNode& out);
+    MOZ_MUST_USE bool parsePatternAux(const BinKind kind, const Tokenizer::BinFields& fields, UniqueNode& out);
+    MOZ_MUST_USE bool parseVariableDeclarationAux(const BinKind kind, const Tokenizer::BinFields& fields, UniqueNode& out);
+
+    // --- Utilities.
+
+    // Modifies in place a node to ensure that it holds a lexical scope.
+    MOZ_MUST_USE bool promoteToLexicalScope(UniqueNode& node);
+
+    // Store any lexically-declared variables in a node. The node MUST be a lexical scope.
+    MOZ_MUST_USE bool storeLexicalScope(UniqueNode& body, ScopeData&& scope);
+
+    // `true` if `name` is used in the subtree
+    bool hasUsedName(HandlePropertyName name);
+
+    MOZ_MUST_USE bool readString(mozilla::Maybe<Chars>&);
+    MOZ_MUST_USE bool readString(MutableHandleString);
+    MOZ_MUST_USE bool readString(MutableHandleAtom);
+    MOZ_MUST_USE bool readString(MutableHandle<PropertyName*>);
+    MOZ_MUST_USE bool readBool(mozilla::Maybe<bool>&);
+    MOZ_MUST_USE bool readNumber(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 {
+        if (tokenizer.isSome()) {
+            return tokenizer->offset();
+        } else {
+            return 0;
+        }
+    }
+    virtual bool hasTokenizationStarted() const override {
+        return tokenizer.isSome();
+    }
+    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;
+    Maybe<Tokenizer> tokenizer;
+    FullParseHandler factory;
+    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/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -41,16 +41,22 @@ class FullParseHandler
      * If this is a full parse to construct the bytecode for a function that
      * was previously lazily parsed, that lazy function and the current index
      * into its inner functions. We do not want to reparse the inner functions.
      */
     const Rooted<LazyScript*> lazyOuterFunction_;
     size_t lazyInnerFunctionIndex;
     size_t lazyClosedOverBindingIndex;
 
+    /**
+     * `true` If we're parsing from a text source (from Parser.h),
+     * `false` if we're parsing from a binary source (from BinSource.h).
+     */
+    bool isSourceText_;
+
   public:
     /* new_ methods for creating parse nodes. These report OOM on context. */
     JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
 
     typedef ParseNode* Node;
 
     bool isPropertyAccess(ParseNode* node) {
         return node->isKind(PNK_DOT) || node->isKind(PNK_ELEM);
@@ -77,21 +83,30 @@ class FullParseHandler
         return isUnparenthesizedDestructuringPattern(node) ||
                isParenthesizedDestructuringPattern(node);
     }
 
     FullParseHandler(JSContext* cx, LifoAlloc& alloc, LazyScript* lazyOuterFunction)
       : allocator(cx, alloc),
         lazyOuterFunction_(cx, lazyOuterFunction),
         lazyInnerFunctionIndex(0),
-        lazyClosedOverBindingIndex(0)
+        lazyClosedOverBindingIndex(0),
+        isSourceText_(true)
     {}
 
     static ParseNode* null() { return nullptr; }
 
+    // The FullParseHandler may be used to parse create nodes for text sources
+    // (from Parser.h) or for binay sources (from BinSource.h). In the latter
+    // case, some common assumptions on offsets are incorrect, e.g. in `a + b`,
+    // `a`, `b` and `+` may be stored in any order. We use `isSourceText`
+    // to determine whether we need to check these assumptions.
+    bool isSourceText() { return isSourceText_; }
+    void setIsSourceText(bool value) { isSourceText_ = value; }
+
     ParseNode* freeTree(ParseNode* pn) { return allocator.freeTree(pn); }
     void prepareNodeForMutation(ParseNode* pn) { return allocator.prepareNodeForMutation(pn); }
 
     ParseNode* newName(PropertyName* name, const TokenPos& pos, JSContext* cx)
     {
         return new_<NameNode>(PNK_NAME, JSOP_GETNAME, name, pos);
     }
 
@@ -245,50 +260,50 @@ class FullParseHandler
     // Expressions
 
     ParseNode* newArrayComprehension(ParseNode* body, const TokenPos& pos) {
         MOZ_ASSERT(pos.begin <= body->pn_pos.begin);
         MOZ_ASSERT(body->pn_pos.end <= pos.end);
         ParseNode* pn = new_<ListNode>(PNK_ARRAYCOMP, pos);
         if (!pn)
             return nullptr;
-        pn->append(body);
+        addList(/*list*/pn, /*child*/body);
         return pn;
     }
 
     ParseNode* newArrayLiteral(uint32_t begin) {
         ParseNode* literal = new_<ListNode>(PNK_ARRAY, TokenPos(begin, begin + 1));
         // Later in this stack: remove dependency on this opcode.
         if (literal)
             literal->setOp(JSOP_NEWINIT);
         return literal;
     }
 
     MOZ_MUST_USE bool addElision(ParseNode* literal, const TokenPos& pos) {
         ParseNode* elision = new_<NullaryNode>(PNK_ELISION, pos);
         if (!elision)
             return false;
-        literal->append(elision);
+        addList(/*list*/literal, /*child*/elision);
         literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
         return true;
     }
 
     MOZ_MUST_USE bool addSpreadElement(ParseNode* literal, uint32_t begin, ParseNode* inner) {
         ParseNode* spread = newSpread(begin, inner);
         if (!spread)
             return false;
-        literal->append(spread);
+        addList(/*list*/literal, /*child*/spread);
         literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
         return true;
     }
 
     void addArrayElement(ParseNode* literal, ParseNode* element) {
         if (!element->isConstant())
             literal->pn_xflags |= PNX_NONCONST;
-        literal->append(element);
+        addList(/*list*/literal, /*child*/element);
     }
 
     ParseNode* newCall(const TokenPos& pos) {
         return newList(PNK_CALL, pos, JSOP_CALL);
     }
 
     ParseNode* newTaggedTemplate(const TokenPos& pos) {
         return newList(PNK_TAGGED_TEMPLATE, pos, JSOP_CALL);
@@ -326,92 +341,92 @@ class FullParseHandler
     MOZ_MUST_USE bool addPrototypeMutation(ParseNode* literal, uint32_t begin, ParseNode* expr) {
         // Object literals with mutated [[Prototype]] are non-constant so that
         // singleton objects will have Object.prototype as their [[Prototype]].
         setListFlag(literal, PNX_NONCONST);
 
         ParseNode* mutation = newUnary(PNK_MUTATEPROTO, JSOP_NOP, begin, expr);
         if (!mutation)
             return false;
-        literal->append(mutation);
+        addList(/*list*/literal, /*child*/mutation);
         return true;
     }
 
     MOZ_MUST_USE bool addPropertyDefinition(ParseNode* literal, ParseNode* key, ParseNode* val) {
         MOZ_ASSERT(literal->isKind(PNK_OBJECT));
         MOZ_ASSERT(literal->isArity(PN_LIST));
         MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
                    key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
                    key->isKind(PNK_STRING) ||
                    key->isKind(PNK_COMPUTED_NAME));
 
         ParseNode* propdef = newBinary(PNK_COLON, key, val, JSOP_INITPROP);
         if (!propdef)
             return false;
-        literal->append(propdef);
+        addList(/*list*/literal, /*child*/propdef);
         return true;
     }
 
     MOZ_MUST_USE bool addShorthand(ParseNode* literal, ParseNode* name, ParseNode* expr) {
         MOZ_ASSERT(literal->isKind(PNK_OBJECT));
         MOZ_ASSERT(literal->isArity(PN_LIST));
         MOZ_ASSERT(name->isKind(PNK_OBJECT_PROPERTY_NAME));
         MOZ_ASSERT(expr->isKind(PNK_NAME));
         MOZ_ASSERT(name->pn_atom == expr->pn_atom);
 
         setListFlag(literal, PNX_NONCONST);
         ParseNode* propdef = newBinary(PNK_SHORTHAND, name, expr, JSOP_INITPROP);
         if (!propdef)
             return false;
-        literal->append(propdef);
+        addList(/*list*/literal, /*child*/propdef);
         return true;
     }
 
     MOZ_MUST_USE bool addSpreadProperty(ParseNode* literal, uint32_t begin, ParseNode* inner) {
         MOZ_ASSERT(literal->isKind(PNK_OBJECT));
         MOZ_ASSERT(literal->isArity(PN_LIST));
 
         setListFlag(literal, PNX_NONCONST);
         ParseNode* spread = newSpread(begin, inner);
         if (!spread)
             return false;
-        literal->append(spread);
+        addList(/*list*/literal, /*child*/spread);
         return true;
     }
 
     MOZ_MUST_USE bool addObjectMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn,
                                                 JSOp op)
     {
         MOZ_ASSERT(literal->isArity(PN_LIST));
         MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
                    key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
                    key->isKind(PNK_STRING) ||
                    key->isKind(PNK_COMPUTED_NAME));
         literal->pn_xflags |= PNX_NONCONST;
 
         ParseNode* propdef = newBinary(PNK_COLON, key, fn, op);
         if (!propdef)
             return false;
-        literal->append(propdef);
+        addList(/*list*/literal, /*child*/propdef);
         return true;
     }
 
     MOZ_MUST_USE bool addClassMethodDefinition(ParseNode* methodList, ParseNode* key, ParseNode* fn,
                                                JSOp op, bool isStatic)
     {
         MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST));
         MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
                    key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
                    key->isKind(PNK_STRING) ||
                    key->isKind(PNK_COMPUTED_NAME));
 
         ParseNode* classMethod = new_<ClassMethod>(key, fn, op, isStatic);
         if (!classMethod)
             return false;
-        methodList->append(classMethod);
+        addList(/*list*/methodList, /*child*/classMethod);
         return true;
     }
 
     ParseNode* newInitialYieldExpression(uint32_t begin, ParseNode* gen) {
         TokenPos pos(begin, begin + 1);
         return new_<UnaryNode>(PNK_INITIALYIELD, JSOP_INITIALYIELD, pos, gen);
     }
 
@@ -440,17 +455,17 @@ class FullParseHandler
         while (stmt->isKind(PNK_LABEL))
             stmt = stmt->as<LabeledStatement>().statement();
         return stmt->isKind(PNK_FUNCTION);
     }
 
     void addStatementToList(ParseNode* list, ParseNode* stmt) {
         MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
 
-        list->append(stmt);
+        addList(/*list*/list, /*child*/stmt);
 
         if (isFunctionStmt(stmt)) {
             // PNX_FUNCDEFS notifies the emitter that the block contains
             // body-level function definitions that should be processed
             // before the rest of nodes.
             list->pn_xflags |= PNX_FUNCDEFS;
         }
     }
@@ -460,17 +475,17 @@ class FullParseHandler
         list->pn_pos.end = pos.end;
     }
 
     void addCaseStatementToList(ParseNode* list, ParseNode* casepn) {
         MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
         MOZ_ASSERT(casepn->isKind(PNK_CASE));
         MOZ_ASSERT(casepn->pn_right->isKind(PNK_STATEMENTLIST));
 
-        list->append(casepn);
+        addList(/*list*/list, /*child*/casepn);
 
         if (casepn->pn_right->pn_xflags & PNX_FUNCDEFS)
             list->pn_xflags |= PNX_FUNCDEFS;
     }
 
     MOZ_MUST_USE bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) {
         MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST));
 
@@ -696,21 +711,21 @@ class FullParseHandler
         pn->pn_body = kid;
     }
     void setFunctionBox(ParseNode* pn, FunctionBox* funbox) {
         MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
         pn->pn_funbox = funbox;
         funbox->functionNode = pn;
     }
     void addFunctionFormalParameter(ParseNode* pn, ParseNode* argpn) {
-        pn->pn_body->append(argpn);
+        addList(/*list*/pn->pn_body, /*child*/argpn);
     }
     void setFunctionBody(ParseNode* fn, ParseNode* body) {
         MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY));
-        fn->pn_body->append(body);
+        addList(/*list*/fn->pn_body, /*child*/body);
     }
 
     ParseNode* newModule(const TokenPos& pos) {
         return new_<CodeNode>(PNK_MODULE, JSOP_NOP, pos);
     }
 
     ParseNode* newLexicalScope(LexicalScope::Data* bindings, ParseNode* body) {
         return new_<LexicalScopeNode>(bindings, body);
@@ -838,16 +853,17 @@ class FullParseHandler
         return new_<ListNode>(PNK_CATCHLIST, JSOP_NOP, pos);
     }
 
     ParseNode* newCommaExpressionList(ParseNode* kid) {
         return newList(PNK_COMMA, kid, JSOP_NOP);
     }
 
     void addList(ParseNode* list, ParseNode* kid) {
+        MOZ_ASSERT_IF(isSourceText_, kid->pn_pos.begin >= list->pn_pos.begin);
         list->append(kid);
     }
 
     void setOp(ParseNode* pn, JSOp op) {
         pn->setOp(op);
     }
     void setListFlag(ParseNode* pn, unsigned flag) {
         MOZ_ASSERT(pn->isArity(PN_LIST));
@@ -938,20 +954,21 @@ class FullParseHandler
     }
 };
 
 inline bool
 FullParseHandler::addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
                                 ParseNode* catchName, ParseNode* catchGuard,
                                 ParseNode* catchBody)
 {
+    MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST));
     ParseNode* catchpn = newTernary(PNK_CATCH, catchName, catchGuard, catchBody);
     if (!catchpn)
         return false;
-    catchList->append(lexicalScope);
+    addList(/*list*/catchList, /*child*/lexicalScope);
     lexicalScope->setScopeBody(catchpn);
     return true;
 }
 
 inline bool
 FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn,
                                                         ParseNode* defaultValue)
 {
--- 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/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -761,17 +761,16 @@ class ParseNode
         pn_head = pn;
         pn_tail = &pn->pn_next;
         pn_count = 1;
         pn_xflags = 0;
     }
 
     void append(ParseNode* pn) {
         MOZ_ASSERT(pn_arity == PN_LIST);
-        MOZ_ASSERT(pn->pn_pos.begin >= pn_pos.begin);
         pn_pos.end = pn->pn_pos.end;
         *pn_tail = pn;
         pn_tail = &pn->pn_next;
         pn_count++;
     }
 
     void prepend(ParseNode* pn) {
         MOZ_ASSERT(pn_arity == PN_LIST);
--- 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