capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

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