Bug 1377007 - WIP BinJS parser draft
authorDavid Teller <dteller@mozilla.com>
Thu, 29 Jun 2017 11:42:50 -0700
changeset 641326 4c331720664b137653851efb22f63ab713a6ce4b
parent 641325 cd4cf06020141c1518417d30f9d45bc5d5d7c1c8
child 641327 9b7dd82d9bc40c62e86a758db99335a66fda7301
push id72504
push userdteller@mozilla.com
push dateSun, 06 Aug 2017 22:28:40 +0000
bugs1377007
milestone57.0a1
Bug 1377007 - WIP BinJS parser MozReview-Commit-ID: 8QkfRHSat8T
js/src/frontend/BinSource.cpp
js/src/moz.build
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinSource.cpp
@@ -0,0 +1,370 @@
+#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/BinTokenReader.h>
+#include <frontend/ParseNode.h>
+
+
+using namespace mozilla;
+using NameBag = GCHashSet<JSString*>;
+using Names = GCVector<JSString*, 8>;
+
+namespace js {
+namespace frontend {
+
+
+// Copied from Parser.cpp
+
+template <typename Scope>
+static typename Scope::Data*
+NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc, uint32_t numBindings)
+{
+    size_t allocSize = Scope::sizeOfData(numBindings);
+    typename Scope::Data* bindings = static_cast<typename Scope::Data*>(alloc.alloc(allocSize));
+    if (!bindings) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
+    PodZero(bindings);
+    return bindings;
+}
+
+const std::string BINJS_VAR_NAME = "BINJS:VarDeclaredNames";
+const std::string BINJS_LET_NAME = "BINJS:LetDeclaredNames";
+const std::string BINJS_CONST_NAME = "BINJS:ConstDeclaredNames";
+const std::string BINJS_CAPTURED_NAME = "BINJS:CapturedNames";
+const std::string BINJS_DIRECT_EVAL = "BINJS:HasDirectEval";
+
+class ASTReader
+{
+public:
+    bool parse(char* start, char* stop, UniquePtr<ParseNode>& out);
+private:
+    bool raiseError();
+
+    // --- Parse full nodes.
+
+    bool parseBool(SimpleTokenReader*, Maybe<bool>*);
+    bool parseProgram(SimpleTokenReader* reader, UniquePtr<ParseNode>& out);
+    bool parseStatement(SimpleTokenReader* reader, UniquePtr<ParseNode>& out);
+    bool parseStatementList(SimpleTokenReader* reader, UniquePtr<ParseNode>& out);
+    bool parseStringList(SimpleTokenReader* reader, MutableHandle<Maybe<Names>> out);
+    bool parseStringSet(SimpleTokenReader* reader, MutableHandle<Maybe<NameBag>>);
+
+    // --- Parse the contents of a node whose kind has already been determined.
+    bool parseBlockStatementAux(SimpleTokenReader* reader, const SimpleTokenReader::Fields* fields, UniquePtr<ParseNode>& out);
+    bool parseExpressionStatementAux(SimpleTokenReader* reader, const SimpleTokenReader::Fields* fields, UniquePtr<ParseNode>& out);
+
+    // --- Utilities.
+    bool readString(SimpleTokenReader* reader, MutableHandleString);
+
+    ParseNodeAllocator allocator;
+
+    ParseNode* allocParseNode(size_t size) {
+        MOZ_ASSERT(size == sizeof(ParseNode));
+        return static_cast<ParseNode*>(allocator.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:
+    JSContext* cx;
+    LifoAlloc& alloc;
+};
+
+bool
+ASTReader::parse(char* start, char* stop, UniquePtr<ParseNode>& out) {
+    SimpleTokenReader reader(this->cx);
+    reader.init(start, stop, nullptr);
+
+    if (!this->parseProgram(&reader, out) || !reader.uninit()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool
+ASTReader::parseProgram(SimpleTokenReader* reader, UniquePtr<ParseNode>& out) {
+    if (out) {
+        // Already parsed.
+        return this->raiseError();
+    }
+    SimpleTokenReader::Fields fields(this->cx);
+    SimpleTokenReader sub(this->cx);
+
+    std::string name;
+    if (!reader->taggedTuple(&name, &fields, &sub)) {
+        return false;
+    }
+
+    if (name != "Program") {
+        return false;
+    }
+
+    return this->parseBlockStatementAux(&sub, &fields, out);
+}
+
+bool
+ASTReader::parseBlockStatementAux(SimpleTokenReader* reader, const SimpleTokenReader::Fields* fields, UniquePtr<ParseNode>& out) {
+    UniquePtr<ParseNode> body;
+    Maybe<bool> hasDirectEval;
+    Rooted<Maybe<Names>> letNames(this->cx);
+    Rooted<Maybe<Names>> constNames(this->cx);
+    Rooted<Maybe<Names>> varNames(this->cx);
+    Rooted<Maybe<NameBag>> capturedNames(this->cx);
+
+    // FIXME: There must be a nicer way to express all of this.
+    // FIXME: Could we somehow generate an optimized parser from the header?
+    for (auto field: *fields) {
+        // Fields inherited from `BINJS::Scope`.
+        // FIXME: We could share this code.
+        if (field == BINJS_DIRECT_EVAL) {
+            if (!this->parseBool(reader, &hasDirectEval)) {
+                return false;
+            }
+        } else if (field == BINJS_LET_NAME) {
+            if (!this->parseStringList(reader, &letNames)) {
+                return false;
+            }
+        } else if (field == BINJS_CONST_NAME) {
+            if (!this->parseStringList(reader, &constNames)) {
+                return false;
+            }
+        } else if (field == BINJS_VAR_NAME) {
+            if (!this->parseStringList(reader, &varNames)) {
+                return false;
+            }
+        } else if (field == BINJS_CAPTURED_NAME) {
+            if (!this->parseStringSet(reader, &capturedNames)) {
+                return false;
+            }
+        }
+        // Own fields
+        else if (field == "body") {
+            if (!this->parseStatementList(reader, body)) {
+                return false;
+            }
+        } else {
+            this->raiseError();
+            return false;
+        }
+    }
+
+    // Check if all fields have been parsed.
+    if (!body || hasDirectEval.isNothing()
+            || letNames.get().isNothing()
+            || constNames.get().isNothing()
+            || varNames.get().isNothing()
+            || capturedNames.get().isNothing()) {
+        // FIXME: Once we have headers + header validation, this error check
+        // should become an assertion.
+        return this->raiseError();
+    }
+
+    // FIXME: For the moment, we are conflating `let` and `const`.
+    // Probably not a good idea.
+    UniquePtr<LexicalScope::Data> bindings(
+        NewEmptyBindingData<LexicalScope>(this->cx, this->alloc, letNames->length() + constNames->length())
+    );
+    if (!bindings) {
+        return this->raiseError();
+    }
+
+    BindingName* cursor = bindings->names;
+    for (auto& name: *letNames.get()) {
+        JS::Rooted<JSAtom*> atom(cx, AtomizeString(this->cx, name));
+        bool isCaptured = capturedNames->has(name);
+        BindingName binding(atom, isCaptured);
+        PodCopy(cursor, &binding, 1); // FIXME: Why does this work?
+        cursor++;
+        bindings->length++;
+    }
+    for (auto& name: *constNames.get()) {
+        JS::Rooted<JSAtom*> atom(cx, AtomizeString(this->cx, name));
+        bool isCaptured = capturedNames->has(name);
+        BindingName binding(atom, isCaptured);
+        PodCopy(cursor, &binding, 1); // FIXME: Why does this work?
+        cursor++;
+        bindings->length++;
+    }
+    bindings->constStart = letNames->length();
+
+    UniquePtr<ParseNode> result(new_<LexicalScopeNode>(bindings.release(), body.release()));
+    out = Move(result);
+    // FIXME: Validate capturedNames, etc.
+    return true;
+}
+
+bool
+ASTReader::parseStringList(SimpleTokenReader* reader, MutableHandle<Maybe<Names>> out) {
+    if (out.get()) {
+        return this->raiseError();
+    }
+    uint32_t length;
+    SimpleTokenReader sub(this->cx);
+
+    Names result(this->cx);
+
+    if (!reader->readList(&length, &sub)) {
+        return false;
+    }
+
+    if (!result.reserve(length)) {
+        return this->raiseError();
+    }
+    for (uint32_t i = 0; i < length; ++i) {
+        RootedString string(this->cx);
+        if (!this->readString(&sub, &string)) {
+            return false;
+        }
+
+        if (!result.append(Move(string))) {
+            MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(); // Checked in the call to `reserve`.
+        }
+    }
+
+    out.set(Move(Some(Move(result))));
+    return true;
+}
+
+bool
+ASTReader::parseStatementList(SimpleTokenReader* reader, UniquePtr<ParseNode>& out) {
+    if (out) {
+        return this->raiseError();
+    }
+    uint32_t length;
+    SimpleTokenReader sub(this->cx);
+
+    UniquePtr<ParseNode> result(new_<ListNode>(PNK_STATEMENTLIST, TokenPos()));
+    if (!result) {
+        return this->raiseError();
+    }
+
+    if (!reader->readList(&length, &sub)) {
+        return false;
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+        UniquePtr<ParseNode> statement;
+        if (!this->parseStatement(&sub, statement)) {
+            return false;
+        }
+
+        result->append(statement.release()); // `result` knows how to deallocate `statement`.
+    }
+
+    result.swap(out);
+    return true;
+}
+
+bool
+ASTReader::parseStatement(SimpleTokenReader* reader, UniquePtr<ParseNode>& out) {
+    if (out) {
+        return this->raiseError();
+    }
+    SimpleTokenReader::Fields fields(this->cx);
+    SimpleTokenReader sub(this->cx);
+
+    std::string name;
+    if (!reader->taggedTuple(&name, &fields, &sub)) {
+        return false;
+    }
+
+    if (name == "EmptyStatement") {
+        UniquePtr<ParseNode> result(new_<NullaryNode>(PNK_NOP, TokenPos()));
+        if (!result) {
+            return false;
+        }
+        out = Move(result);
+    } else if (name == "BlockStatement") {
+        UniquePtr<ParseNode> body;
+        if (!this->parseBlockStatementAux(&sub, &fields, body)) {
+            return false;
+        }
+        out = Move(body);
+    } else if (name == "ExpressionStatement") {
+        UniquePtr<ParseNode> body;
+        if (!this->parseExpressionStatementAux(&sub, &fields, body)) {
+            return false;
+        }
+        out = Move(body);
+    } else if (name == "DebuggerStatement") {
+        UniquePtr<ParseNode> result(new_<DebuggerStatement>(TokenPos()));
+        if (!result) {
+            return false;
+        }
+        out = Move(result);
+    } else if (name == "WithStatement") {
+        // FIXME: Implement
+    } else if (name == "ReturnStatement") {
+        // FIXME: Implement
+    } else if (name == "LabeledStatement") {
+        // FIXME: Implement
+    } else if (name == "BreakStatement") {
+        // FIXME: Implement
+    } else if (name == "ContinueStatement") {
+        // FIXME: Implement
+    } else if (name == "IfStatement") {
+        // FIXME: Implement
+    } else if (name == "SwitchStatement") {
+        // FIXME: Implement
+    } else if (name == "SwitchCase") {
+        // FIXME: Implement
+    } else if (name == "ThrowStatement") {
+        // FIXME: Implement
+    } else if (name == "TryStatement") {
+        // FIXME: Implement
+    } else if (name == "WhileStatement") {
+        // FIXME: Implement
+    } else if (name == "DoWhileStatement") {
+        // FIXME: Implement
+    } else if (name == "ForStatement") {
+        // FIXME: Implement
+    } else if (name == "ForInStatement") {
+        // FIXME: Implement
+    } else if (name == "FunctionDeclaration") {
+        // FIXME: Implement
+    } else if (name == "VariableDeclaration") {
+        // FIXME: Implement
+    } else if (name == "SwitchStatement") {
+        // FIXME: Implement
+    } else {
+        return this->raiseError();
+    }
+
+    return true;
+}
+
+bool
+ASTReader::parseBool(SimpleTokenReader* reader, Maybe<bool>* result) {
+    if (result->isSome()) {
+        // Already parsed.
+        // FIXME: Can replace with an assert once we have
+        // implemented headers and header validation.
+        return this->raiseError();
+    }
+    return reader->readBool(result);
+}
+
+bool
+ASTReader::raiseError() {
+    // FIXME: Implement actual error conditions.
+    return false;
+}
+
+
+} // namespace frontend
+} // namespace js
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -163,16 +163,17 @@ UNIFIED_SOURCES += [
     'builtin/TestingFunctions.cpp',
     'builtin/TypedObject.cpp',
     'builtin/WeakMapObject.cpp',
     'builtin/WeakSetObject.cpp',
     'devtools/sharkctl.cpp',
     'ds/Bitmap.cpp',
     'ds/LifoAlloc.cpp',
     'ds/MemoryProtectionExceptionHandler.cpp',
+    'frontend/BinSource.cpp',
     'frontend/BinTokenReader.cpp',
     'frontend/BytecodeCompiler.cpp',
     'frontend/BytecodeEmitter.cpp',
     'frontend/FoldConstants.cpp',
     'frontend/NameFunctions.cpp',
     'frontend/ParseNode.cpp',
     'frontend/TokenStream.cpp',
     'gc/Allocator.cpp',