| // 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. |
| |
| #pragma once |
| |
| #include <capnp/compiler/grammar.capnp.h> |
| #include <capnp/schema.capnp.h> |
| #include <capnp/schema-loader.h> |
| #include "error-reporter.h" |
| #include "generics.h" |
| |
| CAPNP_BEGIN_HEADER |
| |
| namespace capnp { |
| namespace compiler { |
| |
| class Module: public ErrorReporter { |
| public: |
| virtual kj::StringPtr getSourceName() = 0; |
| // The name of the module file relative to the source tree. Used to decide where to output |
| // generated code and to form the `displayName` in the schema. |
| |
| virtual Orphan<ParsedFile> loadContent(Orphanage orphanage) = 0; |
| // Loads the module content, using the given orphanage to allocate objects if necessary. |
| |
| virtual kj::Maybe<Module&> importRelative(kj::StringPtr importPath) = 0; |
| // Find another module, relative to this one. Importing the same logical module twice should |
| // produce the exact same object, comparable by identity. These objects are owned by some |
| // outside pool that outlives the Compiler instance. |
| |
| virtual kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) = 0; |
| // Read and return the content of a file specified using `embed`. |
| }; |
| |
| class Compiler final: private SchemaLoader::LazyLoadCallback { |
| // Cross-links separate modules (schema files) and translates them into schema nodes. |
| // |
| // This class is thread-safe, hence all its methods are const. |
| |
| class Node; |
| |
| public: |
| enum AnnotationFlag { |
| COMPILE_ANNOTATIONS, |
| // Compile annotations normally. |
| |
| DROP_ANNOTATIONS |
| // Do not compile any annotations, eagerly or lazily. All "annotations" fields in the schema |
| // will be left empty. This is useful to avoid parsing imports that are used only for |
| // annotations which you don't intend to use anyway. |
| // |
| // Unfortunately annotations cannot simply be compiled lazily because filling in the |
| // "annotations" field at the usage site requires knowing the annotation's type, which requires |
| // compiling the annotation, and the schema API has no particular way to detect when you |
| // try to access the "annotations" field in order to lazily compile the annotations at that |
| // point. |
| }; |
| |
| explicit Compiler(AnnotationFlag annotationFlag = COMPILE_ANNOTATIONS); |
| ~Compiler() noexcept(false); |
| KJ_DISALLOW_COPY(Compiler); |
| |
| class CompiledType { |
| // Represents a compiled type expression, from which you can traverse to nested types, apply |
| // generics, etc. |
| |
| public: |
| CompiledType clone(); |
| // Make another CompiledType pointing to the same type. |
| |
| kj::Maybe<Type> getSchema(); |
| // Evaluate to a type schema. Returns null if this "type" cannot actually be used as a field |
| // type, e.g. because it's the pseudo-type representing a file's top-level scope. |
| |
| kj::Maybe<CompiledType> getMember(kj::StringPtr name); |
| // Look up a nested declaration. Returns null if there is no such member, or if the member is |
| // not a type. |
| |
| kj::Maybe<CompiledType> applyBrand(kj::Array<CompiledType> arguments); |
| // If this is a generic type, specializes apply a brand to it. Returns null if this is |
| // not a generic type or too many arguments were specified. |
| |
| private: |
| const Compiler& compiler; |
| kj::ExternalMutexGuarded<BrandedDecl> decl; |
| |
| CompiledType(const Compiler& compiler, kj::ExternalMutexGuarded<BrandedDecl> decl) |
| : compiler(compiler), decl(kj::mv(decl)) {} |
| |
| friend class Compiler; |
| }; |
| |
| class ModuleScope { |
| // Result of compiling a module. |
| |
| public: |
| uint64_t getId() { return id; } |
| |
| CompiledType getRoot(); |
| // Get a CompiledType representing the root, which can be used to programmatically look up |
| // declarations. |
| |
| kj::Maybe<CompiledType> evalType(Expression::Reader expression, ErrorReporter& errorReporter); |
| // Evaluate some type expression within the scope of this module. |
| // |
| // Returns null if errors prevented evaluation; the errors will have been reported to |
| // `errorReporter`. |
| |
| private: |
| const Compiler& compiler; |
| uint64_t id; |
| Node& node; |
| |
| ModuleScope(const Compiler& compiler, uint64_t id, Node& node) |
| : compiler(compiler), id(id), node(node) {} |
| |
| friend class Compiler; |
| }; |
| |
| ModuleScope add(Module& module) const; |
| // Add a module to the Compiler, returning a CompiledType representing the top-level scope of |
| // the module. The module is parsed at the time `add()` is called, but not fully compiled -- |
| // individual schema nodes are compiled lazily. If you want to force eager compilation, |
| // see `eagerlyCompile()`, below. |
| |
| kj::Maybe<uint64_t> lookup(uint64_t parent, kj::StringPtr childName) const; |
| // Given the type ID of a schema node, find the ID of a node nested within it. Throws an |
| // exception if the parent ID is not recognized; returns null if the parent has no child of the |
| // given name. Neither the parent nor the child schema node is actually compiled. |
| // |
| // TODO(cleanup): This interface does not handle generics correctly. Use the |
| // ModuleScope/CompiledType interface instead. |
| |
| kj::Maybe<schema::Node::SourceInfo::Reader> getSourceInfo(uint64_t id) const; |
| // Get the SourceInfo for the given type ID, if available. |
| |
| Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>> |
| getFileImportTable(Module& module, Orphanage orphanage) const; |
| // Build the import table for the CodeGeneratorRequest for the given module. |
| |
| Orphan<List<schema::Node::SourceInfo>> getAllSourceInfo(Orphanage orphanage) const; |
| // Gets the SourceInfo structs for all nodes parsed by the compiler. |
| |
| enum Eagerness: uint32_t { |
| // Flags specifying how eager to be about compilation. These are intended to be bitwise OR'd. |
| // Used with the method `eagerlyCompile()`. |
| // |
| // Schema declarations can be compiled upfront, or they can be compiled lazily as they are |
| // needed. Usually, the difference is not observable, but it is not a perfect abstraction. |
| // The difference has the following effects: |
| // * `getLoader().getAllLoaded()` only returns the schema nodes which have been compiled so |
| // far. |
| // * `getLoader().get()` (i.e. searching for a schema by ID) can only find schema nodes that |
| // have either been compiled already, or which are referenced by schema nodes which have been |
| // compiled already. This means that if the ID you pass in came from another schema node |
| // compiled with the same compiler, there should be no observable difference, but if you |
| // have an ID from elsewhere which you _a priori_ expect is defined in a particular schema |
| // file, you will need to compile that file eagerly before you look up the node by ID. |
| // * Errors are reported when they are encountered, so some errors will not be reported until |
| // the node is actually compiled. |
| // * If an imported file is not needed, it will never even be read from disk. |
| // |
| // The last point is the main reason why you might want to prefer lazy compilation: it allows |
| // you to use a schema file with missing imports, so long as those missing imports are not |
| // actually needed. |
| // |
| // For example, the flag combo: |
| // EAGER_NODE | EAGER_CHILDREN | EAGER_DEPENDENCIES | EAGER_DEPENDENCY_PARENTS |
| // will compile the entire given module, plus all direct dependencies of anything in that |
| // module, plus all lexical ancestors of those dependencies. This is what the Cap'n Proto |
| // compiler uses when building initial code generator requests. |
| |
| ALL_RELATED_NODES = ~0u, |
| // Compile everything that is in any way related to the target node, including its entire |
| // containing file and everything transitively imported by it. |
| |
| NODE = 1 << 0, |
| // Eagerly compile the requested node, but not necessarily any of its parents, children, or |
| // dependencies. |
| |
| PARENTS = 1 << 1, |
| // Eagerly compile all lexical parents of the requested node. Only meaningful in conjunction |
| // with NODE. |
| |
| CHILDREN = 1 << 2, |
| // Eagerly compile all of the node's lexically nested nodes. Only meaningful in conjunction |
| // with NODE. |
| |
| DEPENDENCIES = NODE << 15, |
| // For all nodes compiled as a result of the above flags, also compile their direct |
| // dependencies. E.g. if Foo is a struct which contains a field of type Bar, and Foo is |
| // compiled, then also compile Bar. "Dependencies" are defined as field types, method |
| // parameter and return types, and annotation types. Nested types and outer types are not |
| // considered dependencies. |
| |
| DEPENDENCY_PARENTS = PARENTS * DEPENDENCIES, |
| DEPENDENCY_CHILDREN = CHILDREN * DEPENDENCIES, |
| DEPENDENCY_DEPENDENCIES = DEPENDENCIES * DEPENDENCIES, |
| // Like PARENTS, CHILDREN, and DEPENDENCIES, but applies relative to dependency nodes rather |
| // than the original requested node. Note that DEPENDENCY_DEPENDENCIES causes all transitive |
| // dependencies of the requested node to be compiled. |
| // |
| // These flags are defined as multiples of the original flag and DEPENDENCIES so that we |
| // can form the flags to use when traversing a dependency by shifting bits. |
| }; |
| |
| void eagerlyCompile(uint64_t id, uint eagerness) const; |
| // Force eager compilation of schema nodes related to the given ID. `eagerness` specifies which |
| // related nodes should be compiled before returning. It is a bitwise OR of the possible values |
| // of the `Eagerness` enum. |
| // |
| // If this returns and no errors have been reported, then it is guaranteed that the compiled |
| // nodes can be found in the SchemaLoader returned by `getLoader()`. |
| |
| const SchemaLoader& getLoader() const { return loader; } |
| SchemaLoader& getLoader() { return loader; } |
| // Get a SchemaLoader backed by this compiler. Schema nodes will be lazily constructed as you |
| // traverse them using this loader. |
| |
| void clearWorkspace() const; |
| // The compiler builds a lot of temporary tables and data structures while it works. It's |
| // useful to keep these around if more work is expected (especially if you are using lazy |
| // compilation and plan to look up Schema nodes that haven't already been seen), but once |
| // the SchemaLoader has everything you need, you can call clearWorkspace() to free up the |
| // temporary space. Note that it's safe to call clearWorkspace() even if you do expect to |
| // compile more nodes in the future; it may simply lead to redundant work if the discarded |
| // structures are needed again. |
| |
| private: |
| class Impl; |
| kj::MutexGuarded<kj::Own<Impl>> impl; |
| SchemaLoader loader; |
| |
| class CompiledModule; |
| class Alias; |
| class ErrorIgnorer; |
| |
| void load(const SchemaLoader& loader, uint64_t id) const override; |
| }; |
| |
| } // namespace compiler |
| } // namespace capnp |
| |
| CAPNP_END_HEADER |