| // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors |
| // Licensed under the MIT License: |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| // THE SOFTWARE. |
| |
| #include "compiler.h" |
| #include "parser.h" // only for generateChildId() |
| #include <kj/mutex.h> |
| #include <kj/arena.h> |
| #include <kj/vector.h> |
| #include <kj/debug.h> |
| #include <capnp/message.h> |
| #include <map> |
| #include <set> |
| #include <unordered_map> |
| #include "node-translator.h" |
| |
| namespace capnp { |
| namespace compiler { |
| |
| typedef std::unordered_map<uint64_t, Orphan<schema::Node::SourceInfo::Reader>> SourceInfoMap; |
| |
| class Compiler::Alias { |
| public: |
| Alias(CompiledModule& module, Node& parent, const Expression::Reader& targetName) |
| : module(module), parent(parent), targetName(targetName) {} |
| |
| kj::Maybe<Resolver::ResolveResult> compile(); |
| |
| private: |
| CompiledModule& module; |
| Node& parent; |
| Expression::Reader targetName; |
| kj::Maybe<Resolver::ResolveResult> target; |
| Orphan<schema::Brand> brandOrphan; |
| bool initialized = false; |
| }; |
| |
| class Compiler::Node final: public Resolver { |
| // Passes through four states: |
| // - Stub: On initial construction, the Node is just a placeholder object. Its ID has been |
| // determined, and it is placed in its parent's member table as well as the compiler's |
| // nodes-by-ID table. |
| // - Expanded: Nodes have been constructed for all of this Node's nested children. This happens |
| // the first time a lookup is performed for one of those children. |
| // - Bootstrap: A NodeTranslator has been built and advanced to the bootstrap phase. |
| // - Finished: A final Schema object has been constructed. |
| |
| public: |
| explicit Node(CompiledModule& module); |
| // Create a root node representing the given file. May |
| |
| Node(Node& parent, const Declaration::Reader& declaration); |
| // Create a child node. |
| |
| Node(kj::StringPtr name, Declaration::Which kind, |
| List<Declaration::BrandParameter>::Reader genericParams); |
| // Create a dummy node representing a built-in declaration, like "Int32" or "true". |
| |
| uint64_t getId() { return id; } |
| uint getParameterCount() { return genericParamCount; } |
| Declaration::Which getKind() { return kind; } |
| |
| kj::Maybe<Schema> getBootstrapSchema(); |
| kj::Maybe<schema::Node::Reader> getFinalSchema(); |
| void loadFinalSchema(const SchemaLoader& loader); |
| |
| void traverse(uint eagerness, std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo); |
| // Get the final schema for this node, and also possibly traverse the node's children and |
| // dependencies to ensure that they are loaded, depending on the mode. |
| |
| void addError(kj::StringPtr error); |
| // Report an error on this Node. |
| |
| // implements Resolver --------------------------------------------- |
| kj::Maybe<ResolveResult> resolve(kj::StringPtr name) override; |
| kj::Maybe<ResolveResult> resolveMember(kj::StringPtr name) override; |
| ResolvedDecl resolveBuiltin(Declaration::Which which) override; |
| ResolvedDecl resolveId(uint64_t id) override; |
| kj::Maybe<ResolvedDecl> getParent() override; |
| ResolvedDecl getTopScope() override; |
| kj::Maybe<Schema> resolveBootstrapSchema( |
| uint64_t id, schema::Brand::Reader brand) override; |
| kj::Maybe<schema::Node::Reader> resolveFinalSchema(uint64_t id) override; |
| kj::Maybe<ResolvedDecl> resolveImport(kj::StringPtr name) override; |
| kj::Maybe<kj::Array<const byte>> readEmbed(kj::StringPtr name) override; |
| kj::Maybe<Type> resolveBootstrapType(schema::Type::Reader type, Schema scope) override; |
| |
| private: |
| CompiledModule* module; // null iff isBuiltin is true |
| kj::Maybe<Node&> parent; |
| |
| Declaration::Reader declaration; |
| // AST of the declaration parsed from the schema file. May become invalid once the content |
| // state has reached FINISHED. |
| |
| uint64_t id; |
| // The ID of this node, either taken from the AST or computed based on the parent. Or, a dummy |
| // value, if duplicates were detected. |
| |
| kj::StringPtr displayName; |
| // Fully-qualified display name for this node. For files, this is just the file name, otherwise |
| // it is "filename:Path.To.Decl". |
| |
| Declaration::Which kind; |
| // Kind of node. |
| |
| uint genericParamCount; |
| // Number of generic parameters. |
| |
| bool isBuiltin; |
| // Whether this is a bulit-in declaration, like "Int32" or "true". |
| |
| uint32_t startByte; |
| uint32_t endByte; |
| // Start and end byte for reporting general errors. |
| |
| struct Content { |
| inline Content(): state(STUB) {} |
| |
| enum State { |
| STUB, |
| EXPANDED, |
| BOOTSTRAP, |
| FINISHED |
| }; |
| State state; |
| // Indicates which fields below are valid. |
| |
| inline bool stateHasReached(State minimumState) { |
| return state >= minimumState; |
| } |
| inline void advanceState(State newState) { |
| state = newState; |
| } |
| |
| // EXPANDED ------------------------------------ |
| |
| typedef std::multimap<kj::StringPtr, kj::Own<Node>> NestedNodesMap; |
| NestedNodesMap nestedNodes; |
| kj::Vector<Node*> orderedNestedNodes; |
| // multimap in case of duplicate member names -- we still want to compile them, even if it's an |
| // error. |
| |
| typedef std::multimap<kj::StringPtr, kj::Own<Alias>> AliasMap; |
| AliasMap aliases; |
| // The "using" declarations. These are just links to nodes elsewhere. |
| |
| // BOOTSTRAP ----------------------------------- |
| |
| NodeTranslator* translator; |
| // Node translator, allocated in the bootstrap arena. |
| |
| kj::Maybe<Schema> bootstrapSchema; |
| // The schema built in the bootstrap loader. Null if the bootstrap loader threw an exception. |
| |
| // FINISHED ------------------------------------ |
| |
| kj::Maybe<schema::Node::Reader> finalSchema; |
| // The completed schema, ready to load into the real schema loader. |
| |
| kj::Array<schema::Node::Reader> auxSchemas; |
| // Schemas for all auxiliary nodes built by the NodeTranslator. |
| |
| kj::Array<schema::Node::SourceInfo::Reader> sourceInfo; |
| // All source info structs as built by the NodeTranslator. |
| }; |
| |
| Content guardedContent; // Read using getContent() only! |
| bool inGetContent = false; // True while getContent() is running; detects cycles. |
| |
| kj::Maybe<schema::Node::Reader> loadedFinalSchema; |
| // Copy of `finalSchema` as loaded into the final schema loader. This doesn't go away if the |
| // workspace is destroyed. |
| |
| // --------------------------------------------- |
| |
| static uint64_t generateId(uint64_t parentId, kj::StringPtr declName, |
| Declaration::Id::Reader declId); |
| // Extract the ID from the declaration, or if it has none, generate one based on the name and |
| // parent ID. |
| |
| static kj::StringPtr joinDisplayName(kj::Arena& arena, Node& parent, kj::StringPtr declName); |
| // Join the parent's display name with the child's unqualified name to construct the child's |
| // display name. |
| |
| kj::Maybe<Content&> getContent(Content::State minimumState); |
| // Advances the content to at least the given state and returns it. Returns null if getContent() |
| // is being called recursively and the given state has not yet been reached, as this indicates |
| // that the declaration recursively depends on itself. |
| |
| void traverseNodeDependencies(const schema::Node::Reader& schemaNode, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo); |
| void traverseType(const schema::Type::Reader& type, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo); |
| void traverseBrand(const schema::Brand::Reader& brand, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo); |
| void traverseAnnotations(const List<schema::Annotation>::Reader& annotations, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo); |
| void traverseDependency(uint64_t depId, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo, |
| bool ignoreIfNotFound = false); |
| // Helpers for traverse(). |
| }; |
| |
| class Compiler::CompiledModule { |
| public: |
| CompiledModule(Compiler::Impl& compiler, Module& parserModule); |
| |
| Compiler::Impl& getCompiler() { return compiler; } |
| |
| ErrorReporter& getErrorReporter() { return parserModule; } |
| ParsedFile::Reader getParsedFile() { return content.getReader(); } |
| Node& getRootNode() { return rootNode; } |
| kj::StringPtr getSourceName() { return parserModule.getSourceName(); } |
| |
| kj::Maybe<CompiledModule&> importRelative(kj::StringPtr importPath); |
| kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr importPath); |
| |
| Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>> |
| getFileImportTable(Orphanage orphanage); |
| |
| private: |
| Compiler::Impl& compiler; |
| Module& parserModule; |
| MallocMessageBuilder contentArena; |
| Orphan<ParsedFile> content; |
| Node rootNode; |
| }; |
| |
| class Compiler::Impl: public SchemaLoader::LazyLoadCallback { |
| public: |
| explicit Impl(AnnotationFlag annotationFlag); |
| virtual ~Impl() noexcept(false); |
| |
| uint64_t add(Module& module); |
| kj::Maybe<uint64_t> lookup(uint64_t parent, kj::StringPtr childName); |
| kj::Maybe<schema::Node::SourceInfo::Reader> getSourceInfo(uint64_t id); |
| Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>> |
| getFileImportTable(Module& module, Orphanage orphanage); |
| Orphan<List<schema::Node::SourceInfo>> getAllSourceInfo(Orphanage orphanage); |
| void eagerlyCompile(uint64_t id, uint eagerness, const SchemaLoader& loader); |
| CompiledModule& addInternal(Module& parsedModule); |
| |
| struct Workspace { |
| // Scratch space where stuff can be allocated while working. The Workspace is available |
| // whenever nodes are actively being compiled, then is destroyed once control exits the |
| // compiler. Note that since nodes are compiled lazily, a new Workspace may have to be |
| // constructed in order to compile more nodes later. |
| |
| MallocMessageBuilder message; |
| Orphanage orphanage; |
| // Orphanage for allocating temporary Cap'n Proto objects. |
| |
| kj::Arena arena; |
| // Arena for allocating temporary native objects. Note that objects in `arena` may contain |
| // pointers into `message` that will be manipulated on destruction, so `arena` must be declared |
| // after `message`. |
| |
| SchemaLoader bootstrapLoader; |
| // Loader used to load bootstrap schemas. The bootstrap schema nodes are similar to the final |
| // versions except that any value expressions which depend on knowledge of other types (e.g. |
| // default values for struct fields) are left unevaluated (the values in the schema are empty). |
| // These bootstrap schemas can then be plugged into the dynamic API and used to evaluate these |
| // remaining values. |
| |
| inline explicit Workspace(const SchemaLoader::LazyLoadCallback& loaderCallback) |
| : orphanage(message.getOrphanage()), |
| bootstrapLoader(loaderCallback) {} |
| }; |
| |
| kj::Arena& getNodeArena() { return nodeArena; } |
| // Arena where nodes and other permanent objects should be allocated. |
| |
| Workspace& getWorkspace() { return workspace; } |
| // Temporary workspace that can be used to construct bootstrap objects. |
| |
| inline bool shouldCompileAnnotations() { |
| return annotationFlag == AnnotationFlag::COMPILE_ANNOTATIONS; |
| } |
| |
| void clearWorkspace(); |
| // Reset the temporary workspace. |
| |
| uint64_t addNode(uint64_t desiredId, Node& node); |
| // Add the given node to the by-ID map under the given ID. If another node with the same ID |
| // already exists, choose a new one arbitrarily and use that instead. Return the ID that was |
| // finally used. |
| |
| kj::Maybe<Node&> findNode(uint64_t id); |
| |
| kj::Maybe<Node&> lookupBuiltin(kj::StringPtr name); |
| Node& getBuiltin(Declaration::Which which); |
| |
| void load(const SchemaLoader& loader, uint64_t id) const override; |
| // SchemaLoader callback for the bootstrap loader. |
| |
| void loadFinal(const SchemaLoader& loader, uint64_t id); |
| // Called from the SchemaLoader callback for the final loader. |
| |
| private: |
| AnnotationFlag annotationFlag; |
| |
| kj::Arena nodeArena; |
| // Arena used to allocate nodes and other permanent objects. |
| |
| std::unordered_map<Module*, kj::Own<CompiledModule>> modules; |
| // Map of parser modules to compiler modules. |
| |
| Workspace workspace; |
| // The temporary workspace. This field must be declared after `modules` because objects |
| // allocated in the workspace may hold references to the compiled modules in `modules`. |
| |
| std::unordered_map<uint64_t, Node*> nodesById; |
| // Map of nodes by ID. |
| |
| std::unordered_map<uint64_t, schema::Node::SourceInfo::Reader> sourceInfoById; |
| // Map of SourceInfos by ID, including SourceInfos for groups and param sturcts (which are not |
| // listed in nodesById). |
| |
| std::map<kj::StringPtr, kj::Own<Node>> builtinDecls; |
| std::map<Declaration::Which, Node*> builtinDeclsByKind; |
| // Map of built-in declarations, like "Int32" and "List", which make up the global scope. |
| |
| uint64_t nextBogusId = 1000; |
| // Counter for assigning bogus IDs to nodes whose real ID is a duplicate. |
| }; |
| |
| // ======================================================================================= |
| |
| kj::Maybe<Resolver::ResolveResult> Compiler::Alias::compile() { |
| if (!initialized) { |
| initialized = true; |
| |
| auto& workspace = module.getCompiler().getWorkspace(); |
| brandOrphan = workspace.orphanage.newOrphan<schema::Brand>(); |
| |
| // If the Workspace is destroyed, revert the alias to the uninitialized state, because the |
| // orphan we created is no longer valid in this case. |
| workspace.arena.copy(kj::defer([this]() { |
| initialized = false; |
| brandOrphan = Orphan<schema::Brand>(); |
| })); |
| |
| target = NodeTranslator::compileDecl( |
| parent.getId(), parent.getParameterCount(), parent, |
| module.getErrorReporter(), targetName, brandOrphan.get()); |
| } |
| |
| return target; |
| } |
| |
| // ======================================================================================= |
| |
| Compiler::Node::Node(CompiledModule& module) |
| : module(&module), |
| parent(nullptr), |
| declaration(module.getParsedFile().getRoot()), |
| id(generateId(0, declaration.getName().getValue(), declaration.getId())), |
| displayName(module.getSourceName()), |
| kind(declaration.which()), |
| genericParamCount(declaration.getParameters().size()), |
| isBuiltin(false) { |
| auto name = declaration.getName(); |
| if (name.getValue().size() > 0) { |
| startByte = name.getStartByte(); |
| endByte = name.getEndByte(); |
| } else { |
| startByte = declaration.getStartByte(); |
| endByte = declaration.getEndByte(); |
| } |
| |
| id = module.getCompiler().addNode(id, *this); |
| } |
| |
| Compiler::Node::Node(Node& parent, const Declaration::Reader& declaration) |
| : module(parent.module), |
| parent(parent), |
| declaration(declaration), |
| id(generateId(parent.id, declaration.getName().getValue(), declaration.getId())), |
| displayName(joinDisplayName(parent.module->getCompiler().getNodeArena(), |
| parent, declaration.getName().getValue())), |
| kind(declaration.which()), |
| genericParamCount(declaration.getParameters().size()), |
| isBuiltin(false) { |
| auto name = declaration.getName(); |
| if (name.getValue().size() > 0) { |
| startByte = name.getStartByte(); |
| endByte = name.getEndByte(); |
| } else { |
| startByte = declaration.getStartByte(); |
| endByte = declaration.getEndByte(); |
| } |
| |
| id = module->getCompiler().addNode(id, *this); |
| } |
| |
| Compiler::Node::Node(kj::StringPtr name, Declaration::Which kind, |
| List<Declaration::BrandParameter>::Reader genericParams) |
| : module(nullptr), |
| parent(nullptr), |
| // It's helpful if these have unique IDs. Real type IDs can't be under 2^31 anyway. |
| id(1000 + static_cast<uint>(kind)), |
| displayName(name), |
| kind(kind), |
| genericParamCount(genericParams.size()), |
| isBuiltin(true), |
| startByte(0), |
| endByte(0) {} |
| |
| uint64_t Compiler::Node::generateId(uint64_t parentId, kj::StringPtr declName, |
| Declaration::Id::Reader declId) { |
| if (declId.isUid()) { |
| return declId.getUid().getValue(); |
| } |
| |
| return generateChildId(parentId, declName); |
| } |
| |
| kj::StringPtr Compiler::Node::joinDisplayName( |
| kj::Arena& arena, Node& parent, kj::StringPtr declName) { |
| kj::ArrayPtr<char> result = arena.allocateArray<char>( |
| parent.displayName.size() + declName.size() + 2); |
| |
| size_t separatorPos = parent.displayName.size(); |
| memcpy(result.begin(), parent.displayName.begin(), separatorPos); |
| result[separatorPos] = parent.parent == nullptr ? ':' : '.'; |
| memcpy(result.begin() + separatorPos + 1, declName.begin(), declName.size()); |
| result[result.size() - 1] = '\0'; |
| return kj::StringPtr(result.begin(), result.size() - 1); |
| } |
| |
| kj::Maybe<Compiler::Node::Content&> Compiler::Node::getContent(Content::State minimumState) { |
| KJ_REQUIRE(!isBuiltin, "illegal method call for built-in declaration"); |
| |
| auto& content = guardedContent; |
| |
| if (content.stateHasReached(minimumState)) { |
| return content; |
| } |
| |
| if (inGetContent) { |
| addError("Declaration recursively depends on itself."); |
| return nullptr; |
| } |
| |
| inGetContent = true; |
| KJ_DEFER(inGetContent = false); |
| |
| switch (content.state) { |
| case Content::STUB: { |
| if (minimumState <= Content::STUB) break; |
| |
| // Expand the child nodes. |
| auto& arena = module->getCompiler().getNodeArena(); |
| |
| for (auto nestedDecl: declaration.getNestedDecls()) { |
| switch (nestedDecl.which()) { |
| case Declaration::FILE: |
| case Declaration::CONST: |
| case Declaration::ANNOTATION: |
| case Declaration::ENUM: |
| case Declaration::STRUCT: |
| case Declaration::INTERFACE: { |
| kj::Own<Node> subNode = arena.allocateOwn<Node>(*this, nestedDecl); |
| kj::StringPtr name = nestedDecl.getName().getValue(); |
| content.orderedNestedNodes.add(subNode); |
| content.nestedNodes.insert(std::make_pair(name, kj::mv(subNode))); |
| break; |
| } |
| |
| case Declaration::USING: { |
| kj::Own<Alias> alias = arena.allocateOwn<Alias>( |
| *module, *this, nestedDecl.getUsing().getTarget()); |
| kj::StringPtr name = nestedDecl.getName().getValue(); |
| content.aliases.insert(std::make_pair(name, kj::mv(alias))); |
| break; |
| } |
| case Declaration::ENUMERANT: |
| case Declaration::FIELD: |
| case Declaration::UNION: |
| case Declaration::GROUP: |
| case Declaration::METHOD: |
| case Declaration::NAKED_ID: |
| case Declaration::NAKED_ANNOTATION: |
| // Not a node. Skip. |
| break; |
| default: |
| KJ_FAIL_ASSERT("unknown declaration type", nestedDecl); |
| break; |
| } |
| } |
| |
| content.advanceState(Content::EXPANDED); |
| } KJ_FALLTHROUGH; |
| |
| case Content::EXPANDED: { |
| if (minimumState <= Content::EXPANDED) break; |
| |
| // Construct the NodeTranslator. |
| auto& workspace = module->getCompiler().getWorkspace(); |
| |
| auto schemaNode = workspace.orphanage.newOrphan<schema::Node>(); |
| auto builder = schemaNode.get(); |
| builder.setId(id); |
| builder.setDisplayName(displayName); |
| // TODO(cleanup): Would be better if we could remember the prefix length from before we |
| // added this decl's name to the end. |
| KJ_IF_MAYBE(lastDot, displayName.findLast('.')) { |
| builder.setDisplayNamePrefixLength(*lastDot + 1); |
| } |
| KJ_IF_MAYBE(lastColon, displayName.findLast(':')) { |
| if (*lastColon > builder.getDisplayNamePrefixLength()) { |
| builder.setDisplayNamePrefixLength(*lastColon + 1); |
| } |
| } |
| KJ_IF_MAYBE(p, parent) { |
| builder.setScopeId(p->id); |
| } |
| |
| auto nestedNodes = builder.initNestedNodes(content.orderedNestedNodes.size()); |
| auto nestedIter = nestedNodes.begin(); |
| for (auto node: content.orderedNestedNodes) { |
| nestedIter->setName(node->declaration.getName().getValue()); |
| nestedIter->setId(node->id); |
| ++nestedIter; |
| } |
| |
| content.translator = &workspace.arena.allocate<NodeTranslator>( |
| *this, module->getErrorReporter(), declaration, kj::mv(schemaNode), |
| module->getCompiler().shouldCompileAnnotations()); |
| KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&](){ |
| auto nodeSet = content.translator->getBootstrapNode(); |
| for (auto& auxNode: nodeSet.auxNodes) { |
| workspace.bootstrapLoader.loadOnce(auxNode); |
| } |
| content.bootstrapSchema = workspace.bootstrapLoader.loadOnce(nodeSet.node); |
| })) { |
| content.bootstrapSchema = nullptr; |
| // Only bother to report validation failures if we think we haven't seen any errors. |
| // Otherwise we assume that the errors caused the validation failure. |
| if (!module->getErrorReporter().hadErrors()) { |
| addError(kj::str("Internal compiler bug: Bootstrap schema failed validation:\n", |
| *exception)); |
| } |
| } |
| |
| // If the Workspace is destroyed, revert the node to the EXPANDED state, because the |
| // NodeTranslator is no longer valid in this case. |
| workspace.arena.copy(kj::defer([&content]() { |
| content.bootstrapSchema = nullptr; |
| if (content.state > Content::EXPANDED) { |
| content.state = Content::EXPANDED; |
| } |
| })); |
| |
| content.advanceState(Content::BOOTSTRAP); |
| } KJ_FALLTHROUGH; |
| |
| case Content::BOOTSTRAP: { |
| if (minimumState <= Content::BOOTSTRAP) break; |
| |
| // Create the final schema. |
| NodeTranslator::NodeSet nodeSet; |
| if (content.bootstrapSchema == nullptr) { |
| // Must have failed in an earlier stage. |
| KJ_ASSERT(module->getErrorReporter().hadErrors()); |
| nodeSet = content.translator->getBootstrapNode(); |
| } else { |
| nodeSet = content.translator->finish( |
| module->getCompiler().getWorkspace().bootstrapLoader.getUnbound(id)); |
| } |
| |
| content.finalSchema = nodeSet.node; |
| content.auxSchemas = kj::mv(nodeSet.auxNodes); |
| content.sourceInfo = kj::mv(nodeSet.sourceInfo); |
| |
| content.advanceState(Content::FINISHED); |
| } KJ_FALLTHROUGH; |
| |
| case Content::FINISHED: |
| break; |
| } |
| |
| return content; |
| } |
| |
| kj::Maybe<Schema> Compiler::Node::getBootstrapSchema() { |
| KJ_IF_MAYBE(schema, loadedFinalSchema) { |
| // We don't need to rebuild the bootstrap schema if we already have a final schema. |
| return module->getCompiler().getWorkspace().bootstrapLoader.loadOnce(*schema); |
| } else KJ_IF_MAYBE(content, getContent(Content::BOOTSTRAP)) { |
| if (content->state == Content::FINISHED && content->bootstrapSchema == nullptr) { |
| // The bootstrap schema was discarded. Copy it from the final schema. |
| // (We can't just return the final schema because using it could trigger schema loader |
| // callbacks that would deadlock.) |
| KJ_IF_MAYBE(finalSchema, content->finalSchema) { |
| return module->getCompiler().getWorkspace().bootstrapLoader.loadOnce(*finalSchema); |
| } else { |
| return nullptr; |
| } |
| } else { |
| return content->bootstrapSchema; |
| } |
| } else { |
| return nullptr; |
| } |
| } |
| kj::Maybe<schema::Node::Reader> Compiler::Node::getFinalSchema() { |
| KJ_IF_MAYBE(schema, loadedFinalSchema) { |
| return *schema; |
| } else KJ_IF_MAYBE(content, getContent(Content::FINISHED)) { |
| return content->finalSchema; |
| } else { |
| return nullptr; |
| } |
| } |
| void Compiler::Node::loadFinalSchema(const SchemaLoader& loader) { |
| KJ_IF_MAYBE(content, getContent(Content::FINISHED)) { |
| KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&](){ |
| KJ_IF_MAYBE(finalSchema, content->finalSchema) { |
| KJ_MAP(auxSchema, content->auxSchemas) { |
| return loader.loadOnce(auxSchema); |
| }; |
| loadedFinalSchema = loader.loadOnce(*finalSchema).getProto(); |
| } |
| })) { |
| // Schema validation threw an exception. |
| |
| // Don't try loading this again. |
| content->finalSchema = nullptr; |
| |
| // Only bother to report validation failures if we think we haven't seen any errors. |
| // Otherwise we assume that the errors caused the validation failure. |
| if (!module->getErrorReporter().hadErrors()) { |
| addError(kj::str("Internal compiler bug: Schema failed validation:\n", *exception)); |
| } |
| } |
| } |
| } |
| |
| void Compiler::Node::traverse(uint eagerness, std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo) { |
| uint& slot = seen[this]; |
| if ((slot & eagerness) == eagerness) { |
| // We've already covered this node. |
| return; |
| } |
| slot |= eagerness; |
| |
| KJ_IF_MAYBE(content, getContent(Content::FINISHED)) { |
| loadFinalSchema(finalLoader); |
| |
| KJ_IF_MAYBE(schema, getFinalSchema()) { |
| if (eagerness / DEPENDENCIES != 0) { |
| // For traversing dependencies, discard the bits lower than DEPENDENCIES and replace |
| // them with the bits above DEPENDENCIES shifted over. |
| uint newEagerness = (eagerness & ~(DEPENDENCIES - 1)) | (eagerness / DEPENDENCIES); |
| |
| traverseNodeDependencies(*schema, newEagerness, seen, finalLoader, sourceInfo); |
| for (auto& aux: content->auxSchemas) { |
| traverseNodeDependencies(aux, newEagerness, seen, finalLoader, sourceInfo); |
| } |
| } |
| } |
| |
| sourceInfo.addAll(content->sourceInfo); |
| } |
| |
| if (eagerness & PARENTS) { |
| KJ_IF_MAYBE(p, parent) { |
| p->traverse(eagerness, seen, finalLoader, sourceInfo); |
| } |
| } |
| |
| if (eagerness & CHILDREN) { |
| KJ_IF_MAYBE(content, getContent(Content::EXPANDED)) { |
| for (auto& child: content->orderedNestedNodes) { |
| child->traverse(eagerness, seen, finalLoader, sourceInfo); |
| } |
| |
| // Also traverse `using` declarations. |
| for (auto& child: content->aliases) { |
| child.second->compile(); |
| } |
| } |
| } |
| } |
| |
| void Compiler::Node::traverseNodeDependencies( |
| const schema::Node::Reader& schemaNode, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo) { |
| switch (schemaNode.which()) { |
| case schema::Node::STRUCT: |
| for (auto field: schemaNode.getStruct().getFields()) { |
| switch (field.which()) { |
| case schema::Field::SLOT: |
| traverseType(field.getSlot().getType(), eagerness, seen, finalLoader, sourceInfo); |
| break; |
| case schema::Field::GROUP: |
| // Aux node will be scanned later. |
| break; |
| } |
| |
| traverseAnnotations(field.getAnnotations(), eagerness, seen, finalLoader, sourceInfo); |
| } |
| break; |
| |
| case schema::Node::ENUM: |
| for (auto enumerant: schemaNode.getEnum().getEnumerants()) { |
| traverseAnnotations(enumerant.getAnnotations(), eagerness, seen, finalLoader, sourceInfo); |
| } |
| break; |
| |
| case schema::Node::INTERFACE: { |
| auto interface = schemaNode.getInterface(); |
| for (auto superclass: interface.getSuperclasses()) { |
| uint64_t superclassId = superclass.getId(); |
| if (superclassId != 0) { // if zero, we reported an error earlier |
| traverseDependency(superclassId, eagerness, seen, finalLoader, sourceInfo); |
| } |
| traverseBrand(superclass.getBrand(), eagerness, seen, finalLoader, sourceInfo); |
| } |
| for (auto method: interface.getMethods()) { |
| traverseDependency( |
| method.getParamStructType(), eagerness, seen, finalLoader, sourceInfo, true); |
| traverseBrand(method.getParamBrand(), eagerness, seen, finalLoader, sourceInfo); |
| traverseDependency( |
| method.getResultStructType(), eagerness, seen, finalLoader, sourceInfo, true); |
| traverseBrand(method.getResultBrand(), eagerness, seen, finalLoader, sourceInfo); |
| traverseAnnotations(method.getAnnotations(), eagerness, seen, finalLoader, sourceInfo); |
| } |
| break; |
| } |
| |
| case schema::Node::CONST: |
| traverseType(schemaNode.getConst().getType(), eagerness, seen, finalLoader, sourceInfo); |
| break; |
| |
| case schema::Node::ANNOTATION: |
| traverseType(schemaNode.getAnnotation().getType(), eagerness, seen, finalLoader, sourceInfo); |
| break; |
| |
| default: |
| break; |
| } |
| |
| traverseAnnotations(schemaNode.getAnnotations(), eagerness, seen, finalLoader, sourceInfo); |
| } |
| |
| void Compiler::Node::traverseType(const schema::Type::Reader& type, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo) { |
| uint64_t id = 0; |
| schema::Brand::Reader brand; |
| switch (type.which()) { |
| case schema::Type::STRUCT: |
| id = type.getStruct().getTypeId(); |
| brand = type.getStruct().getBrand(); |
| break; |
| case schema::Type::ENUM: |
| id = type.getEnum().getTypeId(); |
| brand = type.getEnum().getBrand(); |
| break; |
| case schema::Type::INTERFACE: |
| id = type.getInterface().getTypeId(); |
| brand = type.getInterface().getBrand(); |
| break; |
| case schema::Type::LIST: |
| traverseType(type.getList().getElementType(), eagerness, seen, finalLoader, sourceInfo); |
| return; |
| default: |
| return; |
| } |
| |
| traverseDependency(id, eagerness, seen, finalLoader, sourceInfo); |
| traverseBrand(brand, eagerness, seen, finalLoader, sourceInfo); |
| } |
| |
| void Compiler::Node::traverseBrand( |
| const schema::Brand::Reader& brand, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo) { |
| for (auto scope: brand.getScopes()) { |
| switch (scope.which()) { |
| case schema::Brand::Scope::BIND: |
| for (auto binding: scope.getBind()) { |
| switch (binding.which()) { |
| case schema::Brand::Binding::UNBOUND: |
| break; |
| case schema::Brand::Binding::TYPE: |
| traverseType(binding.getType(), eagerness, seen, finalLoader, sourceInfo); |
| break; |
| } |
| } |
| break; |
| case schema::Brand::Scope::INHERIT: |
| break; |
| } |
| } |
| } |
| |
| void Compiler::Node::traverseDependency(uint64_t depId, uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo, |
| bool ignoreIfNotFound) { |
| KJ_IF_MAYBE(node, module->getCompiler().findNode(depId)) { |
| node->traverse(eagerness, seen, finalLoader, sourceInfo); |
| } else if (!ignoreIfNotFound) { |
| KJ_FAIL_ASSERT("Dependency ID not present in compiler?", depId); |
| } |
| } |
| |
| void Compiler::Node::traverseAnnotations(const List<schema::Annotation>::Reader& annotations, |
| uint eagerness, |
| std::unordered_map<Node*, uint>& seen, |
| const SchemaLoader& finalLoader, |
| kj::Vector<schema::Node::SourceInfo::Reader>& sourceInfo) { |
| for (auto annotation: annotations) { |
| KJ_IF_MAYBE(node, module->getCompiler().findNode(annotation.getId())) { |
| node->traverse(eagerness, seen, finalLoader, sourceInfo); |
| } |
| } |
| } |
| |
| |
| void Compiler::Node::addError(kj::StringPtr error) { |
| module->getErrorReporter().addError(startByte, endByte, error); |
| } |
| |
| kj::Maybe<Resolver::ResolveResult> |
| Compiler::Node::resolve(kj::StringPtr name) { |
| // Check members. |
| KJ_IF_MAYBE(member, resolveMember(name)) { |
| return *member; |
| } |
| |
| // Check parameters. |
| // TODO(perf): Maintain a map? |
| auto params = declaration.getParameters(); |
| for (uint i: kj::indices(params)) { |
| if (params[i].getName() == name) { |
| ResolveResult result; |
| result.init<ResolvedParameter>(ResolvedParameter {id, i}); |
| return result; |
| } |
| } |
| |
| // Check parent scope. |
| KJ_IF_MAYBE(p, parent) { |
| return p->resolve(name); |
| } else KJ_IF_MAYBE(b, module->getCompiler().lookupBuiltin(name)) { |
| ResolveResult result; |
| result.init<ResolvedDecl>(ResolvedDecl { b->id, b->genericParamCount, 0, b->kind, b, nullptr }); |
| return result; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| kj::Maybe<Resolver::ResolveResult> |
| Compiler::Node::resolveMember(kj::StringPtr name) { |
| if (isBuiltin) return nullptr; |
| |
| KJ_IF_MAYBE(content, getContent(Content::EXPANDED)) { |
| { |
| auto iter = content->nestedNodes.find(name); |
| if (iter != content->nestedNodes.end()) { |
| Node* node = iter->second; |
| ResolveResult result; |
| result.init<ResolvedDecl>(ResolvedDecl { |
| node->id, node->genericParamCount, id, node->kind, node, nullptr }); |
| return result; |
| } |
| } |
| { |
| auto iter = content->aliases.find(name); |
| if (iter != content->aliases.end()) { |
| return iter->second->compile(); |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| Resolver::ResolvedDecl Compiler::Node::resolveBuiltin(Declaration::Which which) { |
| auto& b = module->getCompiler().getBuiltin(which); |
| return { b.id, b.genericParamCount, 0, b.kind, &b, nullptr }; |
| } |
| |
| Resolver::ResolvedDecl Compiler::Node::resolveId(uint64_t id) { |
| auto& n = KJ_ASSERT_NONNULL(module->getCompiler().findNode(id)); |
| uint64_t parentId = n.parent.map([](Node& n) { return n.id; }).orDefault(0); |
| return { n.id, n.genericParamCount, parentId, n.kind, &n, nullptr }; |
| } |
| |
| kj::Maybe<Resolver::ResolvedDecl> Compiler::Node::getParent() { |
| return parent.map([](Node& parent) { |
| uint64_t scopeId = parent.parent.map([](Node& gp) { return gp.id; }).orDefault(0); |
| return ResolvedDecl { parent.id, parent.genericParamCount, scopeId, parent.kind, &parent, nullptr }; |
| }); |
| } |
| |
| Resolver::ResolvedDecl Compiler::Node::getTopScope() { |
| Node& node = module->getRootNode(); |
| return ResolvedDecl { node.id, 0, 0, node.kind, &node, nullptr }; |
| } |
| |
| kj::Maybe<Schema> Compiler::Node::resolveBootstrapSchema( |
| uint64_t id, schema::Brand::Reader brand) { |
| KJ_IF_MAYBE(node, module->getCompiler().findNode(id)) { |
| // Make sure the bootstrap schema is loaded into the SchemaLoader. |
| if (node->getBootstrapSchema() == nullptr) { |
| return nullptr; |
| } |
| |
| // Now we actually invoke get() to evaluate the brand. |
| return module->getCompiler().getWorkspace().bootstrapLoader.get(id, brand); |
| } else { |
| KJ_FAIL_REQUIRE("Tried to get schema for ID we haven't seen before."); |
| } |
| } |
| |
| kj::Maybe<schema::Node::Reader> Compiler::Node::resolveFinalSchema(uint64_t id) { |
| KJ_IF_MAYBE(node, module->getCompiler().findNode(id)) { |
| return node->getFinalSchema(); |
| } else { |
| KJ_FAIL_REQUIRE("Tried to get schema for ID we haven't seen before."); |
| } |
| } |
| |
| kj::Maybe<Resolver::ResolvedDecl> |
| Compiler::Node::resolveImport(kj::StringPtr name) { |
| KJ_IF_MAYBE(m, module->importRelative(name)) { |
| Node& root = m->getRootNode(); |
| return ResolvedDecl { root.id, 0, 0, root.kind, &root, nullptr }; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| kj::Maybe<kj::Array<const byte>> Compiler::Node::readEmbed(kj::StringPtr name) { |
| return module->embedRelative(name); |
| } |
| |
| kj::Maybe<Type> Compiler::Node::resolveBootstrapType(schema::Type::Reader type, Schema scope) { |
| // TODO(someday): Arguably should return null if the type or its dependencies are placeholders. |
| |
| kj::Maybe<Type> result; |
| KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { |
| result = module->getCompiler().getWorkspace().bootstrapLoader.getType(type, scope); |
| })) { |
| result = nullptr; |
| if (!module->getErrorReporter().hadErrors()) { |
| addError(kj::str("Internal compiler bug: Bootstrap schema failed to load:\n", |
| *exception)); |
| } |
| } |
| return result; |
| } |
| |
| // ======================================================================================= |
| |
| Compiler::CompiledModule::CompiledModule(Compiler::Impl& compiler, Module& parserModule) |
| : compiler(compiler), parserModule(parserModule), |
| content(parserModule.loadContent(contentArena.getOrphanage())), |
| rootNode(*this) {} |
| |
| kj::Maybe<Compiler::CompiledModule&> Compiler::CompiledModule::importRelative( |
| kj::StringPtr importPath) { |
| return parserModule.importRelative(importPath).map( |
| [this](Module& module) -> Compiler::CompiledModule& { |
| return compiler.addInternal(module); |
| }); |
| } |
| |
| kj::Maybe<kj::Array<const byte>> Compiler::CompiledModule::embedRelative(kj::StringPtr embedPath) { |
| return parserModule.embedRelative(embedPath); |
| } |
| |
| static void findImports(Expression::Reader exp, std::set<kj::StringPtr>& output) { |
| switch (exp.which()) { |
| case Expression::UNKNOWN: |
| case Expression::POSITIVE_INT: |
| case Expression::NEGATIVE_INT: |
| case Expression::FLOAT: |
| case Expression::STRING: |
| case Expression::BINARY: |
| case Expression::RELATIVE_NAME: |
| case Expression::ABSOLUTE_NAME: |
| case Expression::EMBED: |
| break; |
| |
| case Expression::IMPORT: |
| output.insert(exp.getImport().getValue()); |
| break; |
| |
| case Expression::LIST: |
| for (auto element: exp.getList()) { |
| findImports(element, output); |
| } |
| break; |
| |
| case Expression::TUPLE: |
| for (auto element: exp.getTuple()) { |
| findImports(element.getValue(), output); |
| } |
| break; |
| |
| case Expression::APPLICATION: { |
| auto app = exp.getApplication(); |
| findImports(app.getFunction(), output); |
| for (auto param: app.getParams()) { |
| findImports(param.getValue(), output); |
| } |
| break; |
| } |
| |
| case Expression::MEMBER: { |
| findImports(exp.getMember().getParent(), output); |
| break; |
| } |
| } |
| } |
| |
| static void findImports(Declaration::ParamList::Reader paramList, std::set<kj::StringPtr>& output) { |
| switch (paramList.which()) { |
| case Declaration::ParamList::NAMED_LIST: |
| for (auto param: paramList.getNamedList()) { |
| findImports(param.getType(), output); |
| for (auto ann: param.getAnnotations()) { |
| findImports(ann.getName(), output); |
| } |
| } |
| break; |
| case Declaration::ParamList::TYPE: |
| findImports(paramList.getType(), output); |
| break; |
| case Declaration::ParamList::STREAM: |
| output.insert("/capnp/stream.capnp"); |
| break; |
| } |
| } |
| |
| static void findImports(Declaration::Reader decl, std::set<kj::StringPtr>& output) { |
| switch (decl.which()) { |
| case Declaration::USING: |
| findImports(decl.getUsing().getTarget(), output); |
| break; |
| case Declaration::CONST: |
| findImports(decl.getConst().getType(), output); |
| break; |
| case Declaration::FIELD: |
| findImports(decl.getField().getType(), output); |
| break; |
| case Declaration::INTERFACE: |
| for (auto superclass: decl.getInterface().getSuperclasses()) { |
| findImports(superclass, output); |
| } |
| break; |
| case Declaration::METHOD: { |
| auto method = decl.getMethod(); |
| |
| findImports(method.getParams(), output); |
| if (method.getResults().isExplicit()) { |
| findImports(method.getResults().getExplicit(), output); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| for (auto ann: decl.getAnnotations()) { |
| findImports(ann.getName(), output); |
| } |
| |
| for (auto nested: decl.getNestedDecls()) { |
| findImports(nested, output); |
| } |
| } |
| |
| Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>> |
| Compiler::CompiledModule::getFileImportTable(Orphanage orphanage) { |
| // Build a table of imports for CodeGeneratorRequest.RequestedFile.imports. Note that we only |
| // care about type imports, not constant value imports, since constant values (including default |
| // values) are already embedded in full in the schema. In other words, we only need the imports |
| // that would need to be #included in the generated code. |
| |
| std::set<kj::StringPtr> importNames; |
| findImports(content.getReader().getRoot(), importNames); |
| |
| auto result = orphanage.newOrphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>( |
| importNames.size()); |
| auto builder = result.get(); |
| |
| uint i = 0; |
| for (auto name: importNames) { |
| // We presumably ran this import before, so it shouldn't throw now. |
| auto entry = builder[i++]; |
| entry.setId(KJ_ASSERT_NONNULL(importRelative(name)).rootNode.getId()); |
| entry.setName(name); |
| } |
| |
| return result; |
| } |
| |
| // ======================================================================================= |
| |
| Compiler::Impl::Impl(AnnotationFlag annotationFlag) |
| : annotationFlag(annotationFlag), workspace(*this) { |
| // Reflectively interpret the members of Declaration.body. Any member prefixed by "builtin" |
| // defines a builtin declaration visible in the global scope. |
| |
| StructSchema declSchema = Schema::from<Declaration>(); |
| for (auto field: declSchema.getFields()) { |
| auto fieldProto = field.getProto(); |
| if (fieldProto.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT) { |
| auto name = fieldProto.getName(); |
| if (name.startsWith("builtin")) { |
| kj::StringPtr symbolName = name.slice(strlen("builtin")); |
| |
| List<Declaration::BrandParameter>::Reader params; |
| for (auto annotation: fieldProto.getAnnotations()) { |
| if (annotation.getId() == 0x94099c3f9eb32d6bull) { |
| params = annotation.getValue().getList().getAs<List<Declaration::BrandParameter>>(); |
| break; |
| } |
| } |
| |
| Declaration::Which which = |
| static_cast<Declaration::Which>(fieldProto.getDiscriminantValue()); |
| kj::Own<Node> newNode = nodeArena.allocateOwn<Node>(symbolName, which, params); |
| builtinDeclsByKind[which] = newNode; |
| builtinDecls[symbolName] = kj::mv(newNode); |
| } |
| } |
| } |
| } |
| |
| Compiler::Impl::~Impl() noexcept(false) {} |
| |
| void Compiler::Impl::clearWorkspace() { |
| // Make sure we reconstruct the workspace even if destroying it throws an exception. |
| KJ_DEFER(kj::ctor(workspace, *this)); |
| kj::dtor(workspace); |
| } |
| |
| Compiler::CompiledModule& Compiler::Impl::addInternal(Module& parsedModule) { |
| kj::Own<CompiledModule>& slot = modules[&parsedModule]; |
| if (slot.get() == nullptr) { |
| slot = kj::heap<CompiledModule>(*this, parsedModule); |
| } |
| |
| return *slot; |
| } |
| |
| uint64_t Compiler::Impl::addNode(uint64_t desiredId, Node& node) { |
| for (;;) { |
| auto insertResult = nodesById.insert(std::make_pair(desiredId, &node)); |
| if (insertResult.second) { |
| return desiredId; |
| } |
| |
| // Only report an error if this ID is not bogus. Actual IDs specified in the original source |
| // code are required to have the upper bit set. Anything else must have been manufactured |
| // at some point to cover up an error. |
| if (desiredId & (1ull << 63)) { |
| node.addError(kj::str("Duplicate ID @0x", kj::hex(desiredId), ".")); |
| insertResult.first->second->addError( |
| kj::str("ID @0x", kj::hex(desiredId), " originally used here.")); |
| } |
| |
| // Assign a new bogus ID. |
| desiredId = nextBogusId++; |
| } |
| } |
| |
| kj::Maybe<Compiler::Node&> Compiler::Impl::findNode(uint64_t id) { |
| auto iter = nodesById.find(id); |
| if (iter == nodesById.end()) { |
| return nullptr; |
| } else { |
| return *iter->second; |
| } |
| } |
| |
| kj::Maybe<Compiler::Node&> Compiler::Impl::lookupBuiltin(kj::StringPtr name) { |
| auto iter = builtinDecls.find(name); |
| if (iter == builtinDecls.end()) { |
| return nullptr; |
| } else { |
| return *iter->second; |
| } |
| } |
| |
| Compiler::Node& Compiler::Impl::getBuiltin(Declaration::Which which) { |
| auto iter = builtinDeclsByKind.find(which); |
| KJ_REQUIRE(iter != builtinDeclsByKind.end(), "invalid builtin", (uint)which); |
| return *iter->second; |
| } |
| |
| kj::Maybe<uint64_t> Compiler::Impl::lookup(uint64_t parent, kj::StringPtr childName) { |
| // Looking up members does not use the workspace, so we don't need to lock it. |
| KJ_IF_MAYBE(parentNode, findNode(parent)) { |
| KJ_IF_MAYBE(child, parentNode->resolveMember(childName)) { |
| if (child->is<Resolver::ResolvedDecl>()) { |
| return child->get<Resolver::ResolvedDecl>().id; |
| } else { |
| // An alias. We don't support looking up aliases with this method. |
| return nullptr; |
| } |
| } else { |
| return nullptr; |
| } |
| } else { |
| KJ_FAIL_REQUIRE("lookup()s parameter 'parent' must be a known ID.", parent); |
| } |
| } |
| |
| kj::Maybe<schema::Node::SourceInfo::Reader> Compiler::Impl::getSourceInfo(uint64_t id) { |
| auto iter = sourceInfoById.find(id); |
| if (iter == sourceInfoById.end()) { |
| return nullptr; |
| } else { |
| return iter->second; |
| } |
| } |
| |
| Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>> |
| Compiler::Impl::getFileImportTable(Module& module, Orphanage orphanage) { |
| return addInternal(module).getFileImportTable(orphanage); |
| } |
| |
| Orphan<List<schema::Node::SourceInfo>> Compiler::Impl::getAllSourceInfo(Orphanage orphanage) { |
| auto result = orphanage.newOrphan<List<schema::Node::SourceInfo>>(sourceInfoById.size()); |
| |
| auto builder = result.get(); |
| size_t i = 0; |
| for (auto& entry: sourceInfoById) { |
| builder.setWithCaveats(i++, entry.second); |
| } |
| |
| return result; |
| } |
| |
| void Compiler::Impl::eagerlyCompile(uint64_t id, uint eagerness, |
| const SchemaLoader& finalLoader) { |
| KJ_IF_MAYBE(node, findNode(id)) { |
| std::unordered_map<Node*, uint> seen; |
| kj::Vector<schema::Node::SourceInfo::Reader> sourceInfos; |
| node->traverse(eagerness, seen, finalLoader, sourceInfos); |
| |
| // Copy the SourceInfo structures into permanent space so that they aren't invalidated when |
| // clearWorkspace() is called. |
| for (auto& sourceInfo: sourceInfos) { |
| auto words = nodeArena.allocateArray<word>(sourceInfo.totalSize().wordCount + 1); |
| memset(words.begin(), 0, words.asBytes().size()); |
| copyToUnchecked(sourceInfo, words); |
| sourceInfoById.insert(std::make_pair(sourceInfo.getId(), |
| readMessageUnchecked<schema::Node::SourceInfo>(words.begin()))); |
| } |
| } else { |
| KJ_FAIL_REQUIRE("id did not come from this Compiler.", id); |
| } |
| } |
| |
| void Compiler::Impl::load(const SchemaLoader& loader, uint64_t id) const { |
| // We know that this load() is only called from the bootstrap loader which is already protected |
| // by our mutex, so we can drop thread-safety. |
| auto& self = const_cast<Compiler::Impl&>(*this); |
| |
| KJ_IF_MAYBE(node, self.findNode(id)) { |
| node->getBootstrapSchema(); |
| } |
| } |
| |
| void Compiler::Impl::loadFinal(const SchemaLoader& loader, uint64_t id) { |
| KJ_IF_MAYBE(node, findNode(id)) { |
| node->loadFinalSchema(loader); |
| } |
| } |
| |
| // ======================================================================================= |
| |
| Compiler::Compiler(AnnotationFlag annotationFlag) |
| : impl(kj::heap<Impl>(annotationFlag)), |
| loader(*this) {} |
| Compiler::~Compiler() noexcept(false) {} |
| |
| Compiler::ModuleScope Compiler::add(Module& module) const { |
| Node& root = impl.lockExclusive()->get()->addInternal(module).getRootNode(); |
| return ModuleScope(*this, root.getId(), root); |
| } |
| |
| kj::Maybe<uint64_t> Compiler::lookup(uint64_t parent, kj::StringPtr childName) const { |
| return impl.lockExclusive()->get()->lookup(parent, childName); |
| } |
| |
| kj::Maybe<schema::Node::SourceInfo::Reader> Compiler::getSourceInfo(uint64_t id) const { |
| return impl.lockExclusive()->get()->getSourceInfo(id); |
| } |
| |
| Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>> |
| Compiler::getFileImportTable(Module& module, Orphanage orphanage) const { |
| return impl.lockExclusive()->get()->getFileImportTable(module, orphanage); |
| } |
| |
| Orphan<List<schema::Node::SourceInfo>> Compiler::getAllSourceInfo(Orphanage orphanage) const { |
| return impl.lockExclusive()->get()->getAllSourceInfo(orphanage); |
| } |
| |
| void Compiler::eagerlyCompile(uint64_t id, uint eagerness) const { |
| impl.lockExclusive()->get()->eagerlyCompile(id, eagerness, loader); |
| } |
| |
| void Compiler::clearWorkspace() const { |
| impl.lockExclusive()->get()->clearWorkspace(); |
| } |
| |
| void Compiler::load(const SchemaLoader& loader, uint64_t id) const { |
| impl.lockExclusive()->get()->loadFinal(loader, id); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| class Compiler::ErrorIgnorer: public ErrorReporter { |
| public: |
| void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {} |
| bool hadErrors() override { return false; } |
| |
| static ErrorIgnorer instance; |
| }; |
| Compiler::ErrorIgnorer Compiler::ErrorIgnorer::instance; |
| |
| kj::Maybe<Type> Compiler::CompiledType::getSchema() { |
| capnp::word scratch[32]; |
| memset(&scratch, 0, sizeof(scratch)); |
| capnp::MallocMessageBuilder message(scratch); |
| auto builder = message.getRoot<schema::Type>(); |
| |
| { |
| auto lock = compiler.impl.lockShared(); |
| decl.get(lock).compileAsType(ErrorIgnorer::instance, builder); |
| } |
| |
| // No need to pass `scope` as second parameter since CompiledType always represents a type |
| // expression evaluated free-standing, not in any scope. |
| return compiler.loader.getType(builder.asReader()); |
| } |
| |
| Compiler::CompiledType Compiler::CompiledType::clone() { |
| kj::ExternalMutexGuarded<BrandedDecl> newDecl; |
| { |
| auto lock = compiler.impl.lockExclusive(); |
| newDecl.set(lock, kj::cp(decl.get(lock))); |
| } |
| return CompiledType(compiler, kj::mv(newDecl)); |
| } |
| |
| kj::Maybe<Compiler::CompiledType> Compiler::CompiledType::getMember(kj::StringPtr name) { |
| kj::ExternalMutexGuarded<BrandedDecl> newDecl; |
| bool found = false; |
| |
| { |
| auto lock = compiler.impl.lockShared(); |
| KJ_IF_MAYBE(member, decl.get(lock).getMember(name, {})) { |
| newDecl.set(lock, kj::mv(*member)); |
| found = true; |
| } |
| } |
| |
| if (found) { |
| return CompiledType(compiler, kj::mv(newDecl)); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| kj::Maybe<Compiler::CompiledType> Compiler::CompiledType::applyBrand( |
| kj::Array<CompiledType> arguments) { |
| kj::ExternalMutexGuarded<BrandedDecl> newDecl; |
| bool found = false; |
| |
| { |
| auto lock = compiler.impl.lockShared(); |
| auto args = KJ_MAP(arg, arguments) { return kj::mv(arg.decl.get(lock)); }; |
| KJ_IF_MAYBE(member, decl.get(lock).applyParams(kj::mv(args), {})) { |
| newDecl.set(lock, kj::mv(*member)); |
| found = true; |
| } |
| } |
| |
| if (found) { |
| return CompiledType(compiler, kj::mv(newDecl)); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| Compiler::CompiledType Compiler::ModuleScope::getRoot() { |
| kj::ExternalMutexGuarded<BrandedDecl> newDecl; |
| |
| { |
| auto lock = compiler.impl.lockExclusive(); |
| auto brandScope = kj::refcounted<BrandScope>(ErrorIgnorer::instance, node.getId(), 0, node); |
| Resolver::ResolvedDecl decl { node.getId(), 0, 0, node.getKind(), &node, nullptr }; |
| newDecl.set(lock, BrandedDecl(kj::mv(decl), kj::mv(brandScope), {})); |
| } |
| |
| return CompiledType(compiler, kj::mv(newDecl)); |
| } |
| |
| kj::Maybe<Compiler::CompiledType> Compiler::ModuleScope::evalType( |
| Expression::Reader expression, ErrorReporter& errorReporter) { |
| kj::ExternalMutexGuarded<BrandedDecl> newDecl; |
| bool found = false; |
| |
| { |
| auto lock = compiler.impl.lockExclusive(); |
| auto brandScope = kj::refcounted<BrandScope>(errorReporter, node.getId(), 0, node); |
| KJ_IF_MAYBE(result, brandScope->compileDeclExpression( |
| expression, node, ImplicitParams::none())) { |
| newDecl.set(lock, kj::mv(*result)); |
| found = true; |
| }; |
| } |
| |
| if (found) { |
| return CompiledType(compiler, kj::mv(newDecl)); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| } // namespace compiler |
| } // namespace capnp |