schema-parser.h (12142B)
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 "schema-loader.h" 25 #include <kj/string.h> 26 #include <kj/filesystem.h> 27 28 CAPNP_BEGIN_HEADER 29 30 namespace capnp { 31 32 class ParsedSchema; 33 class SchemaFile; 34 35 class SchemaParser { 36 // Parses `.capnp` files to produce `Schema` objects. 37 // 38 // This class is thread-safe, hence all its methods are const. 39 40 public: 41 SchemaParser(); 42 ~SchemaParser() noexcept(false); 43 44 ParsedSchema parseFromDirectory( 45 const kj::ReadableDirectory& baseDir, kj::Path path, 46 kj::ArrayPtr<const kj::ReadableDirectory* const> importPath) const; 47 // Parse a file from the KJ filesystem API. Throws an exception if the file doesn't exist. 48 // 49 // `baseDir` and `path` are used together to resolve relative imports. `path` is the source 50 // file's path within `baseDir`. Relative imports will be interpreted relative to `path` and 51 // will be opened using `baseDir`. Note that the KJ filesystem API prohibits "breaking out" of 52 // a directory using "..", so relative imports will be restricted to children of `baseDir`. 53 // 54 // `importPath` is used for absolute imports (imports that start with a '/'). Each directory in 55 // the array will be searched in order until a file is found. 56 // 57 // All `ReadableDirectory` objects must remain valid until the `SchemaParser` is destroyed. Also, 58 // the `importPath` array must remain valid. `path` will be copied; it need not remain valid. 59 // 60 // This method is a shortcut, equivalent to: 61 // parser.parseFile(SchemaFile::newDiskFile(baseDir, path, importPath))`; 62 // 63 // This method throws an exception if any errors are encountered in the file or in anything the 64 // file depends on. Note that merely importing another file does not count as a dependency on 65 // anything in the imported file -- only the imported types which are actually used are 66 // "dependencies". 67 // 68 // Hint: Use kj::newDiskFilesystem() to initialize the KJ filesystem API. Usually you should do 69 // this at a high level in your program, e.g. the main() function, and then pass down the 70 // appropriate File/Directory objects to the components that need them. Example: 71 // 72 // auto fs = kj::newDiskFilesystem(); 73 // SchemaParser parser; 74 // auto schema = parser.parseFromDirectory(fs->getCurrent(), 75 // kj::Path::parse("foo/bar.capnp"), nullptr); 76 // 77 // Hint: To use in-memory data rather than real disk, you can use kj::newInMemoryDirectory(), 78 // write the files you want, then pass it to SchemaParser. Example: 79 // 80 // auto dir = kj::newInMemoryDirectory(kj::nullClock()); 81 // auto path = kj::Path::parse("foo/bar.capnp"); 82 // dir->openFile(path, kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT) 83 // ->writeAll("struct Foo {}"); 84 // auto schema = parser.parseFromDirectory(*dir, path, nullptr); 85 // 86 // Hint: You can create an in-memory directory but then populate it with real files from disk, 87 // in order to control what is visible while also avoiding reading files yourself or making 88 // extra copies. Example: 89 // 90 // auto fs = kj::newDiskFilesystem(); 91 // auto dir = kj::newInMemoryDirectory(kj::nullClock()); 92 // auto fakePath = kj::Path::parse("foo/bar.capnp"); 93 // auto realPath = kj::Path::parse("path/to/some/file.capnp"); 94 // dir->transfer(fakePath, kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT, 95 // fs->getCurrent(), realPath, kj::TransferMode::LINK); 96 // auto schema = parser.parseFromDirectory(*dir, fakePath, nullptr); 97 // 98 // In this example, note that any imports in the file will fail, since the in-memory directory 99 // you created contains no files except the specific one you linked in. 100 101 ParsedSchema parseDiskFile(kj::StringPtr displayName, kj::StringPtr diskPath, 102 kj::ArrayPtr<const kj::StringPtr> importPath) const 103 CAPNP_DEPRECATED("Use parseFromDirectory() instead."); 104 // Creates a private kj::Filesystem and uses it to parse files from the real disk. 105 // 106 // DO NOT USE in new code. Use parseFromDirectory() instead. 107 // 108 // This API has a serious problem: the file can import and embed files located anywhere on disk 109 // using relative paths. Even if you specify no `importPath`, relative imports still work. By 110 // using `parseFromDirectory()`, you can arrange so that imports are only allowed within a 111 // particular directory, or even set up a dummy filesystem where other files are not visible. 112 113 void setDiskFilesystem(kj::Filesystem& fs) 114 CAPNP_DEPRECATED("Use parseFromDirectory() instead."); 115 // Call before calling parseDiskFile() to choose an alternative disk filesystem implementation. 116 // This exists mostly for testing purposes; new code should use parseFromDirectory() instead. 117 // 118 // If parseDiskFile() is called without having called setDiskFilesystem(), then 119 // kj::newDiskFilesystem() will be used instead. 120 121 ParsedSchema parseFile(kj::Own<SchemaFile>&& file) const; 122 // Advanced interface for parsing a file that may or may not be located in any global namespace. 123 // Most users will prefer `parseFromDirectory()`. 124 // 125 // If the file has already been parsed (that is, a SchemaFile that compares equal to this one 126 // was parsed previously), the existing schema will be returned again. 127 // 128 // This method reports errors by calling SchemaFile::reportError() on the file where the error 129 // is located. If that call does not throw an exception, `parseFile()` may in fact return 130 // normally. In this case, the result is a best-effort attempt to compile the schema, but it 131 // may be invalid or corrupt, and using it for anything may cause exceptions to be thrown. 132 133 kj::Maybe<schema::Node::SourceInfo::Reader> getSourceInfo(Schema schema) const; 134 // Look up source info (e.g. doc comments) for the given schema, which must have come from this 135 // SchemaParser. Note that this will also work for implicit group and param types that don't have 136 // a type name hence don't have a `ParsedSchema`. 137 138 template <typename T> 139 inline void loadCompiledTypeAndDependencies() { 140 // See SchemaLoader::loadCompiledTypeAndDependencies(). 141 getLoader().loadCompiledTypeAndDependencies<T>(); 142 } 143 144 private: 145 struct Impl; 146 struct DiskFileCompat; 147 class ModuleImpl; 148 kj::Own<Impl> impl; 149 mutable bool hadErrors = false; 150 151 ModuleImpl& getModuleImpl(kj::Own<SchemaFile>&& file) const; 152 SchemaLoader& getLoader(); 153 154 friend class ParsedSchema; 155 }; 156 157 class ParsedSchema: public Schema { 158 // ParsedSchema is an extension of Schema which also has the ability to look up nested nodes 159 // by name. See `SchemaParser`. 160 161 class ParsedSchemaList; 162 friend class ParsedSchemaList; 163 164 public: 165 inline ParsedSchema(): parser(nullptr) {} 166 167 kj::Maybe<ParsedSchema> findNested(kj::StringPtr name) const; 168 // Gets the nested node with the given name, or returns null if there is no such nested 169 // declaration. 170 171 ParsedSchema getNested(kj::StringPtr name) const; 172 // Gets the nested node with the given name, or throws an exception if there is no such nested 173 // declaration. 174 175 ParsedSchemaList getAllNested() const; 176 // Get all the nested nodes 177 178 schema::Node::SourceInfo::Reader getSourceInfo() const; 179 // Get the source info for this schema. 180 181 private: 182 inline ParsedSchema(Schema inner, const SchemaParser& parser): Schema(inner), parser(&parser) {} 183 184 const SchemaParser* parser; 185 friend class SchemaParser; 186 }; 187 188 class ParsedSchema::ParsedSchemaList { 189 public: 190 ParsedSchemaList() = default; // empty list 191 192 inline uint size() const { return list.size(); } 193 ParsedSchema operator[](uint index) const; 194 195 typedef _::IndexingIterator<const ParsedSchemaList, ParsedSchema> Iterator; 196 inline Iterator begin() const { return Iterator(this, 0); } 197 inline Iterator end() const { return Iterator(this, size()); } 198 199 private: 200 ParsedSchema parent; 201 List<schema::Node::NestedNode>::Reader list; 202 203 inline ParsedSchemaList(ParsedSchema parent, List<schema::Node::NestedNode>::Reader list) 204 : parent(parent), list(list) {} 205 206 friend class ParsedSchema; 207 }; 208 209 // ======================================================================================= 210 // Advanced API 211 212 class SchemaFile { 213 // Abstract interface representing a schema file. You can implement this yourself in order to 214 // gain more control over how the compiler resolves imports and reads files. For the 215 // common case of files on disk or other global filesystem-like namespaces, use 216 // `SchemaFile::newDiskFile()`. 217 218 public: 219 // Note: Cap'n Proto 0.6.x and below had classes FileReader and DiskFileReader and a method 220 // newDiskFile() defined here. These were removed when SchemaParser was transitioned to use the 221 // KJ filesystem API. You should be able to get the same effect by subclassing 222 // kj::ReadableDirectory, or using kj::newInMemoryDirectory(). 223 224 static kj::Own<SchemaFile> newFromDirectory( 225 const kj::ReadableDirectory& baseDir, kj::Path path, 226 kj::ArrayPtr<const kj::ReadableDirectory* const> importPath, 227 kj::Maybe<kj::String> displayNameOverride = nullptr); 228 // Construct a SchemaFile representing a file in a kj::ReadableDirectory. This is used to 229 // implement SchemaParser::parseFromDirectory(); see there for details. 230 // 231 // The SchemaFile compares equal to any other SchemaFile that has exactly the same `baseDir` 232 // object (by identity) and `path` (by value). 233 234 // ----------------------------------------------------------------- 235 // For more control, you can implement this interface. 236 237 virtual kj::StringPtr getDisplayName() const = 0; 238 // Get the file's name, as it should appear in the schema. 239 240 virtual kj::Array<const char> readContent() const = 0; 241 // Read the file's entire content and return it as a byte array. 242 243 virtual kj::Maybe<kj::Own<SchemaFile>> import(kj::StringPtr path) const = 0; 244 // Resolve an import, relative to this file. 245 // 246 // `path` is exactly what appears between quotes after the `import` keyword in the source code. 247 // It is entirely up to the `SchemaFile` to decide how to map this to another file. Typically, 248 // a leading '/' means that the file is an "absolute" path and is searched for in some list of 249 // schema file repositories. On the other hand, a path that doesn't start with '/' is relative 250 // to the importing file. 251 252 virtual bool operator==(const SchemaFile& other) const = 0; 253 virtual bool operator!=(const SchemaFile& other) const = 0; 254 virtual size_t hashCode() const = 0; 255 // Compare two SchemaFiles to see if they refer to the same underlying file. This is an 256 // optimization used to avoid the need to re-parse a file to check its ID. 257 258 struct SourcePos { 259 uint byte; 260 uint line; 261 uint column; 262 }; 263 virtual void reportError(SourcePos start, SourcePos end, kj::StringPtr message) const = 0; 264 // Report that the file contains an error at the given interval. 265 266 private: 267 class DiskSchemaFile; 268 }; 269 270 } // namespace capnp 271 272 CAPNP_END_HEADER