blob: 234e0ce1798ad73ae6e30c314645778d6dd87d56 [file] [log] [blame]
// 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