compiler.h (11900B)
1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #pragma once 23 24 #include <capnp/compiler/grammar.capnp.h> 25 #include <capnp/schema.capnp.h> 26 #include <capnp/schema-loader.h> 27 #include "error-reporter.h" 28 #include "generics.h" 29 30 CAPNP_BEGIN_HEADER 31 32 namespace capnp { 33 namespace compiler { 34 35 class Module: public ErrorReporter { 36 public: 37 virtual kj::StringPtr getSourceName() = 0; 38 // The name of the module file relative to the source tree. Used to decide where to output 39 // generated code and to form the `displayName` in the schema. 40 41 virtual Orphan<ParsedFile> loadContent(Orphanage orphanage) = 0; 42 // Loads the module content, using the given orphanage to allocate objects if necessary. 43 44 virtual kj::Maybe<Module&> importRelative(kj::StringPtr importPath) = 0; 45 // Find another module, relative to this one. Importing the same logical module twice should 46 // produce the exact same object, comparable by identity. These objects are owned by some 47 // outside pool that outlives the Compiler instance. 48 49 virtual kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) = 0; 50 // Read and return the content of a file specified using `embed`. 51 }; 52 53 class Compiler final: private SchemaLoader::LazyLoadCallback { 54 // Cross-links separate modules (schema files) and translates them into schema nodes. 55 // 56 // This class is thread-safe, hence all its methods are const. 57 58 class Node; 59 60 public: 61 enum AnnotationFlag { 62 COMPILE_ANNOTATIONS, 63 // Compile annotations normally. 64 65 DROP_ANNOTATIONS 66 // Do not compile any annotations, eagerly or lazily. All "annotations" fields in the schema 67 // will be left empty. This is useful to avoid parsing imports that are used only for 68 // annotations which you don't intend to use anyway. 69 // 70 // Unfortunately annotations cannot simply be compiled lazily because filling in the 71 // "annotations" field at the usage site requires knowing the annotation's type, which requires 72 // compiling the annotation, and the schema API has no particular way to detect when you 73 // try to access the "annotations" field in order to lazily compile the annotations at that 74 // point. 75 }; 76 77 explicit Compiler(AnnotationFlag annotationFlag = COMPILE_ANNOTATIONS); 78 ~Compiler() noexcept(false); 79 KJ_DISALLOW_COPY(Compiler); 80 81 class CompiledType { 82 // Represents a compiled type expression, from which you can traverse to nested types, apply 83 // generics, etc. 84 85 public: 86 CompiledType clone(); 87 // Make another CompiledType pointing to the same type. 88 89 kj::Maybe<Type> getSchema(); 90 // Evaluate to a type schema. Returns null if this "type" cannot actually be used as a field 91 // type, e.g. because it's the pseudo-type representing a file's top-level scope. 92 93 kj::Maybe<CompiledType> getMember(kj::StringPtr name); 94 // Look up a nested declaration. Returns null if there is no such member, or if the member is 95 // not a type. 96 97 kj::Maybe<CompiledType> applyBrand(kj::Array<CompiledType> arguments); 98 // If this is a generic type, specializes apply a brand to it. Returns null if this is 99 // not a generic type or too many arguments were specified. 100 101 private: 102 const Compiler& compiler; 103 kj::ExternalMutexGuarded<BrandedDecl> decl; 104 105 CompiledType(const Compiler& compiler, kj::ExternalMutexGuarded<BrandedDecl> decl) 106 : compiler(compiler), decl(kj::mv(decl)) {} 107 108 friend class Compiler; 109 }; 110 111 class ModuleScope { 112 // Result of compiling a module. 113 114 public: 115 uint64_t getId() { return id; } 116 117 CompiledType getRoot(); 118 // Get a CompiledType representing the root, which can be used to programmatically look up 119 // declarations. 120 121 kj::Maybe<CompiledType> evalType(Expression::Reader expression, ErrorReporter& errorReporter); 122 // Evaluate some type expression within the scope of this module. 123 // 124 // Returns null if errors prevented evaluation; the errors will have been reported to 125 // `errorReporter`. 126 127 private: 128 const Compiler& compiler; 129 uint64_t id; 130 Node& node; 131 132 ModuleScope(const Compiler& compiler, uint64_t id, Node& node) 133 : compiler(compiler), id(id), node(node) {} 134 135 friend class Compiler; 136 }; 137 138 ModuleScope add(Module& module) const; 139 // Add a module to the Compiler, returning a CompiledType representing the top-level scope of 140 // the module. The module is parsed at the time `add()` is called, but not fully compiled -- 141 // individual schema nodes are compiled lazily. If you want to force eager compilation, 142 // see `eagerlyCompile()`, below. 143 144 kj::Maybe<uint64_t> lookup(uint64_t parent, kj::StringPtr childName) const; 145 // Given the type ID of a schema node, find the ID of a node nested within it. Throws an 146 // exception if the parent ID is not recognized; returns null if the parent has no child of the 147 // given name. Neither the parent nor the child schema node is actually compiled. 148 // 149 // TODO(cleanup): This interface does not handle generics correctly. Use the 150 // ModuleScope/CompiledType interface instead. 151 152 kj::Maybe<schema::Node::SourceInfo::Reader> getSourceInfo(uint64_t id) const; 153 // Get the SourceInfo for the given type ID, if available. 154 155 Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>> 156 getFileImportTable(Module& module, Orphanage orphanage) const; 157 // Build the import table for the CodeGeneratorRequest for the given module. 158 159 Orphan<List<schema::Node::SourceInfo>> getAllSourceInfo(Orphanage orphanage) const; 160 // Gets the SourceInfo structs for all nodes parsed by the compiler. 161 162 enum Eagerness: uint32_t { 163 // Flags specifying how eager to be about compilation. These are intended to be bitwise OR'd. 164 // Used with the method `eagerlyCompile()`. 165 // 166 // Schema declarations can be compiled upfront, or they can be compiled lazily as they are 167 // needed. Usually, the difference is not observable, but it is not a perfect abstraction. 168 // The difference has the following effects: 169 // * `getLoader().getAllLoaded()` only returns the schema nodes which have been compiled so 170 // far. 171 // * `getLoader().get()` (i.e. searching for a schema by ID) can only find schema nodes that 172 // have either been compiled already, or which are referenced by schema nodes which have been 173 // compiled already. This means that if the ID you pass in came from another schema node 174 // compiled with the same compiler, there should be no observable difference, but if you 175 // have an ID from elsewhere which you _a priori_ expect is defined in a particular schema 176 // file, you will need to compile that file eagerly before you look up the node by ID. 177 // * Errors are reported when they are encountered, so some errors will not be reported until 178 // the node is actually compiled. 179 // * If an imported file is not needed, it will never even be read from disk. 180 // 181 // The last point is the main reason why you might want to prefer lazy compilation: it allows 182 // you to use a schema file with missing imports, so long as those missing imports are not 183 // actually needed. 184 // 185 // For example, the flag combo: 186 // EAGER_NODE | EAGER_CHILDREN | EAGER_DEPENDENCIES | EAGER_DEPENDENCY_PARENTS 187 // will compile the entire given module, plus all direct dependencies of anything in that 188 // module, plus all lexical ancestors of those dependencies. This is what the Cap'n Proto 189 // compiler uses when building initial code generator requests. 190 191 ALL_RELATED_NODES = ~0u, 192 // Compile everything that is in any way related to the target node, including its entire 193 // containing file and everything transitively imported by it. 194 195 NODE = 1 << 0, 196 // Eagerly compile the requested node, but not necessarily any of its parents, children, or 197 // dependencies. 198 199 PARENTS = 1 << 1, 200 // Eagerly compile all lexical parents of the requested node. Only meaningful in conjuction 201 // with NODE. 202 203 CHILDREN = 1 << 2, 204 // Eagerly compile all of the node's lexically nested nodes. Only meaningful in conjuction 205 // with NODE. 206 207 DEPENDENCIES = NODE << 15, 208 // For all nodes compiled as a result of the above flags, also compile their direct 209 // dependencies. E.g. if Foo is a struct which contains a field of type Bar, and Foo is 210 // compiled, then also compile Bar. "Dependencies" are defined as field types, method 211 // parameter and return types, and annotation types. Nested types and outer types are not 212 // considered dependencies. 213 214 DEPENDENCY_PARENTS = PARENTS * DEPENDENCIES, 215 DEPENDENCY_CHILDREN = CHILDREN * DEPENDENCIES, 216 DEPENDENCY_DEPENDENCIES = DEPENDENCIES * DEPENDENCIES, 217 // Like PARENTS, CHILDREN, and DEPENDENCIES, but applies relative to dependency nodes rather 218 // than the original requested node. Note that DEPENDENCY_DEPENDENCIES causes all transitive 219 // dependencies of the requested node to be compiled. 220 // 221 // These flags are defined as multiples of the original flag and DEPENDENCIES so that we 222 // can form the flags to use when traversing a dependency by shifting bits. 223 }; 224 225 void eagerlyCompile(uint64_t id, uint eagerness) const; 226 // Force eager compilation of schema nodes related to the given ID. `eagerness` specifies which 227 // related nodes should be compiled before returning. It is a bitwise OR of the possible values 228 // of the `Eagerness` enum. 229 // 230 // If this returns and no errors have been reported, then it is guaranteed that the compiled 231 // nodes can be found in the SchemaLoader returned by `getLoader()`. 232 233 const SchemaLoader& getLoader() const { return loader; } 234 SchemaLoader& getLoader() { return loader; } 235 // Get a SchemaLoader backed by this compiler. Schema nodes will be lazily constructed as you 236 // traverse them using this loader. 237 238 void clearWorkspace() const; 239 // The compiler builds a lot of temporary tables and data structures while it works. It's 240 // useful to keep these around if more work is expected (especially if you are using lazy 241 // compilation and plan to look up Schema nodes that haven't already been seen), but once 242 // the SchemaLoader has everything you need, you can call clearWorkspace() to free up the 243 // temporary space. Note that it's safe to call clearWorkspace() even if you do expect to 244 // compile more nodes in the future; it may simply lead to redundant work if the discarded 245 // structures are needed again. 246 247 private: 248 class Impl; 249 kj::MutexGuarded<kj::Own<Impl>> impl; 250 SchemaLoader loader; 251 252 class CompiledModule; 253 class Alias; 254 class ErrorIgnorer; 255 256 void load(const SchemaLoader& loader, uint64_t id) const override; 257 }; 258 259 } // namespace compiler 260 } // namespace capnp 261 262 CAPNP_END_HEADER