Commit 0da57538 authored by Kenton Varda's avatar Kenton Varda

Compiler core WIP.

parent 0391156a
......@@ -31,10 +31,10 @@
#include "../message.h"
#include <iostream>
class CoutErrorReporter: public capnp::compiler::ErrorReporter {
class CerrErrorReporter: public capnp::compiler::ErrorReporter {
public:
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
std::cout << "input:" << startByte << "-" << endByte << ": " << message.cStr() << std::endl;
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) const override {
std::cerr << "input:" << startByte << "-" << endByte << ": " << message.cStr() << std::endl;
}
};
......@@ -52,7 +52,7 @@ int main(int argc, char* argv[]) {
input.addAll(buffer, buffer + n);
}
CoutErrorReporter errorReporter;
CerrErrorReporter errorReporter;
std::cout << "=========================================================================\n"
<< "lex\n"
......
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "compiler.h"
#include <kj/mutex.h>
#include <kj/arena.h>
#include <kj/vector.h>
#include <kj/debug.h>
#include <capnp/message.h>
#include <map>
#include <unordered_map>
#include "node-translator.h"
#include "md5.h"
namespace capnp {
namespace compiler {
class Compiler::Alias {
public:
Alias(const Node& parent, const DeclName::Reader& targetName)
: parent(parent), targetName(targetName) {}
kj::Maybe<const Node&> getTarget() const;
private:
const Node& parent;
DeclName::Reader targetName;
kj::Lazy<kj::Maybe<const Node&>> target;
};
class Compiler::Node: public NodeTranslator::Resolver {
// Passes through four states:
// - Stub: On initial construction, the Node is just a placeholder object. Its ID has been
// determined, and it is placed in its parent's member table as well as the compiler's
// nodes-by-ID table.
// - Expanded: Nodes have been constructed for all of this Node's nested children. This happens
// the first time a lookup is performed for one of those children.
// - Bootstrap: A NodeTranslator has been built and advanced to the bootstrap phase.
// - Finished: A final Schema object has been constructed.
public:
explicit Node(CompiledModule& module);
// Create a root node representing the given file. May
Node(const Node& parent, const Declaration::Reader& declaration);
// Create a child node.
Node(kj::StringPtr name, Declaration::Body::Which kind);
// Create a dummy node representing a built-in declaration, like "Int32" or "true".
uint64_t getId() { return id; }
Declaration::Body::Which getKind() { return kind; }
kj::Maybe<const Node&> lookupMember(kj::StringPtr name) const;
// Find a direct member of this node with the given name.
kj::Maybe<const Node&> lookupLexical(kj::StringPtr name) const;
// Look up the given name first as a member of this Node, then in its parent, and so on, until
// it is found or there are no more parents to search.
kj::Maybe<const Node&> lookup(const DeclName::Reader& name) const;
// Resolve an arbitrary DeclName to a Node.
Schema getBootstrapOrFinalSchema() const;
Schema getFinalSchema() const;
void addError(kj::StringPtr error) const;
// Report an error on this Node.
// implements NodeTranslator::Resolver -----------------------------
kj::Maybe<ResolvedName> resolve(const DeclName::Reader& name) const override;
kj::Maybe<Schema> resolveMaybeBootstrapSchema(uint64_t id) const override;
kj::Maybe<Schema> resolveFinalSchema(uint64_t id) const override;
private:
const CompiledModule* module; // null iff isBuiltin is true
kj::Maybe<const Node&> parent;
Declaration::Reader declaration;
// AST of the declaration parsed from the schema file. May become invalid once the content
// state has reached FINISHED.
uint64_t id;
// The ID of this node, either taken from the AST or computed based on the parent. Or, a dummy
// value, if duplicates were detected.
kj::StringPtr displayName;
// Fully-qualified display name for this node. For files, this is just the file name, otherwise
// it is "filename:Path.To.Decl".
Declaration::Body::Which kind;
// Kind of node.
bool isBuiltin;
// Whether this is a bulit-in declaration, like "Int32" or "true".
uint32_t startByte;
uint32_t endByte;
// Start and end byte for reporting general errors.
struct Content {
inline Content(): state(STUB) {}
enum State {
STUB,
EXPANDED,
BOOTSTRAP,
FINISHED
};
State state;
// Indicates which fields below are valid. Must update with atomic-release semantics.
inline bool stateHasReached(State minimumState) const {
return __atomic_load_n(&state, __ATOMIC_ACQUIRE) >= minimumState;
}
inline void advanceState(State newState) {
__atomic_store_n(&state, newState, __ATOMIC_RELEASE);
}
// EXPANDED ------------------------------------
typedef std::multimap<kj::StringPtr, kj::Own<Node>> NestedNodesMap;
NestedNodesMap nestedNodes;
// Filled in when lookupMember() is first called. multimap in case of duplicate member names --
// we still want to compile them, even if it's an error.
typedef std::multimap<kj::StringPtr, kj::Own<Alias>> AliasMap;
AliasMap aliases;
//
// BOOTSTRAP -----------------------------------
NodeTranslator* translator;
// Node translator, allocated in the bootstrap arena.
Schema bootstrapSchema;
// The schema built in the bootstrap loader.
// FINISHED ------------------------------------
Schema finalSchema;
// The complete schema as loaded by the compiler's main SchemaLoader.
};
kj::MutexGuarded<Content> content;
// ---------------------------------------------
static uint64_t generateId(uint64_t parentId, kj::StringPtr declName,
Declaration::Id::Reader declId);
// Extract the ID from the declaration, or if it has none, generate one based on the name and
// parent ID.
static kj::StringPtr joinDisplayName(const kj::Arena& arena, const Node& parent,
kj::StringPtr declName);
// Join the parent's display name with the child's unqualified name to construct the child's
// display name.
const Content& getContent(Content::State minimumState) const;
// Advances the content to at least the given state and returns it. Does not lock if the content
// is already at or past the given state.
};
class Compiler::CompiledModule {
public:
CompiledModule(const Compiler::Impl& compiler, const Module<ParsedFile::Reader>& parserModule);
const Compiler::Impl& getCompiler() const { return compiler; }
const ErrorReporter& getErrorReporter() const { return parserModule; }
const ParsedFile::Reader& getParsedFile() const { return content; }
const Node& getRootNode() const { return rootNode; }
kj::StringPtr getSourceName() const { return parserModule.getSourceName(); }
kj::Maybe<const CompiledModule&> importRelative(kj::StringPtr importPath) const;
private:
const Compiler::Impl& compiler;
const Module<ParsedFile::Reader>& parserModule;
MallocMessageBuilder contentArena;
ParsedFile::Reader content;
Node rootNode;
};
class Compiler::Impl: public SchemaLoader::LazyLoadCallback {
public:
Impl();
const CompiledModule& add(const Module<ParsedFile::Reader>& parsedModule) const;
struct Workspace {
// Scratch space where stuff can be allocated while working. The Workspace is available
// whenever nodes are actively being compiled, then is destroyed once control exits the
// compiler. Note that since nodes are compiled lazily, a new Workspace may have to be
// constructed in order to compile more nodes later.
kj::Arena arena;
// Arena for allocating temporary native objects.
Orphanage orphanage;
// Orphanage for allocating temporary Cap'n Proto objects.
SchemaLoader bootstrapLoader;
// Loader used to load bootstrap schemas. The bootstrap schema nodes are similar to the final
// versions except that any value expressions which depend on knowledge of other types (e.g.
// default values for struct fields) are left unevaluated (the values in the schema are empty).
// These bootstrap schemas can then be plugged into the dynamic API and used to evaluate these
// remaining values.
};
const kj::Arena& getNodeArena() const { return nodeArena; }
// Arena where nodes and other permanent objects should be allocated.
const SchemaLoader& getFinalLoader() const { return finalLoader; }
// Schema loader containing final versions of schemas.
const Workspace& getWorkspace() const { return *workspace.getWithoutLock(); }
// Temporary workspace that can be used to construct bootstrap objects.
uint64_t addNode(uint64_t desiredId, Node& node) const;
// Add the given node to the by-ID map under the given ID. If another node with the same ID
// already exists, choose a new one arbitrarily and use that instead. Return the ID that was
// finally used.
kj::Maybe<const Node&> findNode(uint64_t id) const;
kj::Maybe<const Node&> lookupBuiltin(kj::StringPtr name) const;
void load(const SchemaLoader& loader, uint64_t id) const override;
private:
kj::Arena nodeArena;
// Arena used to allocate nodes and other permanent objects.
SchemaLoader finalLoader;
// The loader where we put final output of the compiler.
kj::MutexGuarded<const Workspace*> workspace;
// The entry points to the Compiler allocate a Workspace on the stack, lock this mutex, and set
// the pointer to point at said stack workspace. The rest of the compiler can assume that this
// Workspace is active.
uint workspaceRefcount = 0;
// Count of threads that have entered the compiler.
typedef std::unordered_map<Module<ParsedFile::Reader>*, kj::Own<CompiledModule>> ModuleMap;
kj::MutexGuarded<ModuleMap> modules;
// Map of parser modules to compiler modules.
typedef std::unordered_map<uint64_t, const Node*> NodeMap;
kj::MutexGuarded<NodeMap> nodesById;
// Map of nodes by ID.
std::map<kj::StringPtr, kj::Own<Node>> builtinDecls;
// Map of built-in declarations, like "Int32" and "List", which make up the global scope.
mutable uint64_t nextBogusId = 1000;
};
// =======================================================================================
kj::Maybe<const Compiler::Node&> Compiler::Alias::getTarget() const {
return target.get([this](kj::SpaceFor<kj::Maybe<const Node&>>& space) {
return space.construct(parent.lookup(targetName));
});
}
// =======================================================================================
Compiler::Node::Node(CompiledModule& module)
: module(&module),
parent(nullptr),
declaration(module.getParsedFile().getRoot()),
id(generateId(0, declaration.getName().getValue(), declaration.getId())),
displayName(module.getSourceName()),
kind(declaration.getBody().which()),
isBuiltin(false) {
auto name = declaration.getName();
if (name.getValue().size() > 0) {
startByte = name.getStartByte();
endByte = name.getEndByte();
} else {
startByte = declaration.getStartByte();
endByte = declaration.getEndByte();
}
id = module.getCompiler().addNode(id, *this);
}
Compiler::Node::Node(const Node& parent, const Declaration::Reader& declaration)
: module(parent.module),
parent(parent),
declaration(declaration),
id(generateId(parent.id, declaration.getName().getValue(), declaration.getId())),
displayName(joinDisplayName(parent.module->getCompiler().getNodeArena(),
parent, declaration.getName().getValue())),
kind(declaration.getBody().which()),
isBuiltin(false) {
auto name = declaration.getName();
if (name.getValue().size() > 0) {
startByte = name.getStartByte();
endByte = name.getEndByte();
} else {
startByte = declaration.getStartByte();
endByte = declaration.getEndByte();
}
id = module->getCompiler().addNode(id, *this);
}
Compiler::Node::Node(kj::StringPtr name, Declaration::Body::Which kind)
: module(nullptr),
parent(nullptr),
id(0),
displayName(name),
kind(kind),
isBuiltin(true),
startByte(0),
endByte(0) {}
uint64_t Compiler::Node::generateId(uint64_t parentId, kj::StringPtr declName,
Declaration::Id::Reader declId) {
if (declId.which() == Declaration::Id::UID) {
return declId.getUid().getValue();
}
// No explicit ID. Compute it by MD5 hashing the concatenation of the parent ID and the
// declaration name, and then taking the first 8 bytes.
kj::byte parentIdBytes[sizeof(uint64_t)];
for (uint i = 0; i < sizeof(uint64_t); i++) {
parentIdBytes[i] = (parentId >> (i * 8)) & 0xff;
}
Md5 md5;
md5.update(kj::arrayPtr(parentIdBytes, KJ_ARRAY_SIZE(parentIdBytes)));
md5.update(declName);
kj::ArrayPtr<const kj::byte> resultBytes = md5.finish();
uint64_t result = 0;
for (uint i = 0; i < sizeof(uint64_t); i++) {
result = (result << 8) | resultBytes[i];
}
return result;
}
kj::StringPtr Compiler::Node::joinDisplayName(
const kj::Arena& arena, const Node& parent, kj::StringPtr declName) {
kj::ArrayPtr<char> result = arena.allocateArray<char>(
parent.displayName.size() + declName.size() + 2);
size_t separatorPos = parent.displayName.size();
memcpy(result.begin(), parent.displayName.begin(), separatorPos);
result[separatorPos] = parent.parent == nullptr ? ':' : '.';
memcpy(result.begin() + separatorPos + 1, declName.begin(), declName.size());
result[result.size() - 1] = '\0';
return kj::StringPtr(declName.begin(), declName.size() - 1);
}
const Compiler::Node::Content& Compiler::Node::getContent(Content::State minimumState) const {
KJ_REQUIRE(!isBuiltin, "illegal method call for built-in declaration");
if (content.getWithoutLock().stateHasReached(minimumState)) {
return content.getWithoutLock();
}
auto locked = content.lockExclusive();
switch (locked->state) {
case Content::STUB: {
if (minimumState <= Content::STUB) break;
// Expand the child nodes.
auto& arena = module->getCompiler().getNodeArena();
for (auto nestedDecl: declaration.getNestedDecls()) {
switch (nestedDecl.getBody().which()) {
case Declaration::Body::FILE_DECL:
case Declaration::Body::CONST_DECL:
case Declaration::Body::ANNOTATION_DECL:
case Declaration::Body::ENUM_DECL:
case Declaration::Body::STRUCT_DECL:
case Declaration::Body::INTERFACE_DECL: {
kj::Own<Node> subNode = arena.allocateOwn<Node>(*this, nestedDecl);
kj::StringPtr name = nestedDecl.getName().getValue();
locked->nestedNodes.insert(std::make_pair(name, kj::mv(subNode)));
break;
}
case Declaration::Body::USING_DECL: {
kj::Own<Alias> alias = arena.allocateOwn<Alias>(
*this, nestedDecl.getBody().getUsingDecl().getTarget());
kj::StringPtr name = nestedDecl.getName().getValue();
locked->aliases.insert(std::make_pair(name, kj::mv(alias)));
break;
}
case Declaration::Body::ENUMERANT_DECL:
case Declaration::Body::FIELD_DECL:
case Declaration::Body::UNION_DECL:
case Declaration::Body::GROUP_DECL:
case Declaration::Body::METHOD_DECL:
case Declaration::Body::NAKED_ID:
case Declaration::Body::NAKED_ANNOTATION:
// Not a node. Skip.
break;
default:
KJ_FAIL_ASSERT("unknown declaration type", nestedDecl);
break;
}
}
locked->advanceState(Content::EXPANDED);
// no break
}
case Content::EXPANDED: {
if (minimumState <= Content::EXPANDED) break;
// Construct the NodeTranslator.
auto& workspace = module->getCompiler().getWorkspace();
auto schemaNode = workspace.orphanage.newOrphan<schema::Node>();
auto builder = schemaNode.get();
builder.setId(id);
builder.setDisplayName(displayName);
KJ_IF_MAYBE(p, parent) {
builder.setScopeId(p->id);
}
auto nestedIter = builder.initNestedNodes(locked->nestedNodes.size()).begin();
for (auto& entry: locked->nestedNodes) {
nestedIter->setName(entry.first);
nestedIter->setId(entry.second->id);
++nestedIter;
}
locked->translator = &workspace.arena.allocate<NodeTranslator>(
*this, module->getErrorReporter(), declaration, kj::mv(schemaNode));
locked->bootstrapSchema = workspace.bootstrapLoader.loadOnce(
locked->translator->getBootstrapNode());
// If the Workspace is destroyed while this Node is still in the BOOTSTRAP state,
// revert it to the EXPANDED state, because the NodeTranslator is no longer valid in this
// case.
Content* contentPtr = locked.get();
workspace.arena.copy(kj::defer([contentPtr]() {
if (contentPtr->state == Content::BOOTSTRAP) {
contentPtr->state = Content::EXPANDED;
}
}));
locked->advanceState(Content::BOOTSTRAP);
// no break
}
case Content::BOOTSTRAP: {
if (minimumState <= Content::BOOTSTRAP) break;
// Create the final schema.
locked->finalSchema = module->getCompiler().getFinalLoader().loadOnce(
locked->translator->finish());
locked->advanceState(Content::FINISHED);
// no break
}
case Content::FINISHED:
break;
}
return *locked;
}
kj::Maybe<const Compiler::Node&> Compiler::Node::lookupMember(kj::StringPtr name) const {
if (isBuiltin) return nullptr;
auto& content = getContent(Content::EXPANDED);
{
auto iter = content.nestedNodes.find(name);
if (iter != content.nestedNodes.end()) {
return *iter->second;
}
}
{
auto iter = content.aliases.find(name);
if (iter != content.aliases.end()) {
return iter->second->getTarget();
}
}
return nullptr;
}
kj::Maybe<const Compiler::Node&> Compiler::Node::lookupLexical(kj::StringPtr name) const {
KJ_REQUIRE(!isBuiltin, "illegal method call for built-in declaration");
auto result = lookupMember(name);
if (result == nullptr) {
KJ_IF_MAYBE(p, parent) {
result = p->lookupLexical(name);
} else {
result = module->getCompiler().lookupBuiltin(name);
}
}
return result;
}
kj::Maybe<const Compiler::Node&> Compiler::Node::lookup(const DeclName::Reader& name) const {
KJ_REQUIRE(!isBuiltin, "illegal method call for built-in declaration");
const Node* node = nullptr;
auto base = name.getBase();
switch (base.which()) {
case DeclName::Base::ABSOLUTE_NAME: {
auto absoluteName = base.getAbsoluteName();
KJ_IF_MAYBE(n, module->getRootNode().lookupMember(absoluteName.getValue())) {
node = &*n;
} else {
module->getErrorReporter().addErrorOn(
absoluteName, kj::str("not defined: ", absoluteName.getValue()));
return nullptr;
}
break;
}
case DeclName::Base::RELATIVE_NAME: {
auto relativeName = base.getRelativeName();
KJ_IF_MAYBE(n, lookupLexical(relativeName.getValue())) {
node = &*n;
} else {
module->getErrorReporter().addErrorOn(
relativeName, kj::str("not defined: ", relativeName.getValue()));
return nullptr;
}
break;
}
case DeclName::Base::IMPORT_NAME: {
auto importName = base.getImportName();
KJ_IF_MAYBE(m, module->importRelative(importName.getValue())) {
node = &m->getRootNode();
} else {
module->getErrorReporter().addErrorOn(
importName, kj::str("import failed: ", importName.getValue()));
return nullptr;
}
break;
}
}
KJ_ASSERT(node != nullptr);
for (auto partName: name.getMemberPath()) {
KJ_IF_MAYBE(member, node->lookupMember(partName.getValue())) {
node = &*member;
} else {
module->getErrorReporter().addErrorOn(
partName, kj::str("no such member: ", partName.getValue()));
return nullptr;
}
}
return *node;
}
Schema Compiler::Node::getBootstrapOrFinalSchema() const {
auto& content = getContent(Content::BOOTSTRAP);
if (__atomic_load_n(&content.state, __ATOMIC_ACQUIRE) == Content::FINISHED) {
return content.finalSchema;
} else {
return content.bootstrapSchema;
}
}
Schema Compiler::Node::getFinalSchema() const {
return getContent(Content::FINISHED).finalSchema;
}
void Compiler::Node::addError(kj::StringPtr error) const {
module->getErrorReporter().addError(startByte, endByte, error);
}
kj::Maybe<NodeTranslator::Resolver::ResolvedName> Compiler::Node::resolve(
const DeclName::Reader& name) const {
return lookup(name).map([](const Node& node) {
return ResolvedName { node.id, node.kind };
});
}
kj::Maybe<Schema> Compiler::Node::resolveMaybeBootstrapSchema(uint64_t id) const {
return module->getCompiler().findNode(id).map(
[](const Node& node) { return node.getBootstrapOrFinalSchema(); });
}
kj::Maybe<Schema> Compiler::Node::resolveFinalSchema(uint64_t id) const {
return module->getCompiler().findNode(id).map(
[](const Node& node) { return node.getFinalSchema(); });
}
// =======================================================================================
Compiler::CompiledModule::CompiledModule(
const Compiler::Impl& compiler, const Module<ParsedFile::Reader>& parserModule)
: compiler(compiler), parserModule(parserModule),
content(parserModule.loadContent(contentArena.getOrphanage())),
rootNode(*this) {}
kj::Maybe<const Compiler::CompiledModule&> Compiler::CompiledModule::importRelative(
kj::StringPtr importPath) const {
return parserModule.importRelative(importPath).map(
[this](const Module<ParsedFile::Reader>& module) -> const Compiler::CompiledModule& {
return compiler.add(module);
});
}
// =======================================================================================
Compiler::Impl::Impl(): finalLoader(*this), workspace(nullptr) {
// Reflectively interpret the members of Declaration.body. Any member prefixed by "builtin"
// defines a builtin declaration visible in the global scope.
StructSchema::Union declBodySchema =
Schema::from<Declaration>().getMemberByName("body").asUnion();
for (auto member: declBodySchema.getMembers()) {
auto name = member.getProto().getName();
if (name.startsWith("builtin")) {
kj::StringPtr symbolName;
if (name.endsWith("Value")) {
// e.g. "builtinTrueValue" -- transform this to "true".
auto substr = name.slice(strlen("builtin"), name.size() - strlen("Value"));
kj::ArrayPtr<char> array = nodeArena.allocateArray<char>(substr.size() + 1);
memcpy(array.begin(), substr.begin(), substr.size());
array[substr.size()] = '\0';
array[0] += 'a' - 'A';
symbolName = kj::StringPtr(array.begin(), array.size() - 1);
} else {
// e.g. "builtinVoid" -- transform this to "Void".
symbolName = name.slice(strlen("builtin"));
}
builtinDecls[symbolName] = nodeArena.allocateOwn<Node>(
symbolName, static_cast<Declaration::Body::Which>(member.getIndex()));
}
}
}
uint64_t Compiler::Impl::addNode(uint64_t desiredId, Node& node) const {
auto lock = nodesById.lockExclusive();
for (;;) {
auto insertResult = lock->insert(std::make_pair(desiredId, &node));
if (insertResult.second) {
return desiredId;
}
// Only report an error if this ID is not bogus. Actual IDs specified in the original source
// code are required to have the upper bit set. Anything else must have been manufactured
// at some point to cover up an error.
if (desiredId & (1ull << 63)) {
node.addError(kj::str("Duplicate ID @0x", kj::hex(desiredId), "."));
insertResult.first->second->addError(
kj::str("ID @0x", kj::hex(desiredId), " originally used here."));
}
// Assign a new bogus ID.
desiredId = __atomic_fetch_add(&nextBogusId, 1, __ATOMIC_RELAXED);
}
}
kj::Maybe<const Compiler::Node&> Compiler::Impl::findNode(uint64_t id) const {
auto lock = nodesById.lockShared();
auto iter = lock->find(id);
if (iter == lock->end()) {
return nullptr;
} else {
return *iter->second;
}
}
kj::Maybe<const Compiler::Node&> Compiler::Impl::lookupBuiltin(kj::StringPtr name) const {
auto iter = builtinDecls.find(name);
if (iter == builtinDecls.end()) {
return nullptr;
} else {
return *iter->second;
}
}
void Compiler::Impl::load(const SchemaLoader& loader, uint64_t id) const {
KJ_IF_MAYBE(node, findNode(id)) {
if (&loader == &finalLoader) {
Workspace workspace;
auto lock = this->workspace.lockExclusive();
*lock = &workspace;
KJ_DEFER(*lock = nullptr);
node->getFinalSchema();
} else {
// Must be the bootstrap loader.
node->getBootstrapOrFinalSchema();
}
}
}
} // namespace compiler
} // namespace capnp
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CAPNP_COMPILER_COMPILER_H_
#define CAPNP_COMPILER_COMPILER_H_
#include <capnp/compiler/grammar.capnp.h>
#include <capnp/schema.capnp.h>
#include <capnp/schema-loader.h>
#include "error-reporter.h"
namespace capnp {
namespace compiler {
template <typename ContentType>
class Module: public ErrorReporter {
public:
virtual kj::StringPtr getLocalName() const = 0;
// Typically, the absolute or cwd-relative path name of the module file, used in error messages.
// This is only for display purposes.
virtual kj::StringPtr getSourceName() const = 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 ContentType loadContent(Orphanage orphanage) const = 0;
// Loads the module content, using the given orphanage to allocate objects if necessary.
virtual kj::Maybe<const Module&> importRelative(kj::StringPtr importPath) const = 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.
};
class Compiler {
// Cross-links separate modules (schema files) and translates them into schema nodes.
public:
explicit Compiler();
~Compiler();
KJ_DISALLOW_COPY(Compiler);
enum Mode {
EAGER,
// Completely traverse the module's parse tree and translate it into schema nodes before
// returning from add().
LAZY
// Only interpret the module's definitions when they are requested. The main advantage of this
// mode is that imports will only be loaded if they are actually needed.
//
// Since the parse tree is traversed lazily, any particular schema node only becomes findable
// by ID (using the SchemaLoader) once one of its neighbors in the graph has been examined.
// As long as you are only traversing the graph -- only looking up IDs that you obtained from
// other schema nodes from the same loader -- you shouldn't be able to tell the difference.
// But if you receive IDs from some external source and want to look those up, you'd better
// use EAGER mode.
};
Schema add(Module<ParsedFile::Reader>& module, Mode mode) const;
// Add a module to the Compiler, returning its root Schema object.
const SchemaLoader& getLoader() const;
// Get a SchemaLoader backed by this compiler. Schema nodes will be lazily constructed as you
// traverse them using this loader.
private:
class Impl;
kj::Own<Impl> impl;
class CompiledModule;
class Node;
class Alias;
};
} // namespace compiler
} // namespace capnp
#endif // CAPNP_COMPILER_COMPILER_H_
......@@ -34,10 +34,18 @@ class ErrorReporter {
public:
virtual ~ErrorReporter() noexcept(false);
virtual void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) = 0;
virtual void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) const = 0;
// Report an error at the given location in the input text. `startByte` and `endByte` indicate
// the span of text that is erroneous. They may be equal, in which case the parser was only
// able to identify where the error begins, not where it ends.
template <typename T>
inline void addErrorOn(T&& decl, kj::StringPtr message) const {
// Works for any `T` that defines `getStartByte()` and `getEndByte()` methods, which many
// of the Cap'n Proto types defined in `grammar.capnp` do.
addError(decl.getStartByte(), decl.getEndByte(), message);
}
};
} // namespace compiler
......
......@@ -139,6 +139,7 @@ struct Declaration {
docComment @20 :Text;
body @6 union {
fileDecl @24 :File;
usingDecl @7 :Using;
constDecl @8 :Const;
enumDecl @9 :Enum;
......@@ -154,10 +155,38 @@ struct Declaration {
nakedId @21 :LocatedInteger;
nakedAnnotation @22 :AnnotationApplication;
# A floating UID or annotation (allowed at the file top level).
# The following declaration types are not produced by the parser, but are declared here
# so that the compiler can handle symbol name lookups more uniformly.
#
# New union members added here will magically become visible in the global scope.
# "builtinFoo" becomes visible as "Foo", while "builtinFooValue" becomes visible as "foo".
builtinVoid @25 :Void;
builtinBool @26 :Void;
builtinInt8 @27 :Void;
builtinInt16 @28 :Void;
builtinInt32 @29 :Void;
builtinInt64 @30 :Void;
builtinUInt8 @31 :Void;
builtinUInt16 @32 :Void;
builtinUInt32 @33 :Void;
builtinUInt64 @34 :Void;
builtinFloat32 @35 :Void;
builtinFloat64 @36 :Void;
builtinText @37 :Void;
builtinData @38 :Void;
builtinList @39 :Void;
builtinObject @40 :Void;
builtinTrueValue @41 :Void;
builtinFalseValue @42 :Void;
builtinVoidValue @43 :Void;
}
struct File {}
struct Using {
target @0 :TypeExpression;
target @0 :DeclName;
}
struct Const {
......
......@@ -31,7 +31,7 @@ namespace {
class TestFailingErrorReporter: public ErrorReporter {
public:
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) const override {
ADD_FAILURE() << "Parse failed: (" << startByte << "-" << endByte << ") " << message.cStr();
}
};
......
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "node-translator.h"
#include <kj/debug.h>
#include <kj/arena.h>
#include <set>
#include <map>
#include <limits.h>
namespace capnp {
namespace compiler {
class NodeTranslator::StructLayout {
// Massive, disgusting class which implements the layout algorithm, which decides the offset
// for each field.
public:
template <typename UIntType>
struct HoleSet {
inline HoleSet() {}
// Represents a set of "holes" within a segment of allocated space, up to one hole of each
// power-of-two size between 1 bit and 32 bits.
//
// The amount of "used" space in a struct's data segment can always be represented as a
// combination of a word count and a HoleSet. The HoleSet represents the space lost to
// "padding".
//
// There can never be more than one hole of any particular size. Why is this? Well, consider
// that every data field has a power-of-two size, every field must be aligned to a multiple of
// its size, and the maximum size of a single field is 64 bits. If we need to add a new field
// of N bits, there are two possibilities:
// 1. A hole of size N or larger exists. In this case, we find the smallest hole that is at
// least N bits. Let's say that that hole has size M. We allocate the first N bits of the
// hole to the new field. The remaining M - N bits become a series of holes of sizes N*2,
// N*4, ..., M / 2. We know no holes of these sizes existed before because we chose M to be
// the smallest available hole larger than N. So, there is still no more than one hole of
// each size, and no hole larger than any hole that existed previously.
// 2. No hole equal or larger N exists. In that case we extend the data section's size by one
// word, creating a new 64-bit hole at the end. We then allocate N bits from it, creating
// a series of holes between N and 64 bits, as described in point (1). Thus, again, there
// is still at most one hole of each size, and the largest hole is 32 bits.
UIntType holes[6] = {0, 0, 0, 0, 0, 0};
// The offset of each hole as a multiple of its size. A value of zero indicates that no hole
// exists. Notice that it is impossible for any actual hole to have an offset of zero, because
// the first field allocated is always placed at the very beginning of the section. So either
// the section has a size of zero (in which case there are no holes), or offset zero is
// already allocated and therefore cannot be a hole.
kj::Maybe<UIntType> tryAllocate(UIntType lgSize) {
// Try to find space for a field of size lgSize^2 within the set of holes. If found,
// remove it from the holes, and return its offset (as a multiple of its size). If there
// is no such space, returns zero (no hole can be at offset zero, as explained above).
if (holes[lgSize] != 0) {
UIntType result = holes[lgSize];
holes[lgSize] = 0;
return result;
} else {
KJ_IF_MAYBE(next, tryAllocate(lgSize + 1)) {
UIntType result = *next * 2;
holes[lgSize] = result + 1;
return result;
} else {
return nullptr;
}
}
}
uint assertHoleAndAllocate(UIntType lgSize) {
KJ_ASSERT(holes[lgSize] != 0);
uint result = holes[lgSize];
holes[lgSize] = 0;
return result;
}
void addHolesAtEnd(UIntType lgSize, UIntType offset,
UIntType limitLgSize = KJ_ARRAY_SIZE(holes)) {
// Add new holes of progressively larger sizes in the range [lgSize, limitLgSize) starting
// from the given offset. The idea is that you just allocated an lgSize-sized field from
// an limitLgSize-sized space, such as a newly-added word on the end of the data segment.
KJ_DREQUIRE(limitLgSize <= KJ_ARRAY_SIZE(holes));
while (lgSize < limitLgSize) {
KJ_DREQUIRE(holes[lgSize] == 0);
KJ_DREQUIRE(offset % 2 == 1);
holes[lgSize] = offset;
++lgSize;
offset = (offset + 1) / 2;
}
}
bool tryExpand(UIntType oldLgSize, uint oldOffset, uint expansionFactor) {
// Try to expand the value at the given location by combining it with subsequent holes, so
// as to expand the location to be 2^expansionFactor times the size that it started as.
// (In other words, the new lgSize is oldLgSize + expansionFactor.)
if (expansionFactor == 0) {
// No expansion requested.
return true;
}
if (holes[oldLgSize] != oldOffset + 1) {
// The space immediately after the location is not a hole.
return false;
}
// We can expand the location by one factor by combining it with a hole. Try to further
// expand from there to the number of factors requested.
if (tryExpand(oldLgSize + 1, oldOffset >> 1, expansionFactor - 1)) {
// Success. Consume the hole.
holes[oldLgSize] = 0;
return true;
} else {
return false;
}
}
kj::Maybe<int> smallestAtLeast(uint size) {
// Return the size of the smallest hole that is equal to or larger than the given size.
for (uint i = size; i < KJ_ARRAY_SIZE(holes); i++) {
if (holes[i] != 0) {
return i;
}
}
return nullptr;
}
};
struct StructOrGroup {
// Abstract interface for scopes in which fields can be added.
virtual uint addData(uint lgSize) = 0;
virtual uint addPointer() = 0;
virtual bool tryExpandData(uint oldLgSize, uint oldOffset, uint expansionFactor) = 0;
// Try to expand the given previously-allocated space by 2^expansionFactor. Succeeds --
// returning true -- if the following space happens to be empty, making this expansion possible.
// Otherwise, returns false.
};
struct Top: public StructOrGroup {
uint dataWordCount = 0;
uint pointerCount = 0;
// Size of the struct so far.
HoleSet<uint> holes;
uint addData(uint lgSize) override {
KJ_IF_MAYBE(hole, holes.tryAllocate(lgSize)) {
return *hole;
} else {
uint offset = dataWordCount++ << (6 - lgSize);
holes.addHolesAtEnd(lgSize, offset + 1);
return offset;
}
}
uint addPointer() override {
return pointerCount++;
}
bool tryExpandData(uint oldLgSize, uint oldOffset, uint expansionFactor) override {
return holes.tryExpand(oldLgSize, oldOffset, expansionFactor);
}
Top() = default;
KJ_DISALLOW_COPY(Top);
};
struct Union {
struct DataLocation {
uint lgSize;
uint offset;
bool tryExpandTo(Union& u, uint newLgSize) {
if (newLgSize <= lgSize) {
return true;
} else if (u.parent.tryExpandData(lgSize, offset, newLgSize - lgSize)) {
offset >>= (newLgSize - lgSize);
lgSize = newLgSize;
return true;
} else {
return false;
}
}
};
StructOrGroup& parent;
uint groupCount = 0;
kj::Maybe<int> discriminantOffset;
kj::Vector<DataLocation> dataLocations;
kj::Vector<uint> pointerLocations;
inline Union(StructOrGroup& parent): parent(parent) {}
KJ_DISALLOW_COPY(Union);
uint addNewDataLocation(uint lgSize) {
// Add a whole new data location to the union with the given size.
uint offset = parent.addData(lgSize);
dataLocations.add(DataLocation { lgSize, offset });
return offset;
}
uint addNewPointerLocation() {
// Add a whole new pointer location to the union with the given size.
return pointerLocations.add(parent.addPointer());
}
void newGroup() {
if (++groupCount == 2) {
addDiscriminant();
}
}
bool addDiscriminant() {
if (discriminantOffset == nullptr) {
discriminantOffset = parent.addData(4); // 2^4 = 16 bits
return true;
} else {
return false;
}
}
};
struct Group: public StructOrGroup {
public:
class DataLocationUsage {
public:
DataLocationUsage(): isUsed(false) {}
explicit DataLocationUsage(uint lgSize): isUsed(true), lgSizeUsed(lgSize) {}
kj::Maybe<uint> smallestHoleAtLeast(Union::DataLocation& location, uint lgSize) {
// Find the smallest single hole that is at least the given size. This is used to find the
// optimal place to allocate each field -- it is placed in the smallest slot where it fits,
// to reduce fragmentation.
if (!isUsed) {
// The location is effectively one big hole.
return location.lgSize;
} else if (lgSize >= lgSizeUsed) {
// Requested size is at least our current usage, so clearly won't fit in any current
// holes, but if the location's size is larger than what we're using, we'd be able to
// expand.
if (lgSize < location.lgSize) {
return lgSize;
} else {
return nullptr;
}
} else KJ_IF_MAYBE(result, holes.smallestAtLeast(lgSize)) {
// There's a hole.
return *result;
} else {
// The requested size is smaller than what we're already using, but there are no holes
// available. If we could double our size, then we could allocate in the new space.
if (lgSizeUsed < location.lgSize) {
// We effectively create a new hole the same size as the current usage.
return lgSizeUsed;
} else {
return nullptr;
}
}
}
uint allocateFromHole(Group& group, Union::DataLocation& location, uint lgSize) {
// Allocate the given space from an existing hole, given smallestHoleAtLeast() already
// returned non-null indicating such a hole exists.
uint result;
if (!isUsed) {
// The location is totally unused, so just allocate from the beginning.
KJ_DASSERT(lgSize <= location.lgSize, "Did smallestHoleAtLeast() really find a hole?");
result = 0;
isUsed = true;
lgSizeUsed = lgSize;
} else if (lgSize >= lgSizeUsed) {
// Requested size is at least our current usage, so clearly won't fit in any holes.
// We must expand to double the requested size, and return the second half.
KJ_DASSERT(lgSize < location.lgSize, "Did smallestHoleAtLeast() really find a hole?");
holes.addHolesAtEnd(lgSizeUsed, 1, lgSize);
lgSizeUsed = lgSize + 1;
result = 1;
} else KJ_IF_MAYBE(hole, holes.tryAllocate(lgSize)) {
// Found a hole.
result = *hole;
} else {
// The requested size is smaller than what we're using so far, but didn't fit in a
// hole. We should double our "used" size, then allocate from the new space.
KJ_DASSERT(lgSizeUsed < location.lgSize,
"Did smallestHoleAtLeast() really find a hole?");
result = 1 << (lgSizeUsed - lgSize);
holes.addHolesAtEnd(lgSize, result + 1, lgSizeUsed);
lgSizeUsed += 1;
}
// Adjust the offset according to the location's offset before returning.
uint locationOffset = location.offset << (location.lgSize - lgSize);
return locationOffset + result;
}
kj::Maybe<uint> tryAllocateByExpanding(
Group& group, Union::DataLocation& location, uint lgSize) {
// Attempt to allocate the given size by requesting that the parent union expand this
// location to fit. This is used if smallestHoleAtLeast() already determined that there
// are no holes that would fit, so we don't bother checking that.
if (!isUsed) {
if (location.tryExpandTo(group.parent, lgSize)) {
isUsed = true;
lgSizeUsed = lgSize;
return kj::implicitCast<uint>(0);
} else {
return nullptr;
}
} else {
uint newSize = kj::max(lgSizeUsed, lgSize) + 1;
if (tryExpandUsage(group, location, newSize)) {
return holes.assertHoleAndAllocate(lgSize);
} else {
return nullptr;
}
}
}
kj::Maybe<uint> tryAllocate(Group& group, Union::DataLocation& location, uint lgSize) {
if (isUsed) {
// We've already used some space in this location. Try to allocate from a hole.
uint result;
KJ_IF_MAYBE(hole, holes.tryAllocate(lgSize)) {
result = *hole;
} else {
// Failure. But perhaps we could expand the location to include a new hole which would
// be big enough for the value.
uint neededSizeUsed;
if (lgSize <= lgSizeUsed) {
// We are already at least as big as the desired size, so doubling should be good
// enough. The new value will be located just past the end of our current used
// space.
neededSizeUsed = lgSizeUsed + 1;
result = 1 << (lgSizeUsed - lgSize);
} else {
// We are smaller than the desired size, so we'll have to grow to 2x the desired size.
// The new value will be at an offset of 1x its own size.
neededSizeUsed = lgSize + 1;
result = 1;
}
if (!tryExpandUsage(group, location, neededSizeUsed)) {
return nullptr;
}
holes.addHolesAtEnd(lgSize, result + 1, neededSizeUsed - 1);
}
// OK, we found space. Adjust the offset according to the location's offset before
// returning.
uint locationOffset = location.offset << (location.lgSize - lgSize);
return locationOffset + result;
} else {
// We haven't used this location at all yet.
if (location.lgSize < lgSize) {
// Not enough space. Try to expand the location.
if (!location.tryExpandTo(group.parent, lgSize)) {
// Couldn't expand. This location is not viable.
return nullptr;
}
}
// Either the location was already big enough, or we expanded it.
KJ_DASSERT(location.lgSize >= lgSize);
// Just mark the first part used for now.
lgSizeUsed = lgSize;
// Return the offset, adjusted to be appropriate for the size.
return location.offset << (location.lgSize - lgSize);
}
}
bool tryExpand(Group& group, Union::DataLocation& location,
uint oldLgSize, uint oldOffset, uint expansionFactor) {
if (oldOffset == 0 && lgSizeUsed == oldLgSize) {
// This location contains exactly the requested data, so just expand the whole thing.
return tryExpandUsage(group, location, oldLgSize + expansionFactor);
} else {
// This location contains the requested data plus other stuff. Therefore the data cannot
// possibly expand past the end of the space we've already marked used without either
// overlapping with something else or breaking alignment rules. We only have to combine
// it with holes.
return holes.tryExpand(oldLgSize, oldOffset, expansionFactor);
}
}
private:
bool isUsed;
// Whether or not this location has been used at all by the group.
uint8_t lgSizeUsed;
// Amount of space from the location which is "used". This is the minimum size needed to
// cover all allocated space. Only meaningful if `isUsed` is true.
HoleSet<uint8_t> holes;
// Indicates holes present in the space designated by `lgSizeUsed`. The offsets in this
// HoleSet are relative to the beginning of this particular data location, not the beginning
// of the struct.
bool tryExpandUsage(Group& group, Union::DataLocation& location, uint desiredUsage) {
if (desiredUsage > location.lgSize) {
// Need to expand the underlying slot.
if (!location.tryExpandTo(group.parent, desiredUsage)) {
return false;
}
}
// Underlying slot is big enough, so expand our size and update holes.
holes.addHolesAtEnd(lgSizeUsed, 1, desiredUsage);
lgSizeUsed = desiredUsage;
return true;
}
};
Union& parent;
kj::Vector<DataLocationUsage> parentDataLocationUsage;
// Vector corresponding to the parent union's `dataLocations`, indicating how much of each
// location has already been allocated.
uint parentPointerLocationUsage = 0;
// Number of parent's pointer locations that have been used by this group.
inline Group(Union& parent): parent(parent) {
parent.newGroup();
}
KJ_DISALLOW_COPY(Group);
uint addData(uint lgSize) override {
uint bestSize = UINT_MAX;
kj::Maybe<uint> bestLocation = nullptr;
for (uint i = 0; i < parent.dataLocations.size(); i++) {
// If we haven't seen this DataLocation yet, add a corresponding DataLocationUsage.
if (parentDataLocationUsage.size() == i) {
parentDataLocationUsage.add();
}
auto& usage = parentDataLocationUsage[i];
KJ_IF_MAYBE(hole, usage.smallestHoleAtLeast(parent.dataLocations[i], lgSize)) {
if (*hole < bestSize) {
bestSize = *hole;
bestLocation = i;
}
}
}
KJ_IF_MAYBE(best, bestLocation) {
return parentDataLocationUsage[*best].allocateFromHole(
*this, parent.dataLocations[*best], lgSize);
}
// There are no holes at all in the union big enough to fit this field. Go back through all
// of the locations and attempt to expand them to fit.
for (uint i = 0; i < parent.dataLocations.size(); i++) {
KJ_IF_MAYBE(result, parentDataLocationUsage[i].tryAllocateByExpanding(
*this, parent.dataLocations[i], lgSize)) {
return *result;
}
}
// Couldn't find any space in the existing locations, so add a new one.
uint result = parent.addNewDataLocation(lgSize);
parentDataLocationUsage.add(lgSize);
return result;
}
uint addPointer() override {
if (parentPointerLocationUsage < parent.pointerLocations.size()) {
return parent.pointerLocations[parentPointerLocationUsage++];
} else {
parentPointerLocationUsage++;
return parent.addNewPointerLocation();
}
}
bool tryExpandData(uint oldLgSize, uint oldOffset, uint expansionFactor) override {
if (oldLgSize + expansionFactor > 6 ||
(oldOffset & ((1 << expansionFactor) - 1)) != 0) {
// Expansion is not possible because the new size is too large or the offset is not
// properly-aligned.
}
for (uint i = 0; i < parentDataLocationUsage.size(); i++) {
auto& location = parent.dataLocations[i];
if (location.lgSize >= oldLgSize &&
oldOffset >> (location.lgSize - oldLgSize) == location.offset) {
// The location we're trying to expand is a subset of this data location.
auto& usage = parentDataLocationUsage[i];
// Adjust the offset to be only within this location.
uint localOldOffset = oldOffset - (location.offset << (location.lgSize - oldLgSize));
// Try to expand.
return usage.tryExpand(*this, location, oldLgSize, localOldOffset, expansionFactor);
}
}
KJ_FAIL_ASSERT("Tried to expand field that was never allocated.");
return false;
}
};
Top& getTop();
private:
Top top;
};
// =======================================================================================
NodeTranslator::NodeTranslator(
const Resolver& resolver, const ErrorReporter& errorReporter,
const Declaration::Reader& decl, Orphan<schema::Node> wipNodeParam)
: resolver(resolver), errorReporter(errorReporter),
wipNode(kj::mv(wipNodeParam)) {
compileNode(decl, wipNode.get());
}
schema::Node::Reader NodeTranslator::finish() {
// Careful about iteration here: compileFinalValue() may actually add more elements to
// `unfinishedValues`, invalidating iterators in the process.
for (size_t i = 0; i < unfinishedValues.size(); i++) {
auto& value = unfinishedValues[i];
compileFinalValue(value.source, value.type, value.target);
}
return wipNode.getReader();
}
void NodeTranslator::compileNode(Declaration::Reader decl, schema::Node::Builder builder) {
checkMembers(decl.getNestedDecls(), decl.getBody().which());
switch (decl.getBody().which()) {
case Declaration::Body::FILE_DECL:
compileFile(decl, builder.getBody().initFileNode());
break;
case Declaration::Body::CONST_DECL:
compileConst(decl.getBody().getConstDecl(), builder.getBody().initConstNode());
break;
case Declaration::Body::ANNOTATION_DECL:
compileAnnotation(decl.getBody().getAnnotationDecl(), builder.getBody().initAnnotationNode());
break;
case Declaration::Body::ENUM_DECL:
compileEnum(decl.getBody().getEnumDecl(), decl.getNestedDecls(),
builder.getBody().initEnumNode());
break;
case Declaration::Body::STRUCT_DECL:
compileStruct(decl.getBody().getStructDecl(), decl.getNestedDecls(),
builder.getBody().initStructNode());
break;
case Declaration::Body::INTERFACE_DECL:
compileInterface(decl.getBody().getInterfaceDecl(), decl.getNestedDecls(),
builder.getBody().initInterfaceNode());
break;
default:
KJ_FAIL_REQUIRE("This Declaration is not a node.");
break;
}
// TODO(now): annotations
}
void NodeTranslator::checkMembers(
List<Declaration>::Reader nestedDecls, Declaration::Body::Which parentKind) {
std::map<uint, Declaration::Reader> ordinals;
std::map<kj::StringPtr, LocatedText::Reader> names;
for (auto decl: nestedDecls) {
{
auto name = decl.getName();
auto nameText = name.getValue();
auto insertResult = names.insert(std::make_pair(nameText, name));
if (!insertResult.second) {
errorReporter.addErrorOn(
name, kj::str("'", nameText, "' is already defined in this scope."));
errorReporter.addErrorOn(
insertResult.first->second, kj::str("'", nameText, "' previously defined here."));
}
}
switch (decl.getBody().which()) {
case Declaration::Body::USING_DECL:
case Declaration::Body::CONST_DECL:
case Declaration::Body::ENUM_DECL:
case Declaration::Body::STRUCT_DECL:
case Declaration::Body::INTERFACE_DECL:
case Declaration::Body::ANNOTATION_DECL:
switch (parentKind) {
case Declaration::Body::FILE_DECL:
case Declaration::Body::STRUCT_DECL:
case Declaration::Body::INTERFACE_DECL:
// OK.
break;
default:
errorReporter.addErrorOn(decl, "This kind of declaration doesn't belong here.");
break;
}
break;
case Declaration::Body::ENUMERANT_DECL:
if (parentKind != Declaration::Body::ENUM_DECL) {
errorReporter.addErrorOn(decl, "Enumerants can only appear in enums.");
}
break;
case Declaration::Body::METHOD_DECL:
if (parentKind != Declaration::Body::INTERFACE_DECL) {
errorReporter.addErrorOn(decl, "Methods can only appear in interfaces.");
}
break;
case Declaration::Body::FIELD_DECL:
case Declaration::Body::UNION_DECL:
case Declaration::Body::GROUP_DECL:
switch (parentKind) {
case Declaration::Body::STRUCT_DECL:
case Declaration::Body::UNION_DECL:
case Declaration::Body::GROUP_DECL:
// OK.
break;
default:
errorReporter.addErrorOn(decl, "This declaration can only appear in structs.");
break;
}
break;
default:
errorReporter.addErrorOn(decl, "This kind of declaration doesn't belong here.");
break;
}
}
}
void NodeTranslator::disallowNested(List<Declaration>::Reader nestedDecls) {
for (auto decl: nestedDecls) {
errorReporter.addErrorOn(decl, "Nested declaration not allowed here.");
}
}
static void findImports(DynamicValue::Reader value, std::set<kj::StringPtr>& output) {
switch (value.getType()) {
case DynamicValue::STRUCT: {
auto structValue = value.as<DynamicStruct>();
StructSchema schema = structValue.getSchema();
if (schema == Schema::from<DeclName>()) {
auto declName = structValue.as<DeclName>();
if (declName.getBase().which() == DeclName::Base::IMPORT_NAME) {
output.insert(declName.getBase().getImportName().getValue());
}
} else {
for (auto member: schema.getMembers()) {
if (structValue.has(member)) {
findImports(structValue.get(member), output);
}
}
}
break;
}
case DynamicValue::LIST:
for (auto element: value.as<DynamicList>()) {
findImports(element, output);
}
break;
default:
break;
}
}
void NodeTranslator::compileFile(Declaration::Reader decl, schema::FileNode::Builder builder) {
std::set<kj::StringPtr> imports;
findImports(decl, imports);
auto list = builder.initImports(imports.size());
auto iter = imports.begin();
for (auto element: list) {
element.setName(*iter++);
}
KJ_ASSERT(iter == imports.end());
}
void NodeTranslator::compileConst(Declaration::Const::Reader decl,
schema::ConstNode::Builder builder) {
auto typeBuilder = builder.initType();
if (compileType(decl.getType(), typeBuilder)) {
compileBootstrapValue(decl.getValue(), typeBuilder.asReader(), builder.initValue());
}
}
void NodeTranslator::compileAnnotation(Declaration::Annotation::Reader decl,
schema::AnnotationNode::Builder builder) {
compileType(decl.getType(), builder.initType());
// Dynamically copy over the values of all of the "targets" members.
DynamicStruct::Reader src = decl;
DynamicStruct::Builder dst = builder;
for (auto srcMember: src.getSchema().getMembers()) {
kj::StringPtr memberName = srcMember.getProto().getName();
if (memberName.startsWith("targets")) {
auto dstMember = dst.getSchema().getMemberByName(memberName);
dst.set(dstMember, src.get(srcMember));
}
}
}
class NodeTranslator::DuplicateOrdinalDetector {
public:
DuplicateOrdinalDetector(const ErrorReporter& errorReporter): errorReporter(errorReporter) {}
void check(LocatedInteger::Reader ordinal) {
if (ordinal.getValue() < expectedOrdinal) {
errorReporter.addErrorOn(ordinal, "Duplicate ordinal number.");
KJ_IF_MAYBE(last, lastOrdinalLocation) {
errorReporter.addErrorOn(
*last, kj::str("Ordinal @", last->getValue(), " originally used here."));
// Don't report original again.
lastOrdinalLocation = nullptr;
}
} else if (ordinal.getValue() > expectedOrdinal) {
errorReporter.addErrorOn(ordinal,
kj::str("Skipped ordinal @", expectedOrdinal, ". Ordinals must be sequential with no "
"holes."));
} else {
++expectedOrdinal;
lastOrdinalLocation = ordinal;
}
}
private:
const ErrorReporter& errorReporter;
uint expectedOrdinal = 0;
kj::Maybe<LocatedInteger::Reader> lastOrdinalLocation;
};
void NodeTranslator::compileEnum(Declaration::Enum::Reader decl,
List<Declaration>::Reader members,
schema::EnumNode::Builder builder) {
// maps ordinal -> (code order, declaration)
std::multimap<uint, std::pair<uint, Declaration::Reader>> enumerants;
uint codeOrder = 0;
for (auto member: members) {
if (member.getBody().which() == Declaration::Body::ENUMERANT_DECL) {
enumerants.insert(
std::make_pair(member.getId().getOrdinal().getValue(),
std::make_pair(codeOrder++, member)));
}
}
auto list = builder.initEnumerants(enumerants.size());
uint i = 0;
DuplicateOrdinalDetector dupDetector(errorReporter);
for (auto& entry: enumerants) {
uint codeOrder = entry.second.first;
Declaration::Reader enumerantDecl = entry.second.second;
dupDetector.check(enumerantDecl.getId().getOrdinal());
auto enumerantBuilder = list[i];
enumerantBuilder.setName(enumerantDecl.getName().getValue());
enumerantBuilder.setCodeOrder(codeOrder);
enumerantBuilder.adoptAnnotations(compileAnnotationApplications(
enumerantDecl.getAnnotations(), "targetsEnumerant"));
}
}
// -------------------------------------------------------------------
class NodeTranslator::StructTranslator {
public:
explicit StructTranslator(NodeTranslator& translator)
: translator(translator), errorReporter(translator.errorReporter) {}
KJ_DISALLOW_COPY(StructTranslator);
void translate(Declaration::Struct::Reader decl, List<Declaration>::Reader members,
schema::StructNode::Builder builder) {
// Build the member-info-by-ordinal map.
MemberInfo root(layout.getTop());
traverseGroup(members, root);
// Init the root.
root.memberSchemas = builder.initMembers(root.childCount);
// Go through each member in ordinal order, building each member schema.
DuplicateOrdinalDetector dupDetector(errorReporter);
for (auto& entry: membersByOrdinal) {
MemberInfo& member = *entry.second;
if (member.decl.getId().which() == Declaration::Id::ORDINAL) {
dupDetector.check(member.decl.getId().getOrdinal());
}
schema::StructNode::Member::Builder builder = member.parent->getMemberSchema(member.index);
builder.setName(member.decl.getName().getValue());
builder.setOrdinal(entry.first);
builder.setCodeOrder(member.codeOrder);
kj::StringPtr targetsFlagName;
switch (member.decl.getBody().which()) {
case Declaration::Body::FIELD_DECL: {
auto fieldReader = member.decl.getBody().getFieldDecl();
auto fieldBuilder = builder.getBody().initFieldMember();
auto typeBuilder = fieldBuilder.initType();
if (translator.compileType(fieldReader.getType(), typeBuilder)) {
switch (fieldReader.getDefaultValue().which()) {
case Declaration::Field::DefaultValue::VALUE:
translator.compileBootstrapValue(fieldReader.getDefaultValue().getValue(),
typeBuilder, fieldBuilder.initDefaultValue());
break;
case Declaration::Field::DefaultValue::NONE:
translator.compileDefaultDefaultValue(typeBuilder, fieldBuilder.initDefaultValue());
break;
}
int lgSize = -1;
switch (typeBuilder.getBody().which()) {
case schema::Type::Body::VOID_TYPE: lgSize = -1; break;
case schema::Type::Body::BOOL_TYPE: lgSize = 0; break;
case schema::Type::Body::INT8_TYPE: lgSize = 3; break;
case schema::Type::Body::INT16_TYPE: lgSize = 4; break;
case schema::Type::Body::INT32_TYPE: lgSize = 5; break;
case schema::Type::Body::INT64_TYPE: lgSize = 6; break;
case schema::Type::Body::UINT8_TYPE: lgSize = 3; break;
case schema::Type::Body::UINT16_TYPE: lgSize = 4; break;
case schema::Type::Body::UINT32_TYPE: lgSize = 5; break;
case schema::Type::Body::UINT64_TYPE: lgSize = 6; break;
case schema::Type::Body::FLOAT32_TYPE: lgSize = 5; break;
case schema::Type::Body::FLOAT64_TYPE: lgSize = 6; break;
case schema::Type::Body::TEXT_TYPE: lgSize = -2; break;
case schema::Type::Body::DATA_TYPE: lgSize = -2; break;
case schema::Type::Body::LIST_TYPE: lgSize = -2; break;
case schema::Type::Body::ENUM_TYPE: lgSize = 4; break;
case schema::Type::Body::STRUCT_TYPE: lgSize = -2; break;
case schema::Type::Body::INTERFACE_TYPE: lgSize = -2; break;
case schema::Type::Body::OBJECT_TYPE: lgSize = -2; break;
}
if (lgSize == -2) {
// pointer
fieldBuilder.setOffset(member.fieldScope->addPointer());
} else if (lgSize == -1) {
// void
fieldBuilder.setOffset(0);
} else {
fieldBuilder.setOffset(member.fieldScope->addData(lgSize));
}
}
targetsFlagName = "targetsField";
break;
}
case Declaration::Body::UNION_DECL:
if (member.decl.getId().which() == Declaration::Id::ORDINAL) {
if (!member.unionScope->addDiscriminant()) {
errorReporter.addErrorOn(member.decl.getId().getOrdinal(),
"Union ordinal, if specified, must be greater than no more than one of its "
"member ordinals (i.e. there can only be one field retroactively unionized).");
}
}
lateUnions.add(&member);
// No need to fill in members as this is done automatically elsewhere.
targetsFlagName = "targetsUnion";
break;
case Declaration::Body::GROUP_DECL:
// Nothing to do here; members are filled in automatically elsewhere.
targetsFlagName = "targetsGroup";
break;
default:
KJ_FAIL_ASSERT("Unexpected member type.");
break;
}
builder.adoptAnnotations(translator.compileAnnotationApplications(
member.decl.getAnnotations(), targetsFlagName));
}
// OK, all members are built. The only thing left is the late unions.
for (auto member: lateUnions) {
member->unionScope->addDiscriminant(); // if it hasn't happened already
KJ_IF_MAYBE(offset, member->unionScope->discriminantOffset) {
member->parent->getMemberSchema(member->index).getBody().getUnionMember()
.setDiscriminantOffset(*offset);
} else {
KJ_FAIL_ASSERT("addDiscriminant() didn't set the offset?");
}
}
}
private:
NodeTranslator& translator;
const ErrorReporter& errorReporter;
StructLayout layout;
kj::Arena arena;
struct MemberInfo {
MemberInfo* parent;
// The MemberInfo for the parent scope.
uint codeOrder;
// Code order within the parent.
uint childCount = 0;
// Number of children this member has.
uint index = 0;
Declaration::Reader decl;
List<schema::StructNode::Member>::Builder memberSchemas;
union {
StructLayout::StructOrGroup* fieldScope;
// If this member is a field, the scope of that field. This will be used to assign an
// offset for the field when going through in ordinal order.
//
// If the member is a group, this is is the group itself.
StructLayout::Union* unionScope;
// If this member is a union, this is the union. This will be used to assign a discriminant
// offset.
};
inline MemberInfo(StructLayout::Top& topScope)
: parent(nullptr), codeOrder(0), fieldScope(&topScope) {}
inline MemberInfo(MemberInfo& parent, uint codeOrder,
const Declaration::Reader& decl,
StructLayout::StructOrGroup& fieldScope)
: parent(&parent), codeOrder(codeOrder), decl(decl), fieldScope(&fieldScope) {}
inline MemberInfo(MemberInfo& parent, uint codeOrder,
const Declaration::Reader& decl, StructLayout::Union& unionScope)
: parent(&parent), codeOrder(codeOrder), decl(decl), unionScope(&unionScope) {}
schema::StructNode::Member::Builder getMemberSchema(uint childIndex) {
// Get the schema builder for the child member at the given index. This lazily/dynamically
// builds the builder tree.
KJ_REQUIRE(childIndex < childCount);
if (memberSchemas.size() == 0) {
switch (decl.getBody().which()) {
case Declaration::Body::FIELD_DECL:
KJ_FAIL_ASSERT("Fields don't have members.");
break;
case Declaration::Body::UNION_DECL:
memberSchemas = parent->getMemberSchema(index).getBody()
.initUnionMember().initMembers(childCount);
break;
case Declaration::Body::GROUP_DECL:
memberSchemas = parent->getMemberSchema(index).getBody()
.initGroupMember().initMembers(childCount);
break;
default:
KJ_FAIL_ASSERT("Unexpected member type.");
break;
}
}
return memberSchemas[childIndex];
}
};
std::multimap<uint, MemberInfo*> membersByOrdinal;
// For fields, the key is the ordinal. For unions and groups, the key is the lowest ordinal
// number among their members, or the union's explicit ordinal number if it has one.
kj::Vector<MemberInfo*> lateUnions;
// Unions that need to have their discriminant offsets filled in after layout is complete.
uint traverseUnion(List<Declaration>::Reader members, MemberInfo& parent) {
uint minOrdinal = UINT_MAX;
uint codeOrder = 0;
if (members.size() < 2) {
errorReporter.addErrorOn(parent.decl, "Union must have at least two members.");
}
for (auto member: members) {
uint ordinal = 0;
MemberInfo* memberInfo = nullptr;
switch (member.getBody().which()) {
case Declaration::Body::FIELD_DECL: {
StructLayout::Group& singletonGroup =
arena.allocate<StructLayout::Group>(*parent.unionScope);
memberInfo = &arena.allocate<MemberInfo>(parent, codeOrder++, member, singletonGroup);
ordinal = member.getId().getOrdinal().getValue();
break;
}
case Declaration::Body::UNION_DECL:
errorReporter.addErrorOn(member, "Unions cannot contain unions.");
break;
case Declaration::Body::GROUP_DECL: {
StructLayout::Group& group =
arena.allocate<StructLayout::Group>(*parent.unionScope);
memberInfo = &arena.allocate<MemberInfo>(parent, codeOrder++, member, group);
ordinal = traverseGroup(member.getNestedDecls(), *memberInfo);
break;
}
default:
// Ignore others.
break;
}
if (memberInfo != nullptr) {
memberInfo->index = parent.childCount++;
membersByOrdinal.insert(std::make_pair(ordinal, memberInfo));
minOrdinal = kj::min(minOrdinal, ordinal);
}
}
return minOrdinal;
}
uint traverseGroup(List<Declaration>::Reader members, MemberInfo& parent) {
uint minOrdinal = UINT_MAX;
uint codeOrder = 0;
if (members.size() < 2) {
errorReporter.addErrorOn(parent.decl, "Group must have at least two members.");
}
for (auto member: members) {
uint ordinal = 0;
MemberInfo* memberInfo = nullptr;
switch (member.getBody().which()) {
case Declaration::Body::FIELD_DECL: {
memberInfo = &arena.allocate<MemberInfo>(
parent, codeOrder++, member, *parent.fieldScope);
break;
}
case Declaration::Body::UNION_DECL: {
StructLayout::Union& unionLayout = arena.allocate<StructLayout::Union>(
*parent.fieldScope);
memberInfo = &arena.allocate<MemberInfo>(
parent, codeOrder++, member, unionLayout);
ordinal = traverseUnion(member.getNestedDecls(), *memberInfo);
if (member.getId().which() == Declaration::Id::ORDINAL) {
ordinal = member.getId().getOrdinal().getValue();
}
break;
}
case Declaration::Body::GROUP_DECL:
errorReporter.addErrorOn(member, "Groups should only appear inside unions.");
break;
default:
// Ignore others.
break;
}
if (memberInfo != nullptr) {
memberInfo->index = parent.childCount++;
membersByOrdinal.insert(std::make_pair(ordinal, memberInfo));
minOrdinal = kj::min(minOrdinal, ordinal);
}
}
return minOrdinal;
}
};
void NodeTranslator::compileStruct(Declaration::Struct::Reader decl,
List<Declaration>::Reader members,
schema::StructNode::Builder builder) {
}
// -------------------------------------------------------------------
void NodeTranslator::compileInterface(Declaration::Interface::Reader decl,
List<Declaration>::Reader members,
schema::InterfaceNode::Builder builder) {
}
void NodeTranslator::compileBootstrapValue(ValueExpression::Reader source,
schema::Type::Reader type,
schema::Value::Builder target) {
}
void NodeTranslator::compileFinalValue(ValueExpression::Reader source,
schema::Type::Reader type, schema::Value::Builder target) {
}
Orphan<List<schema::Annotation>> NodeTranslator::compileAnnotationApplications(
List<Declaration::AnnotationApplication>::Reader annotations,
kj::StringPtr targetsFlagName) {
}
} // namespace compiler
} // namespace capnp
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CAPNP_COMPILER_NODE_TRANSLATOR_H_
#define CAPNP_COMPILER_NODE_TRANSLATOR_H_
#include <capnp/orphan.h>
#include <capnp/compiler/grammar.capnp.h>
#include <capnp/schema.capnp.h>
#include <capnp/schema-loader.h>
#include <capnp/dynamic.h>
#include <kj/vector.h>
#include "error-reporter.h"
namespace capnp {
namespace compiler {
class NodeTranslator {
// Translates one node in the schema from AST form to final schema form. A "node" is anything
// that has a unique ID, such as structs, enums, constants, and annotations, but not fields,
// unions, enumerants, or methods (the latter set have 16-bit ordinals but not 64-bit global IDs).
public:
class Resolver {
// Callback class used to find other nodes relative to this one.
public:
struct ResolvedName {
uint64_t id;
Declaration::Body::Which kind;
};
virtual kj::Maybe<ResolvedName> resolve(const DeclName::Reader& name) const = 0;
// Look up the given name, relative to this node, and return basic information about the
// target.
virtual kj::Maybe<Schema> resolveMaybeBootstrapSchema(uint64_t id) const = 0;
// Get the schema for the given ID. Returning either a bootstrap schema or a final schema
// is acceptable.
virtual kj::Maybe<Schema> resolveFinalSchema(uint64_t id) const = 0;
// Get the final schema for the given ID. A bootstrap schema is not acceptable.
};
NodeTranslator(const Resolver& resolver, const ErrorReporter& errorReporter,
const Declaration::Reader& decl, Orphan<schema::Node> wipNode);
// Construct a NodeTranslator to translate the given declaration. The wipNode starts out with
// `displayName`, `id`, `scopeId`, and `nestedNodes` already initialized. The `NodeTranslator`
// fills in the rest.
schema::Node::Reader getBootstrapNode() { return wipNode.getReader(); }
// Get an incomplete version of the node in which pointer-typed value expressions have not yet
// been translated. Instead, for all `schema.Value` objects representing pointer-type values,
// the value is set to an appropriate "empty" value. This version of the schema can be used to
// bootstrap the dynamic API which can then in turn be used to encode the missing complex values.
//
// If the final node has already been built, this will actually return the final node (in fact,
// it's the same node object).
schema::Node::Reader finish();
// Finish translating the node (including filling in all the pieces that are missing from the
// bootstrap node) and return it.
private:
const Resolver& resolver;
const ErrorReporter& errorReporter;
Orphan<schema::Node> wipNode;
// The work-in-progress schema node.
struct UnfinishedValue {
ValueExpression::Reader source;
schema::Type::Reader type;
schema::Value::Builder target;
};
kj::Vector<UnfinishedValue> unfinishedValues;
// List of values in `wipNode` which have not yet been interpreted, because they are structs
// or lists and as such interpreting them require using the types' schemas (to take advantage
// of the dynamic API). Once bootstrap schemas have been built, they can be used to interpret
// these values.
void compileNode(Declaration::Reader decl, schema::Node::Builder builder);
void checkMembers(List<Declaration>::Reader nestedDecls, Declaration::Body::Which parentKind);
// Check the given member list for errors, including detecting duplicate names and detecting
// out-of-place declarations.
void disallowNested(List<Declaration>::Reader nestedDecls);
// Complain if the nested decl list is non-empty.
void compileFile(Declaration::Reader decl, schema::FileNode::Builder builder);
void compileConst(Declaration::Const::Reader decl, schema::ConstNode::Builder builder);
void compileAnnotation(Declaration::Annotation::Reader decl,
schema::AnnotationNode::Builder builder);
class DuplicateOrdinalDetector;
class StructLayout;
class StructTranslator;
void compileEnum(Declaration::Enum::Reader decl, List<Declaration>::Reader members,
schema::EnumNode::Builder builder);
void compileStruct(Declaration::Struct::Reader decl, List<Declaration>::Reader members,
schema::StructNode::Builder builder);
void compileInterface(Declaration::Interface::Reader decl, List<Declaration>::Reader members,
schema::InterfaceNode::Builder builder);
// The `members` arrays contain only members with ordinal numbers, in code order. Other members
// are handled elsewhere.
bool compileType(TypeExpression::Reader source, schema::Type::Builder target);
// Returns false if there was a problem, in which case value expressions of this type should
// not be parsed.
void compileDefaultDefaultValue(schema::Type::Reader type, schema::Value::Builder target);
// Initializes `target` to contain the "default default" value for `type`.
void compileBootstrapValue(ValueExpression::Reader source, schema::Type::Reader type,
schema::Value::Builder target);
// Interprets the value expression and initializes `target` with the result. If some parts of
// the value cannot be built at bootstrap time, they'll be added to `unfinishedValues`
// automatically for later processing.
void compileFinalValue(ValueExpression::Reader source,
schema::Type::Reader type, schema::Value::Builder target);
// Compile a previously-unfinished value. See `unfinishedValues`.
Orphan<List<schema::Annotation>> compileAnnotationApplications(
List<Declaration::AnnotationApplication>::Reader annotations,
kj::StringPtr targetsFlagName);
};
} // namespace compiler
} // namespace capnp
#endif // CAPNP_COMPILER_NODE_TRANSLATOR_H_
......@@ -57,6 +57,7 @@ void parseFile(List<Statement>::Reader statements, ParsedFile::Builder result,
kj::Vector<Orphan<Declaration::AnnotationApplication>> annotations;
auto fileDecl = result.getRoot();
fileDecl.getBody().initFileDecl();
for (auto statement: statements) {
KJ_IF_MAYBE(decl, parser.parseStatement(statement, parser.getParsers().fileLevelDecl)) {
......@@ -556,13 +557,13 @@ CapnpParser::CapnpParser(Orphanage orphanageParam, ErrorReporter& errorReporterP
// -----------------------------------------------------------------
parsers.usingDecl = arena.copy(p::transform(
p::sequence(keyword("using"), identifier, op("="), parsers.typeExpression),
[this](Located<Text::Reader>&& name, Orphan<TypeExpression>&& type) -> DeclParserResult {
p::sequence(keyword("using"), identifier, op("="), parsers.declName),
[this](Located<Text::Reader>&& name, Orphan<DeclName>&& target) -> DeclParserResult {
auto decl = orphanage.newOrphan<Declaration>();
auto builder = decl.get();
name.copyTo(builder.initName());
// no id, no annotations for using decl
builder.getBody().initUsingDecl().adoptTarget(kj::mv(type));
builder.getBody().initUsingDecl().adoptTarget(kj::mv(target));
return DeclParserResult(kj::mv(decl));
}));
......
......@@ -151,7 +151,17 @@ struct FileNode {
imports @0 :List(Import);
struct Import {
id @0 :Id;
# ID of the imported file.
# DEPRECATED: ID of the imported file. This is no longer filled in because it is hostile to
# lazy importing: since this import list appears in the FileNode, and since the FileNode must
# necessarily be cosntructed if any schemas in the file are used, the implication of listing
# import IDs here is that if a schema file is used at all, all of its imports must be parsed,
# just to get their IDs. We'd much rather delay parsing a file until something inside it is
# actually used.
#
# In any case, this import list's main reason for existing is to make it easy to generate
# the appropriate #include statements in C++. The IDs of files aren't needed for that.
#
# TODO(someday): Perhaps provide an alternative way to identify the remote file.
name @1 :Text;
# Name which *this* file used to refer to the foreign file. This may be a relative name.
......@@ -197,6 +207,11 @@ struct StructNode {
name @0 :Text;
ordinal @1 :UInt16;
# For fields, the ordinal number. For unions, if an explicit ordinal was given, that number.
# Otherwise, for unions and groups, this is the ordinal of the lowest-numbered field in the
# union/group.
#
# TODO(someday): When revamping the meta-schema, move this into Field.
codeOrder @2 :UInt16;
# Indicates where this member appeared in the code, relative to other members.
......@@ -214,6 +229,7 @@ struct StructNode {
fieldMember @5 :Field;
unionMember @6 :Union;
groupMember @7 :Group;
}
}
......@@ -236,6 +252,10 @@ struct StructNode {
# consumers should skip member types that they don't understand. The first member in this list
# gets discriminant value zero, the next gets one, and so on.
}
struct Group {
members @0 :List(Member);
}
}
struct EnumNode {
......
......@@ -325,10 +325,10 @@ public:
}
template <typename... Params>
void add(Params&&... params) {
T& add(Params&&... params) {
KJ_IREQUIRE(pos < endPtr, "Added too many elements to ArrayBuilder.");
ctor(*pos, kj::fwd<Params>(params)...);
++pos;
return *pos++;
}
template <typename Container>
......
......@@ -70,9 +70,9 @@ public:
}
template <typename... Params>
inline void add(Params&&... params) {
inline T& add(Params&&... params) {
if (builder.isFull()) grow();
builder.add(kj::fwd<Params>(params)...);
return builder.add(kj::fwd<Params>(params)...);
}
template <typename Iterator>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment