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 | #if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS) |
25 | #pragma GCC system_header |
26 | #endif |
27 | |
28 | #include <capnp/compiler/grammar.capnp.h> |
29 | #include <capnp/schema.capnp.h> |
30 | #include <capnp/schema-loader.h> |
31 | #include "error-reporter.h" |
32 | |
33 | namespace capnp { |
34 | namespace compiler { |
35 | |
36 | class Module: public ErrorReporter { |
37 | public: |
38 | virtual kj::StringPtr getSourceName() = 0; |
39 | // The name of the module file relative to the source tree. Used to decide where to output |
40 | // generated code and to form the `displayName` in the schema. |
41 | |
42 | virtual Orphan<ParsedFile> loadContent(Orphanage orphanage) = 0; |
43 | // Loads the module content, using the given orphanage to allocate objects if necessary. |
44 | |
45 | virtual kj::Maybe<Module&> importRelative(kj::StringPtr importPath) = 0; |
46 | // Find another module, relative to this one. Importing the same logical module twice should |
47 | // produce the exact same object, comparable by identity. These objects are owned by some |
48 | // outside pool that outlives the Compiler instance. |
49 | |
50 | virtual kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) = 0; |
51 | // Read and return the content of a file specified using `embed`. |
52 | }; |
53 | |
54 | class Compiler final: private SchemaLoader::LazyLoadCallback { |
55 | // Cross-links separate modules (schema files) and translates them into schema nodes. |
56 | // |
57 | // This class is thread-safe, hence all its methods are const. |
58 | |
59 | public: |
60 | enum AnnotationFlag { |
61 | COMPILE_ANNOTATIONS, |
62 | // Compile annotations normally. |
63 | |
64 | DROP_ANNOTATIONS |
65 | // Do not compile any annotations, eagerly or lazily. All "annotations" fields in the schema |
66 | // will be left empty. This is useful to avoid parsing imports that are used only for |
67 | // annotations which you don't intend to use anyway. |
68 | // |
69 | // Unfortunately annotations cannot simply be compiled lazily because filling in the |
70 | // "annotations" field at the usage site requires knowing the annotation's type, which requires |
71 | // compiling the annotation, and the schema API has no particular way to detect when you |
72 | // try to access the "annotations" field in order to lazily compile the annotations at that |
73 | // point. |
74 | }; |
75 | |
76 | explicit Compiler(AnnotationFlag annotationFlag = COMPILE_ANNOTATIONS); |
77 | ~Compiler() noexcept(false); |
78 | KJ_DISALLOW_COPY(Compiler); |
79 | |
80 | uint64_t add(Module& module) const; |
81 | // Add a module to the Compiler, returning the module's file ID. The ID can then be looked up in |
82 | // the `SchemaLoader` returned by `getLoader()`. However, the SchemaLoader may behave as if the |
83 | // schema node doesn't exist if any compilation errors occur (reported via the module's |
84 | // ErrorReporter). The module is parsed at the time `add()` is called, but not fully compiled -- |
85 | // individual schema nodes are compiled lazily. If you want to force eager compilation, |
86 | // see `eagerlyCompile()`, below. |
87 | |
88 | kj::Maybe<uint64_t> lookup(uint64_t parent, kj::StringPtr childName) const; |
89 | // Given the type ID of a schema node, find the ID of a node nested within it. Throws an |
90 | // exception if the parent ID is not recognized; returns null if the parent has no child of the |
91 | // given name. Neither the parent nor the child schema node is actually compiled. |
92 | |
93 | kj::Maybe<schema::Node::SourceInfo::Reader> getSourceInfo(uint64_t id) const; |
94 | // Get the SourceInfo for the given type ID, if available. |
95 | |
96 | Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>> |
97 | getFileImportTable(Module& module, Orphanage orphanage) const; |
98 | // Build the import table for the CodeGeneratorRequest for the given module. |
99 | |
100 | Orphan<List<schema::Node::SourceInfo>> getAllSourceInfo(Orphanage orphanage) const; |
101 | // Gets the SourceInfo structs for all nodes parsed by the compiler. |
102 | |
103 | enum Eagerness: uint32_t { |
104 | // Flags specifying how eager to be about compilation. These are intended to be bitwise OR'd. |
105 | // Used with the method `eagerlyCompile()`. |
106 | // |
107 | // Schema declarations can be compiled upfront, or they can be compiled lazily as they are |
108 | // needed. Usually, the difference is not observable, but it is not a perfect abstraction. |
109 | // The difference has the following effects: |
110 | // * `getLoader().getAllLoaded()` only returns the schema nodes which have been compiled so |
111 | // far. |
112 | // * `getLoader().get()` (i.e. searching for a schema by ID) can only find schema nodes that |
113 | // have either been compiled already, or which are referenced by schema nodes which have been |
114 | // compiled already. This means that if the ID you pass in came from another schema node |
115 | // compiled with the same compiler, there should be no observable difference, but if you |
116 | // have an ID from elsewhere which you _a priori_ expect is defined in a particular schema |
117 | // file, you will need to compile that file eagerly before you look up the node by ID. |
118 | // * Errors are reported when they are encountered, so some errors will not be reported until |
119 | // the node is actually compiled. |
120 | // * If an imported file is not needed, it will never even be read from disk. |
121 | // |
122 | // The last point is the main reason why you might want to prefer lazy compilation: it allows |
123 | // you to use a schema file with missing imports, so long as those missing imports are not |
124 | // actually needed. |
125 | // |
126 | // For example, the flag combo: |
127 | // EAGER_NODE | EAGER_CHILDREN | EAGER_DEPENDENCIES | EAGER_DEPENDENCY_PARENTS |
128 | // will compile the entire given module, plus all direct dependencies of anything in that |
129 | // module, plus all lexical ancestors of those dependencies. This is what the Cap'n Proto |
130 | // compiler uses when building initial code generator requests. |
131 | |
132 | ALL_RELATED_NODES = ~0u, |
133 | // Compile everything that is in any way related to the target node, including its entire |
134 | // containing file and everything transitively imported by it. |
135 | |
136 | NODE = 1 << 0, |
137 | // Eagerly compile the requested node, but not necessarily any of its parents, children, or |
138 | // dependencies. |
139 | |
140 | PARENTS = 1 << 1, |
141 | // Eagerly compile all lexical parents of the requested node. Only meaningful in conjuction |
142 | // with NODE. |
143 | |
144 | CHILDREN = 1 << 2, |
145 | // Eagerly compile all of the node's lexically nested nodes. Only meaningful in conjuction |
146 | // with NODE. |
147 | |
148 | DEPENDENCIES = NODE << 15, |
149 | // For all nodes compiled as a result of the above flags, also compile their direct |
150 | // dependencies. E.g. if Foo is a struct which contains a field of type Bar, and Foo is |
151 | // compiled, then also compile Bar. "Dependencies" are defined as field types, method |
152 | // parameter and return types, and annotation types. Nested types and outer types are not |
153 | // considered dependencies. |
154 | |
155 | DEPENDENCY_PARENTS = PARENTS * DEPENDENCIES, |
156 | DEPENDENCY_CHILDREN = CHILDREN * DEPENDENCIES, |
157 | DEPENDENCY_DEPENDENCIES = DEPENDENCIES * DEPENDENCIES, |
158 | // Like PARENTS, CHILDREN, and DEPENDENCIES, but applies relative to dependency nodes rather |
159 | // than the original requested node. Note that DEPENDENCY_DEPENDENCIES causes all transitive |
160 | // dependencies of the requested node to be compiled. |
161 | // |
162 | // These flags are defined as multiples of the original flag and DEPENDENCIES so that we |
163 | // can form the flags to use when traversing a dependency by shifting bits. |
164 | }; |
165 | |
166 | void eagerlyCompile(uint64_t id, uint eagerness) const; |
167 | // Force eager compilation of schema nodes related to the given ID. `eagerness` specifies which |
168 | // related nodes should be compiled before returning. It is a bitwise OR of the possible values |
169 | // of the `Eagerness` enum. |
170 | // |
171 | // If this returns and no errors have been reported, then it is guaranteed that the compiled |
172 | // nodes can be found in the SchemaLoader returned by `getLoader()`. |
173 | |
174 | const SchemaLoader& getLoader() const { return loader; } |
175 | SchemaLoader& getLoader() { return loader; } |
176 | // Get a SchemaLoader backed by this compiler. Schema nodes will be lazily constructed as you |
177 | // traverse them using this loader. |
178 | |
179 | void clearWorkspace() const; |
180 | // The compiler builds a lot of temporary tables and data structures while it works. It's |
181 | // useful to keep these around if more work is expected (especially if you are using lazy |
182 | // compilation and plan to look up Schema nodes that haven't already been seen), but once |
183 | // the SchemaLoader has everything you need, you can call clearWorkspace() to free up the |
184 | // temporary space. Note that it's safe to call clearWorkspace() even if you do expect to |
185 | // compile more nodes in the future; it may simply lead to redundant work if the discarded |
186 | // structures are needed again. |
187 | |
188 | private: |
189 | class Impl; |
190 | kj::MutexGuarded<kj::Own<Impl>> impl; |
191 | SchemaLoader loader; |
192 | |
193 | class CompiledModule; |
194 | class Node; |
195 | class Alias; |
196 | |
197 | void load(const SchemaLoader& loader, uint64_t id) const override; |
198 | }; |
199 | |
200 | } // namespace compiler |
201 | } // namespace capnp |
202 | |