From 570ff3e2c3f64b5e4b4b54b3af4bdc9db0a013a5 Mon Sep 17 00:00:00 2001 From: Adam Rosenberger Date: Thu, 15 May 2014 19:05:20 -0400 Subject: [PATCH] Add SBT as build system, add Java package annotation for generated code --- .gitignore | 6 + .../capnproto/examples}/AddressbookMain.java | 2 + .../{ => src/main/schema}/addressbook.capnp | 0 .../src/main/cpp/compiler/capnpc-java.c++ | 1639 +++++++++++++++++ .../main/cpp/compiler/java_support/java.capnp | 3 + .../cpp/compiler/java_support/java.capnp.c++ | 35 + .../cpp/compiler/java_support/java.capnp.h | 36 + .../main/java/org/capnproto/AnyPointer.java | 17 + .../java/org/capnproto/DecodeException.java | 7 + .../main/java/org/capnproto/FieldSize.java | 12 + .../java/org/capnproto/FromStructBuilder.java | 6 + .../java/org/capnproto/FromStructReader.java | 5 + .../capnproto/InputStreamMessageReader.java | 84 + .../main/java/org/capnproto/ListBuilder.java | 24 + .../main/java/org/capnproto/ListPointer.java | 17 + .../main/java/org/capnproto/ListReader.java | 52 + .../java/org/capnproto/MessageBuilder.java | 11 + .../java/org/capnproto/MessageReader.java | 20 + .../java/org/capnproto/PointerBuilder.java | 20 + .../java/org/capnproto/PointerReader.java | 49 + .../java/org/capnproto/SegmentBuilder.java | 34 + .../java/org/capnproto/SegmentReader.java | 11 + .../java/org/capnproto/StructBuilder.java | 42 + .../main/java/org/capnproto/StructList.java | 34 + .../java/org/capnproto/StructPointer.java | 17 + .../main/java/org/capnproto/StructReader.java | 71 + .../main/java/org/capnproto/StructSize.java | 18 + .../src/main/java/org/capnproto/Text.java | 45 + .../main/java/org/capnproto/WireHelpers.java | 174 ++ .../main/java/org/capnproto/WirePointer.java | 51 + .../main/java/org/capnproto/WordPointer.java | 13 + project/build.properties | 1 + project/build.scala | 89 + project/plugins.sbt | 3 + version.sbt | 1 + 35 files changed, 2649 insertions(+) create mode 100644 .gitignore rename examples/{ => src/main/java/org/capnproto/examples}/AddressbookMain.java (98%) rename examples/{ => src/main/schema}/addressbook.capnp (100%) create mode 100644 generator/src/main/cpp/compiler/capnpc-java.c++ create mode 100644 generator/src/main/cpp/compiler/java_support/java.capnp create mode 100644 generator/src/main/cpp/compiler/java_support/java.capnp.c++ create mode 100644 generator/src/main/cpp/compiler/java_support/java.capnp.h create mode 100644 generator/src/main/java/org/capnproto/AnyPointer.java create mode 100644 generator/src/main/java/org/capnproto/DecodeException.java create mode 100644 generator/src/main/java/org/capnproto/FieldSize.java create mode 100644 generator/src/main/java/org/capnproto/FromStructBuilder.java create mode 100644 generator/src/main/java/org/capnproto/FromStructReader.java create mode 100644 generator/src/main/java/org/capnproto/InputStreamMessageReader.java create mode 100644 generator/src/main/java/org/capnproto/ListBuilder.java create mode 100644 generator/src/main/java/org/capnproto/ListPointer.java create mode 100644 generator/src/main/java/org/capnproto/ListReader.java create mode 100644 generator/src/main/java/org/capnproto/MessageBuilder.java create mode 100644 generator/src/main/java/org/capnproto/MessageReader.java create mode 100644 generator/src/main/java/org/capnproto/PointerBuilder.java create mode 100644 generator/src/main/java/org/capnproto/PointerReader.java create mode 100644 generator/src/main/java/org/capnproto/SegmentBuilder.java create mode 100644 generator/src/main/java/org/capnproto/SegmentReader.java create mode 100644 generator/src/main/java/org/capnproto/StructBuilder.java create mode 100644 generator/src/main/java/org/capnproto/StructList.java create mode 100644 generator/src/main/java/org/capnproto/StructPointer.java create mode 100644 generator/src/main/java/org/capnproto/StructReader.java create mode 100644 generator/src/main/java/org/capnproto/StructSize.java create mode 100644 generator/src/main/java/org/capnproto/Text.java create mode 100644 generator/src/main/java/org/capnproto/WireHelpers.java create mode 100644 generator/src/main/java/org/capnproto/WirePointer.java create mode 100644 generator/src/main/java/org/capnproto/WordPointer.java create mode 100644 project/build.properties create mode 100644 project/build.scala create mode 100644 project/plugins.sbt create mode 100644 version.sbt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22e1a96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +target/ +project/target +*.iml +.idea +*.ipr +*.iws diff --git a/examples/AddressbookMain.java b/examples/src/main/java/org/capnproto/examples/AddressbookMain.java similarity index 98% rename from examples/AddressbookMain.java rename to examples/src/main/java/org/capnproto/examples/AddressbookMain.java index 542cbd9..a190733 100644 --- a/examples/AddressbookMain.java +++ b/examples/src/main/java/org/capnproto/examples/AddressbookMain.java @@ -1,3 +1,5 @@ +package org.capnproto.examples; + import org.capnproto.MessageReader; import org.capnproto.StructList; import org.capnproto.InputStreamMessageReader; diff --git a/examples/addressbook.capnp b/examples/src/main/schema/addressbook.capnp similarity index 100% rename from examples/addressbook.capnp rename to examples/src/main/schema/addressbook.capnp diff --git a/generator/src/main/cpp/compiler/capnpc-java.c++ b/generator/src/main/cpp/compiler/capnpc-java.c++ new file mode 100644 index 0000000..f6ee5bc --- /dev/null +++ b/generator/src/main/cpp/compiler/capnpc-java.c++ @@ -0,0 +1,1639 @@ +// Copyright (c) 2013, Kenton Varda +// 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. + + +// This program is a code generator plugin for `capnp compile` which generates java code. +// It is a modified version of the C++ code generator plugin, capnpc-c++. + + +#include +#include "capnp/serialize.h" +#include +#include +#include +#include +#include "capnp/schema-loader.h" +#include "capnp/dynamic.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef VERSION +#define VERSION "(unknown)" +#endif + +namespace capnp { +namespace { + +static constexpr uint64_t NAMESPACE_ANNOTATION_ID = 0xb9c6f99ebf805f2cull; + +static constexpr const char* FIELD_SIZE_NAMES[] = { + "VOID", "BIT", "BYTE", "TWO_BYTES", "FOUR_BYTES", "EIGHT_BYTES", "POINTER", "INLINE_COMPOSITE" +}; + +bool hasDiscriminantValue(const schema::Field::Reader& reader) { + return reader.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT; +} + +void enumerateDeps(schema::Type::Reader type, std::set& deps) { + switch (type.which()) { + case schema::Type::STRUCT: + deps.insert(type.getStruct().getTypeId()); + break; + case schema::Type::ENUM: + deps.insert(type.getEnum().getTypeId()); + break; + case schema::Type::INTERFACE: + deps.insert(type.getInterface().getTypeId()); + break; + case schema::Type::LIST: + enumerateDeps(type.getList().getElementType(), deps); + break; + default: + break; + } +} + +void enumerateDeps(schema::Node::Reader node, std::set& deps) { + switch (node.which()) { + case schema::Node::STRUCT: { + auto structNode = node.getStruct(); + for (auto field: structNode.getFields()) { + switch (field.which()) { + case schema::Field::SLOT: + enumerateDeps(field.getSlot().getType(), deps); + break; + case schema::Field::GROUP: + deps.insert(field.getGroup().getTypeId()); + break; + } + } + if (structNode.getIsGroup()) { + deps.insert(node.getScopeId()); + } + break; + } + case schema::Node::INTERFACE: { + auto interfaceNode = node.getInterface(); + for (auto extend: interfaceNode.getExtends()) { + deps.insert(extend); + } + for (auto method: interfaceNode.getMethods()) { + deps.insert(method.getParamStructType()); + deps.insert(method.getResultStructType()); + } + break; + } + default: + break; + } +} + +struct OrderByName { + template + inline bool operator()(const T& a, const T& b) const { + return a.getProto().getName() < b.getProto().getName(); + } +}; + +template +kj::Array makeMembersByName(MemberList&& members) { + auto sorted = KJ_MAP(member, members) { return member; }; + std::sort(sorted.begin(), sorted.end(), OrderByName()); + return KJ_MAP(member, sorted) { return member.getIndex(); }; +} + +kj::StringPtr baseName(kj::StringPtr path) { + KJ_IF_MAYBE(slashPos, path.findLast('/')) { + return path.slice(*slashPos + 1); + } else { + return path; + } +} + +kj::String safeIdentifier(kj::StringPtr identifier) { + // Given a desired identifier name, munge it to make it safe for use in generated code. + // + // If the identifier is a keyword, this adds an underscore to the end. + + static const std::set keywords({ + "alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand", "bitor", "bool", "break", + "case", "catch", "char", "char16_t", "char32_t", "class", "compl", "const", "constexpr", + "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", + "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", + "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept", "not", "not_eq", + "nullptr", "operator", "or", "or_eq", "private", "protected", "public", "register", + "reinterpret_cast", "return", "short", "signed", "sizeof", "static", "static_assert", + "static_cast", "struct", "switch", "template", "this", "thread_local", "throw", "true", + "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", + "volatile", "wchar_t", "while", "xor", "xor_eq" + }); + + if (keywords.count(identifier) > 0) { + return kj::str(identifier, '_'); + } else { + return kj::heapString(identifier); + } +} + + kj::String spaces(int n) { + return kj::str(kj::repeat(' ', n * 2)); + } + +// ======================================================================================= + +class CapnpcCppMain { +public: + CapnpcCppMain(kj::ProcessContext& context): context(context) {} + + kj::MainFunc getMain() { + return kj::MainBuilder(context, "Cap'n Proto loopback plugin version " VERSION, + "This is a Cap'n Proto compiler plugin which \"de-compiles\" the schema back into " + "Cap'n Proto schema language format, with comments showing the offsets chosen by the " + "compiler. This is meant to be run using the Cap'n Proto compiler, e.g.:\n" + " capnp compile -ocapnp foo.capnp") + .callAfterParsing(KJ_BIND_METHOD(*this, run)) + .build(); + } + +private: + kj::ProcessContext& context; + SchemaLoader schemaLoader; + std::unordered_set usedImports; + bool hasInterfaces = false; + + kj::String outerClassName; + + kj::StringTree javaFullName(Schema schema) { + auto node = schema.getProto(); + if (node.getScopeId() == 0) { + usedImports.insert(node.getId()); + for (auto annotation: node.getAnnotations()) { + /* if (annotation.getId() == NAMESPACE_ANNOTATION_ID) { + return kj::strTree("", annotation.getValue().getText()); + }*/ + } + return kj::strTree(outerClassName); + } else { + Schema parent = schemaLoader.get(node.getScopeId()); + for (auto nested: parent.getProto().getNestedNodes()) { + if (nested.getId() == node.getId()) { + return kj::strTree(javaFullName(parent), ".", nested.getName()); + } + } + KJ_FAIL_REQUIRE("A schema Node's supposed scope did not contain the node as a NestedNode."); + } + } + + + kj::String toUpperCase(kj::StringPtr name) { + kj::Vector result(name.size() + 4); + + for (char c: name) { + if ('a' <= c && c <= 'z') { + result.add(c - 'a' + 'A'); + } else if (result.size() > 0 && 'A' <= c && c <= 'Z') { + result.add('_'); + result.add(c); + } else { + result.add(c); + } + } + + result.add('\0'); + + return kj::String(result.releaseAsArray()); + } + + kj::String toTitleCase(kj::StringPtr name) { + kj::String result = kj::heapString(name); + if ('a' <= result[0] && result[0] <= 'z') { + result[0] = result[0] - 'a' + 'A'; + } + return kj::mv(result); + } + + kj::StringTree typeName(schema::Type::Reader type) { + switch (type.which()) { + case schema::Type::VOID: return kj::strTree("void"); + + case schema::Type::BOOL: return kj::strTree("boolean"); + case schema::Type::INT8: return kj::strTree("byte"); + case schema::Type::INT16: return kj::strTree("short"); + case schema::Type::INT32: return kj::strTree("int"); + case schema::Type::INT64: return kj::strTree("long"); + case schema::Type::UINT8: return kj::strTree("byte"); + case schema::Type::UINT16: return kj::strTree("short"); + case schema::Type::UINT32: return kj::strTree("int"); + case schema::Type::UINT64: return kj::strTree("long"); + case schema::Type::FLOAT32: return kj::strTree("float"); + case schema::Type::FLOAT64: return kj::strTree("double"); + + case schema::Type::TEXT: return kj::strTree(" org.capnproto.Text"); + case schema::Type::DATA: return kj::strTree(" org.capnproto.Data"); + + case schema::Type::ENUM: + return javaFullName(schemaLoader.get(type.getEnum().getTypeId())); + case schema::Type::STRUCT: + return javaFullName(schemaLoader.get(type.getStruct().getTypeId())); + case schema::Type::INTERFACE: + return javaFullName(schemaLoader.get(type.getInterface().getTypeId())); + + case schema::Type::LIST: + // XXX + return kj::strTree(" org.capnproto.StructList"); + + case schema::Type::ANY_POINTER: + // Not used. + return kj::strTree(); + } + KJ_UNREACHABLE; + } + + kj::StringTree literalValue(schema::Type::Reader type, schema::Value::Reader value) { + switch (value.which()) { + case schema::Value::VOID: return kj::strTree(" ::capnp::VOID"); + case schema::Value::BOOL: return kj::strTree(value.getBool() ? "true" : "false"); + case schema::Value::INT8: return kj::strTree(value.getInt8()); + case schema::Value::INT16: return kj::strTree(value.getInt16()); + case schema::Value::INT32: return kj::strTree(value.getInt32()); + case schema::Value::INT64: return kj::strTree(value.getInt64(), "ll"); + case schema::Value::UINT8: return kj::strTree(value.getUint8(), "u"); + case schema::Value::UINT16: return kj::strTree(value.getUint16(), "u"); + case schema::Value::UINT32: return kj::strTree(value.getUint32(), "u"); + case schema::Value::UINT64: return kj::strTree(value.getUint64(), "llu"); + case schema::Value::FLOAT32: return kj::strTree(value.getFloat32(), "f"); + case schema::Value::FLOAT64: return kj::strTree(value.getFloat64()); + case schema::Value::ENUM: { + EnumSchema schema = schemaLoader.get(type.getEnum().getTypeId()).asEnum(); + if (value.getEnum() < schema.getEnumerants().size()) { + return kj::strTree( + javaFullName(schema), "::", + toUpperCase(schema.getEnumerants()[value.getEnum()].getProto().getName())); + } else { + return kj::strTree("static_cast<", javaFullName(schema), ">(", value.getEnum(), ")"); + } + } + + case schema::Value::TEXT: + case schema::Value::DATA: + case schema::Value::STRUCT: + case schema::Value::INTERFACE: + case schema::Value::LIST: + case schema::Value::ANY_POINTER: + KJ_FAIL_REQUIRE("literalValue() can only be used on primitive types."); + } + KJ_UNREACHABLE; + } + + // ----------------------------------------------------------------- + // Code to deal with "slots" -- determines what to zero out when we clear a group. + + static uint typeSizeBits(schema::Type::Which whichType) { + switch (whichType) { + case schema::Type::BOOL: return 1; + case schema::Type::INT8: return 8; + case schema::Type::INT16: return 16; + case schema::Type::INT32: return 32; + case schema::Type::INT64: return 64; + case schema::Type::UINT8: return 8; + case schema::Type::UINT16: return 16; + case schema::Type::UINT32: return 32; + case schema::Type::UINT64: return 64; + case schema::Type::FLOAT32: return 32; + case schema::Type::FLOAT64: return 64; + case schema::Type::ENUM: return 16; + + case schema::Type::VOID: + case schema::Type::TEXT: + case schema::Type::DATA: + case schema::Type::LIST: + case schema::Type::STRUCT: + case schema::Type::INTERFACE: + case schema::Type::ANY_POINTER: + KJ_FAIL_REQUIRE("Should only be called for data types."); + } + KJ_UNREACHABLE; + } + + enum class Section { + NONE, + DATA, + POINTERS + }; + + static Section sectionFor(schema::Type::Which whichType) { + switch (whichType) { + case schema::Type::VOID: + return Section::NONE; + case schema::Type::BOOL: + case schema::Type::INT8: + case schema::Type::INT16: + case schema::Type::INT32: + case schema::Type::INT64: + case schema::Type::UINT8: + case schema::Type::UINT16: + case schema::Type::UINT32: + case schema::Type::UINT64: + case schema::Type::FLOAT32: + case schema::Type::FLOAT64: + case schema::Type::ENUM: + return Section::DATA; + case schema::Type::TEXT: + case schema::Type::DATA: + case schema::Type::LIST: + case schema::Type::STRUCT: + case schema::Type::INTERFACE: + case schema::Type::ANY_POINTER: + return Section::POINTERS; + } + KJ_UNREACHABLE; + } + + static kj::StringPtr maskType(schema::Type::Which whichType) { + switch (whichType) { + case schema::Type::BOOL: return "bool"; + case schema::Type::INT8: return " ::uint8_t"; + case schema::Type::INT16: return " ::uint16_t"; + case schema::Type::INT32: return " ::uint32_t"; + case schema::Type::INT64: return " ::uint64_t"; + case schema::Type::UINT8: return " ::uint8_t"; + case schema::Type::UINT16: return " ::uint16_t"; + case schema::Type::UINT32: return " ::uint32_t"; + case schema::Type::UINT64: return " ::uint64_t"; + case schema::Type::FLOAT32: return " ::uint32_t"; + case schema::Type::FLOAT64: return " ::uint64_t"; + case schema::Type::ENUM: return " ::uint16_t"; + + case schema::Type::VOID: + case schema::Type::TEXT: + case schema::Type::DATA: + case schema::Type::LIST: + case schema::Type::STRUCT: + case schema::Type::INTERFACE: + case schema::Type::ANY_POINTER: + KJ_FAIL_REQUIRE("Should only be called for data types."); + } + KJ_UNREACHABLE; + } + + struct Slot { + schema::Type::Which whichType; + uint offset; + + bool isSupersetOf(Slot other) const { + auto section = sectionFor(whichType); + if (section != sectionFor(other.whichType)) return false; + switch (section) { + case Section::NONE: + return true; // all voids overlap + case Section::DATA: { + auto bits = typeSizeBits(whichType); + auto start = offset * bits; + auto otherBits = typeSizeBits(other.whichType); + auto otherStart = other.offset * otherBits; + return start <= otherStart && otherStart + otherBits <= start + bits; + } + case Section::POINTERS: + return offset == other.offset; + } + KJ_UNREACHABLE; + } + + bool operator<(Slot other) const { + // Sort by section, then start position, and finally size. + + auto section = sectionFor(whichType); + auto otherSection = sectionFor(other.whichType); + if (section < otherSection) { + return true; + } else if (section > otherSection) { + return false; + } + + switch (section) { + case Section::NONE: + return false; + case Section::DATA: { + auto bits = typeSizeBits(whichType); + auto start = offset * bits; + auto otherBits = typeSizeBits(other.whichType); + auto otherStart = other.offset * otherBits; + if (start < otherStart) { + return true; + } else if (start > otherStart) { + return false; + } + + // Sort larger sizes before smaller. + return bits > otherBits; + } + case Section::POINTERS: + return offset < other.offset; + } + KJ_UNREACHABLE; + } + }; + + void getSlots(StructSchema schema, kj::Vector& slots) { + auto structProto = schema.getProto().getStruct(); + if (structProto.getDiscriminantCount() > 0) { + slots.add(Slot { schema::Type::UINT16, structProto.getDiscriminantOffset() }); + } + + for (auto field: schema.getFields()) { + auto proto = field.getProto(); + switch (proto.which()) { + case schema::Field::SLOT: { + auto slot = proto.getSlot(); + slots.add(Slot { slot.getType().which(), slot.getOffset() }); + break; + } + case schema::Field::GROUP: + getSlots(schema.getDependency(proto.getGroup().getTypeId()).asStruct(), slots); + break; + } + } + } + + kj::Array getSortedSlots(StructSchema schema) { + // Get a representation of all of the field locations owned by this schema, e.g. so that they + // can be zero'd out. + + kj::Vector slots(schema.getFields().size()); + getSlots(schema, slots); + std::sort(slots.begin(), slots.end()); + + kj::Vector result(slots.size()); + + // All void slots are redundant, and they sort towards the front of the list. By starting out + // with `prevSlot` = void, we will end up skipping them all, which is what we want. + Slot prevSlot = { schema::Type::VOID, 0 }; + for (auto slot: slots) { + if (prevSlot.isSupersetOf(slot)) { + // This slot is redundant as prevSlot is a superset of it. + continue; + } + + // Since all sizes are power-of-two, if two slots overlap at all, one must be a superset of + // the other. Since we sort slots by starting position, we know that the only way `slot` + // could be a superset of `prevSlot` is if they have the same starting position. However, + // since we sort slots with the same starting position by descending size, this is not + // possible. + KJ_DASSERT(!slot.isSupersetOf(prevSlot)); + + result.add(slot); + + prevSlot = slot; + } + + return result.releaseAsArray(); + } + + // ----------------------------------------------------------------- + + struct DiscriminantChecks { + kj::String has; + kj::String check; + kj::String set; + kj::StringTree readerIsDecl; + kj::StringTree builderIsDecl; + kj::StringTree isDefs; + }; + + DiscriminantChecks makeDiscriminantChecks(kj::StringPtr scope, + kj::StringPtr memberName, + StructSchema containingStruct, + int indent) { + auto discrimOffset = containingStruct.getProto().getStruct().getDiscriminantOffset(); + + kj::String titleCase = toTitleCase(memberName); + kj::String upperCase = toUpperCase(memberName); + + return DiscriminantChecks { + kj::str( + " if (which() != ", scope, upperCase, ") return false;\n"), + kj::str( + " KJ_IREQUIRE(which() == ", scope, upperCase, ",\n" + " \"Must check which() before get()ing a union member.\");\n"), + kj::str( + " _builder.setDataField<", scope, "Which>(\n" + " ", discrimOffset, " * ::capnp::ELEMENTS, ", + scope, upperCase, ");\n"), + kj::strTree(spaces(indent), " public boolean is", titleCase, "() {\n", + spaces(indent), " return which() == ", scope, "Which.", upperCase,";\n", + spaces(indent), " }\n"), + kj::strTree(spaces(indent), " public boolean is", titleCase, "();\n"), + kj::strTree( + "inline boolean ", scope, "Reader::is", titleCase, "() const {\n" + " return which() == ", scope, upperCase, ";\n" + "}\n" + "inline boolean ", scope, "Builder::is", titleCase, "() {\n" + " return which() == ", scope, upperCase, ";\n" + "}\n") + }; + } + + // ----------------------------------------------------------------- + + struct FieldText { + kj::StringTree readerMethodDecls; + kj::StringTree builderMethodDecls; + kj::StringTree pipelineMethodDecls; + kj::StringTree inlineMethodDefs; + }; + + enum class FieldKind { + PRIMITIVE, + BLOB, + STRUCT, + LIST, + INTERFACE, + ANY_POINTER + }; + + FieldText makeFieldText(kj::StringPtr scope, StructSchema::Field field, int indent) { + auto proto = field.getProto(); + kj::String titleCase = toTitleCase(proto.getName()); + + DiscriminantChecks unionDiscrim; + if (hasDiscriminantValue(proto)) { + unionDiscrim = makeDiscriminantChecks(scope, proto.getName(), field.getContainingStruct(), indent); + } + + switch (proto.which()) { + case schema::Field::SLOT: + // Continue below. + break; + + case schema::Field::GROUP: { + auto slots = getSortedSlots(schemaLoader.get( + field.getProto().getGroup().getTypeId()).asStruct()); + return FieldText { + kj::strTree( + kj::mv(unionDiscrim.readerIsDecl), + spaces(indent), " public ", titleCase, ".Reader get", titleCase, "() {\n", + spaces(indent), " return new ", scope, titleCase, ".Reader(_reader);\n", + spaces(indent), " }\n", + "\n"), + + kj::strTree( + kj::mv(unionDiscrim.builderIsDecl), + " inline ", titleCase, "::Builder get", titleCase, "();\n" + " inline ", titleCase, "::Builder init", titleCase, "();\n" + "\n"), + + hasDiscriminantValue(proto) ? kj::strTree() : + kj::strTree(" inline ", titleCase, "::Pipeline get", titleCase, "();\n"), + + kj::strTree( + kj::mv(unionDiscrim.isDefs), + "inline ", scope, titleCase, "::Reader ", scope, "Reader::get", titleCase, "() const {\n", + unionDiscrim.check, + " return ", scope, titleCase, "::Reader(_reader);\n" + "}\n" + "inline ", scope, titleCase, "::Builder ", scope, "Builder::get", titleCase, "() {\n", + unionDiscrim.check, + " return ", scope, titleCase, "::Builder(_builder);\n" + "}\n", + hasDiscriminantValue(proto) ? kj::strTree() : kj::strTree( + "inline ", scope, titleCase, "::Pipeline ", scope, "Pipeline::get", titleCase, "() {\n", + " return ", scope, titleCase, "::Pipeline(_typeless.noop());\n" + "}\n"), + "inline ", scope, titleCase, "::Builder ", scope, "Builder::init", titleCase, "() {\n", + unionDiscrim.set, + KJ_MAP(slot, slots) { + switch (sectionFor(slot.whichType)) { + case Section::NONE: + return kj::strTree(); + case Section::DATA: + return kj::strTree( + " _builder.setDataField<", maskType(slot.whichType), ">(", + slot.offset, " * ::capnp::ELEMENTS, 0);\n"); + case Section::POINTERS: + return kj::strTree( + " _builder.getPointerField(", slot.offset, + " * ::capnp::POINTERS).clear();\n"); + } + KJ_UNREACHABLE; + }, + " return ", scope, titleCase, "::Builder(_builder);\n" + "}\n") + }; + } + } + + auto slot = proto.getSlot(); + + FieldKind kind = FieldKind::PRIMITIVE; + kj::String ownedType; + kj::String type = typeName(slot.getType()).flatten(); + kj::StringPtr setterDefault; // only for void + kj::String defaultMask; // primitives only + size_t defaultOffset = 0; // pointers only: offset of the default value within the schema. + size_t defaultSize = 0; // blobs only: byte size of the default value. + + auto typeBody = slot.getType(); + auto defaultBody = slot.getDefaultValue(); + switch (typeBody.which()) { + case schema::Type::VOID: + kind = FieldKind::PRIMITIVE; + setterDefault = " = ::capnp::VOID"; + break; + +#define HANDLE_PRIMITIVE(discrim, typeName, defaultName, suffix) \ + case schema::Type::discrim: \ + kind = FieldKind::PRIMITIVE; \ + if (defaultBody.get##defaultName() != 0) { \ + defaultMask = kj::str(defaultBody.get##defaultName(), #suffix); \ + } \ + break; + + HANDLE_PRIMITIVE(BOOL, bool, Bool, ); + HANDLE_PRIMITIVE(INT8 , ::int8_t , Int8 , ); + HANDLE_PRIMITIVE(INT16, ::int16_t, Int16, ); + HANDLE_PRIMITIVE(INT32, ::int32_t, Int32, ); + HANDLE_PRIMITIVE(INT64, ::int64_t, Int64, ll); + HANDLE_PRIMITIVE(UINT8 , ::uint8_t , Uint8 , u); + HANDLE_PRIMITIVE(UINT16, ::uint16_t, Uint16, u); + HANDLE_PRIMITIVE(UINT32, ::uint32_t, Uint32, u); + HANDLE_PRIMITIVE(UINT64, ::uint64_t, Uint64, ull); +#undef HANDLE_PRIMITIVE + + case schema::Type::FLOAT32: + kind = FieldKind::PRIMITIVE; + if (defaultBody.getFloat32() != 0) { + uint32_t mask; + float value = defaultBody.getFloat32(); + static_assert(sizeof(mask) == sizeof(value), "bug"); + memcpy(&mask, &value, sizeof(mask)); + defaultMask = kj::str(mask, "u"); + } + break; + + case schema::Type::FLOAT64: + kind = FieldKind::PRIMITIVE; + if (defaultBody.getFloat64() != 0) { + uint64_t mask; + double value = defaultBody.getFloat64(); + static_assert(sizeof(mask) == sizeof(value), "bug"); + memcpy(&mask, &value, sizeof(mask)); + defaultMask = kj::str(mask, "ull"); + } + break; + + case schema::Type::TEXT: + kind = FieldKind::BLOB; + if (defaultBody.hasText()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + defaultSize = defaultBody.getText().size(); + } + break; + case schema::Type::DATA: + kind = FieldKind::BLOB; + if (defaultBody.hasData()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + defaultSize = defaultBody.getData().size(); + } + break; + + case schema::Type::ENUM: + kind = FieldKind::PRIMITIVE; + if (defaultBody.getEnum() != 0) { + defaultMask = kj::str(defaultBody.getEnum(), "u"); + } + break; + + case schema::Type::STRUCT: + kind = FieldKind::STRUCT; + if (defaultBody.hasStruct()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + } + break; + case schema::Type::LIST: + kind = FieldKind::LIST; + if (defaultBody.hasList()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + } + break; + case schema::Type::INTERFACE: + kind = FieldKind::INTERFACE; + break; + case schema::Type::ANY_POINTER: + kind = FieldKind::ANY_POINTER; + if (defaultBody.hasAnyPointer()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + } + break; + } + + kj::String defaultMaskParam; + if (defaultMask.size() > 0) { + defaultMaskParam = kj::str(", ", defaultMask); + } + + uint offset = slot.getOffset(); + + if (kind == FieldKind::PRIMITIVE) { + return FieldText { + kj::strTree( + kj::mv(unionDiscrim.readerIsDecl), + spaces(indent), " public ", type, " get", titleCase, "() {\n", + spaces(indent), + (typeBody.which() == schema::Type::ENUM ? + kj::strTree(" return ", type, ".values()[_reader.getShortField(", offset, ")];\n") : + (typeBody.which() == schema::Type::VOID ? + kj::strTree(" // nothing to return\n") : + kj::strTree(" return _reader.get",toTitleCase(type),"Field(", offset, ");\n"))), + spaces(indent), " }\n", + "\n"), + + kj::strTree( + kj::mv(unionDiscrim.builderIsDecl), + " inline ", type, " get", titleCase, "();\n" + " inline void set", titleCase, "(", type, " value", setterDefault, ");\n" + "\n"), + + kj::strTree(), + + kj::strTree( + kj::mv(unionDiscrim.isDefs), + "inline ", type, " ", scope, "Reader::get", titleCase, "() const {\n", + unionDiscrim.check, + " return _reader.getDataField<", type, ">(\n" + " ", offset, " * ::capnp::ELEMENTS", defaultMaskParam, ");\n", + "}\n" + "\n" + "inline ", type, " ", scope, "Builder::get", titleCase, "() {\n", + unionDiscrim.check, + " return _builder.getDataField<", type, ">(\n" + " ", offset, " * ::capnp::ELEMENTS", defaultMaskParam, ");\n", + "}\n" + "inline void ", scope, "Builder::set", titleCase, "(", type, " value) {\n", + unionDiscrim.set, + " _builder.setDataField<", type, ">(\n" + " ", offset, " * ::capnp::ELEMENTS, value", defaultMaskParam, ");\n", + "}\n" + "\n") + }; + + } else if (kind == FieldKind::INTERFACE) { + KJ_FAIL_REQUIRE("interfaces unimplemented"); + } else if (kind == FieldKind::ANY_POINTER) { + return FieldText { + kj::strTree( + kj::mv(unionDiscrim.readerIsDecl), + " inline boolean has", titleCase, "() const;\n" + " inline ::capnp::AnyPointer::Reader get", titleCase, "() const;\n" + "\n"), + + kj::strTree( + kj::mv(unionDiscrim.builderIsDecl), + " inline boolean has", titleCase, "();\n" + " inline ::capnp::AnyPointer::Builder get", titleCase, "();\n" + " inline ::capnp::AnyPointer::Builder init", titleCase, "();\n" + "\n"), + + kj::strTree(), + + kj::strTree( + kj::mv(unionDiscrim.isDefs), + "inline boolean ", scope, "Reader::has", titleCase, "() const {\n", + unionDiscrim.has, + " return !_reader.getPointerField(", offset, " * ::capnp::POINTERS).isNull();\n" + "}\n" + "inline boolean ", scope, "Builder::has", titleCase, "() {\n", + unionDiscrim.has, + " return !_builder.getPointerField(", offset, " * ::capnp::POINTERS).isNull();\n" + "}\n" + "inline ::capnp::AnyPointer::Reader ", scope, "Reader::get", titleCase, "() const {\n", + unionDiscrim.check, + " return ::capnp::AnyPointer::Reader(\n" + " _reader.getPointerField(", offset, " * ::capnp::POINTERS));\n" + "}\n" + "inline ::capnp::AnyPointer::Builder ", scope, "Builder::get", titleCase, "() {\n", + unionDiscrim.check, + " return ::capnp::AnyPointer::Builder(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n" + "}\n" + "inline ::capnp::AnyPointer::Builder ", scope, "Builder::init", titleCase, "() {\n", + unionDiscrim.set, + " auto result = ::capnp::AnyPointer::Builder(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n" + " result.clear();\n" + " return result;\n" + "}\n" + "\n") + }; + + } else { + // Blob, struct, or list. These have only minor differences. + + uint64_t typeId = field.getContainingStruct().getProto().getId(); + kj::String defaultParam = defaultOffset == 0 ? kj::str() : kj::str( + ",\n ::capnp::schemas::s_", kj::hex(typeId), ".encodedNode + ", defaultOffset, + defaultSize == 0 ? kj::strTree() : kj::strTree(", ", defaultSize)); + + kj::String elementReaderType; + bool isStructOrCapList = false; + if (kind == FieldKind::LIST) { + bool primitiveElement = false; + bool interface = false; + switch (typeBody.getList().getElementType().which()) { + case schema::Type::VOID: + case schema::Type::BOOL: + case schema::Type::INT8: + case schema::Type::INT16: + case schema::Type::INT32: + case schema::Type::INT64: + case schema::Type::UINT8: + case schema::Type::UINT16: + case schema::Type::UINT32: + case schema::Type::UINT64: + case schema::Type::FLOAT32: + case schema::Type::FLOAT64: + case schema::Type::ENUM: + primitiveElement = true; + break; + + case schema::Type::TEXT: + case schema::Type::DATA: + case schema::Type::LIST: + case schema::Type::ANY_POINTER: + primitiveElement = false; + break; + + case schema::Type::INTERFACE: + isStructOrCapList = true; + primitiveElement = false; + interface = true; + break; + + case schema::Type::STRUCT: + isStructOrCapList = true; + primitiveElement = false; + break; + } + elementReaderType = kj::str( + typeName(typeBody.getList().getElementType()), + primitiveElement ? "" : interface ? "::Client" : ".Reader"); + } + + + return FieldText { + kj::strTree( + kj::mv(unionDiscrim.readerIsDecl), + spaces(indent), " public boolean has", titleCase, "() {\n", + spaces(indent), " return !_reader.getPointerField(", offset, ").isNull();\n", + spaces(indent), " }\n", + + spaces(indent), " public ", type, ".Reader", + (kind == FieldKind::LIST ? + kj::strTree("<", elementReaderType, ">") : + kj::strTree() + ), + " get", titleCase, "() {\n", + (kind == FieldKind::LIST ? + kj::strTree(spaces(indent), + " return new ", type, ".Reader<", + elementReaderType, + ">(_reader.getPointerField(", + offset, ").getList(", + + // XXX what about lists of non-structs? + typeName(typeBody.getList().getElementType()),".STRUCT_SIZE.preferredListEncoding), ", + elementReaderType, ".factory);\n") : + (kind == FieldKind::BLOB ? + kj::strTree(spaces(indent), " return _reader.getPointerField(", + offset,").getText();\n") : + kj::strTree(spaces(indent), "Struct\n"))), // XXX + spaces(indent), " }\n", + "\n"), + + kj::strTree( + kj::mv(unionDiscrim.builderIsDecl), + " inline boolean has", titleCase, "();\n" + " inline ", type, "::Builder get", titleCase, "();\n" + " inline void set", titleCase, "(", type, "::Reader value);\n", + kind == FieldKind::LIST && !isStructOrCapList + ? kj::strTree( + " inline void set", titleCase, "(::kj::ArrayPtr value);\n") + : kj::strTree(), + kind == FieldKind::STRUCT + ? kj::strTree( + " inline ", type, "::Builder init", titleCase, "();\n") + : kj::strTree( + " inline ", type, "::Builder init", titleCase, "(unsigned int size);\n"), + " inline void adopt", titleCase, "(::capnp::Orphan<", type, ">&& value);\n" + " inline ::capnp::Orphan<", type, "> disown", titleCase, "();\n" + "\n"), + + kj::strTree( + kind == FieldKind::STRUCT && !hasDiscriminantValue(proto) + ? kj::strTree( + " inline ", type, "::Pipeline get", titleCase, "();\n") + : kj::strTree()), + + kj::strTree( + kj::mv(unionDiscrim.isDefs), + "inline bool ", scope, "Reader::has", titleCase, "() const {\n", + unionDiscrim.has, + " return !_reader.getPointerField(", offset, " * ::capnp::POINTERS).isNull();\n" + "}\n" + "inline bool ", scope, "Builder::has", titleCase, "() {\n", + unionDiscrim.has, + " return !_builder.getPointerField(", offset, " * ::capnp::POINTERS).isNull();\n" + "}\n" + "inline ", type, "::Reader ", scope, "Reader::get", titleCase, "() const {\n", + unionDiscrim.check, + " return ::capnp::_::PointerHelpers<", type, ">::get(\n" + " _reader.getPointerField(", offset, " * ::capnp::POINTERS)", defaultParam, ");\n" + "}\n" + "inline ", type, "::Builder ", scope, "Builder::get", titleCase, "() {\n", + unionDiscrim.check, + " return ::capnp::_::PointerHelpers<", type, ">::get(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS)", defaultParam, ");\n" + "}\n", + kind == FieldKind::STRUCT && !hasDiscriminantValue(proto) + ? kj::strTree( + "inline ", type, "::Pipeline ", scope, "Pipeline::get", titleCase, "() {\n", + " return ", type, "::Pipeline(_typeless.getPointerField(", offset, "));\n" + "}\n") + : kj::strTree(), + "inline void ", scope, "Builder::set", titleCase, "(", type, "::Reader value) {\n", + unionDiscrim.set, + " ::capnp::_::PointerHelpers<", type, ">::set(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS), value);\n" + "}\n", + kind == FieldKind::LIST && !isStructOrCapList + ? kj::strTree( + "inline void ", scope, "Builder::set", titleCase, "(::kj::ArrayPtr value) {\n", + unionDiscrim.set, + " ::capnp::_::PointerHelpers<", type, ">::set(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS), value);\n" + "}\n") + : kj::strTree(), + kind == FieldKind::STRUCT + ? kj::strTree( + "inline ", type, "::Builder ", scope, "Builder::init", titleCase, "() {\n", + unionDiscrim.set, + " return ::capnp::_::PointerHelpers<", type, ">::init(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n" + "}\n") + : kj::strTree( + "inline ", type, "::Builder ", scope, "Builder::init", titleCase, "(unsigned int size) {\n", + unionDiscrim.set, + " return ::capnp::_::PointerHelpers<", type, ">::init(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS), size);\n" + "}\n"), + "inline void ", scope, "Builder::adopt", titleCase, "(\n" + " ::capnp::Orphan<", type, ">&& value) {\n", + unionDiscrim.set, + " ::capnp::_::PointerHelpers<", type, ">::adopt(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS), kj::mv(value));\n" + "}\n" + "inline ::capnp::Orphan<", type, "> ", scope, "Builder::disown", titleCase, "() {\n", + unionDiscrim.check, + " return ::capnp::_::PointerHelpers<", type, ">::disown(\n" + " _builder.getPointerField(", offset, " * ::capnp::POINTERS));\n" + "}\n" + "\n") + }; + } + } + + // ----------------------------------------------------------------- + + struct StructText { + kj::StringTree outerTypeDecl; + kj::StringTree outerTypeDef; + kj::StringTree readerBuilderDefs; + kj::StringTree inlineMethodDefs; + }; + + kj::StringTree makeReaderDef(kj::StringPtr fullName, kj::StringPtr unqualifiedParentType, + bool isUnion, uint discriminantOffset, kj::Array&& methodDecls, + int indent) { + return kj::strTree( + spaces(indent), "public static final class Reader {\n", + spaces(indent), + " public static class Factory implements org.capnproto.FromStructReader {\n", + spaces(indent), + " public final Reader fromStructReader(org.capnproto.StructReader reader) {\n", + spaces(indent), " return new Reader(reader);\n", + spaces(indent), " }\n", + spaces(indent), " }\n", + spaces(indent), " public static final Factory factory = new Factory();\n", + spaces(indent), " public Reader(org.capnproto.StructReader base){ this._reader = base; }\n", + "\n", + (isUnion ? + kj::strTree(spaces(indent), " public Which which() {\n", + spaces(indent), " return Which.values()[_reader.getShortField(", + discriminantOffset, ")];\n", + spaces(indent), " }\n") + : kj::strTree()), + kj::mv(methodDecls), + spaces(indent), " public org.capnproto.StructReader _reader;\n", + spaces(indent), "}\n" + "\n"); + } + + kj::StringTree makeBuilderDef(kj::StringPtr fullName, kj::StringPtr unqualifiedParentType, + schema::Node::Struct::Reader structNode, + kj::Array&& methodDecls, + int indent) { + bool isUnion = structNode.getDiscriminantCount() != 0; + return kj::strTree( + spaces(indent), "public static final class Builder {\n", + spaces(indent), " public static class Factory implements org.capnproto.FromStructBuilder {\n", + spaces(indent), " public final Builder fromStructBuilder(org.capnproto.StructBuilder builder) {\n", + spaces(indent), " return new Builder(builder);\n", + spaces(indent), " }\n", + spaces(indent), " public final org.capnproto.StructSize structSize() {\n", + spaces(indent), " return ", fullName, ".STRUCT_SIZE;\n", + spaces(indent), " }\n", + spaces(indent), " }\n", + spaces(indent), " public static final Factory factory = new Factory();\n", + spaces(indent), " public Builder(org.capnproto.StructBuilder base){ this._builder = base; }\n", + spaces(indent), " public org.capnproto.StructBuilder _builder;\n", + spaces(indent), "}\n", + "\n"); + } + + StructText makeStructText(kj::StringPtr scope, kj::StringPtr name, StructSchema schema, + kj::Array nestedTypeDecls, int indent) { + auto proto = schema.getProto(); + auto fullName = kj::str(scope, name); + auto subScope = kj::str(fullName, "."); + auto fieldTexts = KJ_MAP(f, schema.getFields()) { return makeFieldText(subScope, f, indent + 1); }; + + auto structNode = proto.getStruct(); + uint discrimOffset = structNode.getDiscriminantOffset(); + structNode.getPointerCount(); + + return StructText { + kj::strTree( + " struct ", name, ";\n"), + + kj::strTree( + spaces(indent), "public static class ", name, " {\n", + kj::strTree( + spaces(indent), " public static final org.capnproto.StructSize STRUCT_SIZE =\n", + spaces(indent), " new org.capnproto.StructSize((short)", structNode.getDataWordCount(), + ",(short)", structNode.getPointerCount(), + ", org.capnproto.FieldSize.", FIELD_SIZE_NAMES[(int)structNode.getPreferredListEncoding()], ");\n"), + + kj::strTree(makeReaderDef(fullName, name, structNode.getDiscriminantCount() != 0, + structNode.getDiscriminantOffset(), + KJ_MAP(f, fieldTexts) { return kj::mv(f.readerMethodDecls); }, + indent + 1), + makeBuilderDef(fullName, name, structNode, + KJ_MAP(f, fieldTexts) { return kj::mv(f.builderMethodDecls); }, + indent + 1)), + + structNode.getDiscriminantCount() == 0 ? + kj::strTree() : + kj::strTree( + spaces(indent), " public enum Which {\n", + KJ_MAP(f, structNode.getFields()) { + if (hasDiscriminantValue(f)) { + return kj::strTree(spaces(indent), " ", toUpperCase(f.getName()), ",\n"); + } else { + return kj::strTree(); + } + }, + spaces(indent), " }\n"), + KJ_MAP(n, nestedTypeDecls) { return kj::mv(n); }, + spaces(indent), "}\n" + "\n", + "\n"), + + kj::strTree(), + kj::strTree() + }; + } + + + // ----------------------------------------------------------------- + + struct ConstText { + bool needsSchema; + kj::StringTree decl; + kj::StringTree def; + }; + + ConstText makeConstText(kj::StringPtr scope, kj::StringPtr name, ConstSchema schema) { + auto proto = schema.getProto(); + auto constProto = proto.getConst(); + auto type = constProto.getType(); + auto typeName_ = typeName(type).flatten(); + auto upperCase = toUpperCase(name); + + // Linkage qualifier for non-primitive types. + const char* linkage = scope.size() == 0 ? "extern " : "static "; + + switch (type.which()) { + case schema::Value::VOID: + case schema::Value::BOOL: + case schema::Value::INT8: + case schema::Value::INT16: + case schema::Value::INT32: + case schema::Value::INT64: + case schema::Value::UINT8: + case schema::Value::UINT16: + case schema::Value::UINT32: + case schema::Value::UINT64: + case schema::Value::FLOAT32: + case schema::Value::FLOAT64: + case schema::Value::ENUM: + return ConstText { + false, + kj::strTree("static constexpr ", typeName_, ' ', upperCase, " = ", + literalValue(constProto.getType(), constProto.getValue()), ";\n"), + scope.size() == 0 ? kj::strTree() : kj::strTree( + "constexpr ", typeName_, ' ', scope, upperCase, ";\n") + }; + + case schema::Value::TEXT: { + kj::String constType = kj::strTree( + "::capnp::_::ConstText<", schema.as().size(), ">").flatten(); + return ConstText { + true, + kj::strTree(linkage, "const ", constType, ' ', upperCase, ";\n"), + kj::strTree("const ", constType, ' ', scope, upperCase, "(::capnp::schemas::b_", + kj::hex(proto.getId()), ".words + ", schema.getValueSchemaOffset(), ");\n") + }; + } + + case schema::Value::DATA: { + kj::String constType = kj::strTree( + "::capnp::_::ConstData<", schema.as().size(), ">").flatten(); + return ConstText { + true, + kj::strTree(linkage, "const ", constType, ' ', upperCase, ";\n"), + kj::strTree("const ", constType, ' ', scope, upperCase, "(::capnp::schemas::b_", + kj::hex(proto.getId()), ".words + ", schema.getValueSchemaOffset(), ");\n") + }; + } + + case schema::Value::STRUCT: { + kj::String constType = kj::strTree( + "::capnp::_::ConstStruct<", typeName_, ">").flatten(); + return ConstText { + true, + kj::strTree(linkage, "const ", constType, ' ', upperCase, ";\n"), + kj::strTree("const ", constType, ' ', scope, upperCase, "(::capnp::schemas::b_", + kj::hex(proto.getId()), ".words + ", schema.getValueSchemaOffset(), ");\n") + }; + } + + case schema::Value::LIST: { + kj::String constType = kj::strTree( + "::capnp::_::ConstList<", typeName(type.getList().getElementType()), ">").flatten(); + return ConstText { + true, + kj::strTree(linkage, "const ", constType, ' ', upperCase, ";\n"), + kj::strTree("const ", constType, ' ', scope, upperCase, "(::capnp::schemas::b_", + kj::hex(proto.getId()), ".words + ", schema.getValueSchemaOffset(), ");\n") + }; + } + + case schema::Value::ANY_POINTER: + case schema::Value::INTERFACE: + return ConstText { false, kj::strTree(), kj::strTree() }; + } + + KJ_UNREACHABLE; + } + + // ----------------------------------------------------------------- + + struct NodeText { + kj::StringTree outerTypeDecl; + kj::StringTree outerTypeDef; + kj::StringTree readerBuilderDefs; + kj::StringTree inlineMethodDefs; + kj::StringTree capnpSchemaDecls; + kj::StringTree capnpSchemaDefs; + kj::StringTree capnpPrivateDecls; + kj::StringTree capnpPrivateDefs; + kj::StringTree sourceFileDefs; + }; + + struct NodeTextNoSchema { + kj::StringTree outerTypeDecl; + kj::StringTree outerTypeDef; + kj::StringTree readerBuilderDefs; + kj::StringTree inlineMethodDefs; + kj::StringTree capnpPrivateDecls; + kj::StringTree capnpPrivateDefs; + kj::StringTree sourceFileDefs; + }; + + NodeText makeNodeText(kj::StringPtr namespace_, kj::StringPtr scope, + kj::StringPtr name, Schema schema, + int indent) { + auto proto = schema.getProto(); + auto fullName = kj::str(scope, name); + auto subScope = kj::str(fullName, "."); + auto hexId = kj::hex(proto.getId()); + + // Compute nested nodes, including groups. + kj::Vector nestedTexts(proto.getNestedNodes().size()); + for (auto nested: proto.getNestedNodes()) { + nestedTexts.add(makeNodeText( + namespace_, + subScope, nested.getName(), schemaLoader.get(nested.getId()), indent + 1)); + }; + + if (proto.isStruct()) { + for (auto field: proto.getStruct().getFields()) { + if (field.isGroup()) { + nestedTexts.add(makeNodeText( + namespace_, subScope, toTitleCase(field.getName()), + schemaLoader.get(field.getGroup().getTypeId()), indent + 1)); + } + } + } else if (proto.isInterface()) { + KJ_FAIL_REQUIRE("interfaces not implemented"); + } + + // Convert the encoded schema to a literal byte array. + kj::ArrayPtr rawSchema = schema.asUncheckedMessage(); + auto schemaLiteral = kj::StringTree(KJ_MAP(w, rawSchema) { + const byte* bytes = reinterpret_cast(&w); + + return kj::strTree(KJ_MAP(i, kj::range(0, sizeof(word))) { + auto text = kj::toCharSequence(kj::implicitCast(bytes[i])); + return kj::strTree(kj::repeat(' ', 4 - text.size()), text, ","); + }); + }, "\n "); + + auto schemaDecl = kj::strTree( + "extern const ::capnp::_::RawSchema s_", hexId, ";\n"); + + std::set deps; + enumerateDeps(proto, deps); + + kj::Array membersByName; + kj::Array membersByDiscrim; + switch (proto.which()) { + case schema::Node::STRUCT: { + auto structSchema = schema.asStruct(); + membersByName = makeMembersByName(structSchema.getFields()); + auto builder = kj::heapArrayBuilder(structSchema.getFields().size()); + for (auto field: structSchema.getUnionFields()) { + builder.add(field.getIndex()); + } + for (auto field: structSchema.getNonUnionFields()) { + builder.add(field.getIndex()); + } + membersByDiscrim = builder.finish(); + break; + } + case schema::Node::ENUM: + membersByName = makeMembersByName(schema.asEnum().getEnumerants()); + break; + case schema::Node::INTERFACE: + membersByName = makeMembersByName(schema.asInterface().getMethods()); + break; + default: + break; + } + + auto schemaDef = kj::strTree( + "static const ::capnp::_::AlignedData<", rawSchema.size(), "> b_", hexId, " = {\n" + " {", kj::mv(schemaLiteral), " }\n" + "};\n", + deps.size() == 0 ? kj::strTree() : kj::strTree( + "static const ::capnp::_::RawSchema* const d_", hexId, "[] = {\n", + KJ_MAP(depId, deps) { + return kj::strTree(" &s_", kj::hex(depId), ",\n"); + }, + "};\n"), + membersByName.size() == 0 ? kj::strTree() : kj::strTree( + "static const uint16_t m_", hexId, "[] = {", + kj::StringTree(KJ_MAP(index, membersByName) { return kj::strTree(index); }, ", "), + "};\n"), + membersByDiscrim.size() == 0 ? kj::strTree() : kj::strTree( + "static const uint16_t i_", hexId, "[] = {", + kj::StringTree(KJ_MAP(index, membersByDiscrim) { return kj::strTree(index); }, ", "), + "};\n"), + "const ::capnp::_::RawSchema s_", hexId, " = {\n" + " 0x", hexId, ", b_", hexId, ".words, ", rawSchema.size(), ", ", + deps.size() == 0 ? kj::strTree("nullptr") : kj::strTree("d_", hexId), ", ", + membersByName.size() == 0 ? kj::strTree("nullptr") : kj::strTree("m_", hexId), ",\n", + " ", deps.size(), ", ", membersByName.size(), ", ", + membersByDiscrim.size() == 0 ? kj::strTree("nullptr") : kj::strTree("i_", hexId), + ", nullptr, nullptr\n" + "};\n"); + + NodeTextNoSchema top = makeNodeTextWithoutNested( + namespace_, scope, name, schema, + KJ_MAP(n, nestedTexts) { return kj::mv(n.outerTypeDef); }, indent); + + return NodeText { + kj::mv(top.outerTypeDecl), + + kj::strTree( + kj::mv(top.outerTypeDef), + KJ_MAP(n, nestedTexts) { return kj::mv(n.outerTypeDef); }), + + kj::strTree( + kj::mv(top.readerBuilderDefs), + KJ_MAP(n, nestedTexts) { return kj::mv(n.readerBuilderDefs); }), + + kj::strTree( + kj::mv(top.inlineMethodDefs), + KJ_MAP(n, nestedTexts) { return kj::mv(n.inlineMethodDefs); }), + + kj::strTree( + kj::mv(schemaDecl), + KJ_MAP(n, nestedTexts) { return kj::mv(n.capnpSchemaDecls); }), + + kj::strTree( + kj::mv(schemaDef), + KJ_MAP(n, nestedTexts) { return kj::mv(n.capnpSchemaDefs); }), + + kj::strTree( + kj::mv(top.capnpPrivateDecls), + KJ_MAP(n, nestedTexts) { return kj::mv(n.capnpPrivateDecls); }), + + kj::strTree( + kj::mv(top.capnpPrivateDefs), + KJ_MAP(n, nestedTexts) { return kj::mv(n.capnpPrivateDefs); }), + + kj::strTree( + kj::mv(top.sourceFileDefs), + KJ_MAP(n, nestedTexts) { return kj::mv(n.sourceFileDefs); }), + }; + } + + NodeTextNoSchema makeNodeTextWithoutNested(kj::StringPtr namespace_, kj::StringPtr scope, + kj::StringPtr name, Schema schema, + kj::Array nestedTypeDecls, + int indent) { + auto proto = schema.getProto(); + auto fullName = kj::str(scope, name); + auto hexId = kj::hex(proto.getId()); + + switch (proto.which()) { + case schema::Node::FILE: + KJ_FAIL_REQUIRE("This method shouldn't be called on file nodes."); + + case schema::Node::STRUCT: { + StructText structText = + makeStructText(scope, name, schema.asStruct(), kj::mv(nestedTypeDecls), indent); + auto structNode = proto.getStruct(); + + return NodeTextNoSchema { + kj::mv(structText.outerTypeDecl), + kj::mv(structText.outerTypeDef), + kj::mv(structText.readerBuilderDefs), + kj::mv(structText.inlineMethodDefs), + + kj::strTree(), + kj::strTree(), + + kj::strTree(), + }; + } + + case schema::Node::ENUM: { + auto enumerants = schema.asEnum().getEnumerants(); + + return NodeTextNoSchema { + kj::strTree(), + + kj::strTree( + spaces(indent), "public enum ", name, " {\n", + KJ_MAP(e, enumerants) { + return kj::strTree(spaces(indent), " ", toUpperCase(e.getProto().getName()), ",\n"); + }, + spaces(indent), "}\n" + "\n"), + + kj::strTree(), + kj::strTree(), + + kj::strTree(), + kj::strTree(), + + kj::strTree(), + }; + } + + case schema::Node::INTERFACE: { + hasInterfaces = true; + KJ_FAIL_REQUIRE("unimplemented"); + } + + case schema::Node::CONST: { + auto constText = makeConstText(scope, name, schema.asConst()); + + return NodeTextNoSchema { + scope.size() == 0 ? kj::strTree() : kj::strTree(" ", kj::mv(constText.decl)), + scope.size() > 0 ? kj::strTree() : kj::mv(constText.decl), + kj::strTree(), + kj::strTree(), + + kj::strTree(), + kj::strTree(), + + kj::mv(constText.def), + }; + } + + case schema::Node::ANNOTATION: { + return NodeTextNoSchema { + kj::strTree(), + kj::strTree(), + kj::strTree(), + kj::strTree(), + + kj::strTree(), + kj::strTree(), + + kj::strTree(), + }; + } + } + + KJ_UNREACHABLE; + } + + // ----------------------------------------------------------------- + + struct FileText { + kj::StringTree source; + }; + + FileText makeFileText(Schema schema, + schema::CodeGeneratorRequest::RequestedFile::Reader request) { + usedImports.clear(); + + auto node = schema.getProto(); + auto displayName = node.getDisplayName(); + + kj::Vector> namespaceParts; + kj::String namespacePrefix; + + for (auto annotation: node.getAnnotations()) { + if (annotation.getId() == NAMESPACE_ANNOTATION_ID) { + kj::StringPtr ns = annotation.getValue().getText(); + kj::StringPtr ns2 = ns; + namespacePrefix = kj::str("::", ns); + + for (;;) { + KJ_IF_MAYBE(colonPos, ns.findFirst(':')) { + namespaceParts.add(ns.slice(0, *colonPos)); + ns = ns.slice(*colonPos); + if (!ns.startsWith("::")) { + context.exitError(kj::str(displayName, ": invalid namespace spec: ", ns2)); + } + ns = ns.slice(2); + } else { + namespaceParts.add(ns); + break; + } + } + + break; + } + } + auto nodeTexts = KJ_MAP(nested, node.getNestedNodes()) { + return makeNodeText(namespacePrefix, "", nested.getName(), schemaLoader.get(nested.getId()), 1); + }; + + kj::String separator = kj::str("// ", kj::repeat('=', 87), "\n"); + + kj::Vector includes; + for (auto import: request.getImports()) { + if (usedImports.count(import.getId()) > 0) { + includes.add(import.getName()); + } + } + + kj::StringTree sourceDefs = kj::strTree( + KJ_MAP(n, nodeTexts) { return kj::mv(n.sourceFileDefs); }); + + return FileText { + kj::strTree( + "// Generated by Cap'n Proto compiler, DO NOT EDIT\n" + "// source: ", baseName(displayName), "\n", + "\n", + //"import org.capnproto;\n", + // KJ_MAP(n, namespaceParts) { return kj::strTree("namespace ", n, " {\n"); }, "\n", + "public class ", outerClassName, " {\n", + KJ_MAP(n, nodeTexts) { return kj::mv(n.outerTypeDef); }, + KJ_MAP(n, namespaceParts) { return kj::strTree("}\n"); }, "\n") + }; + } + + // ----------------------------------------------------------------- + + void makeDirectory(kj::StringPtr path) { + KJ_IF_MAYBE(slashpos, path.findLast('/')) { + // Make the parent dir. + makeDirectory(kj::str(path.slice(0, *slashpos))); + } + + if (mkdir(path.cStr(), 0777) < 0) { + int error = errno; + if (error != EEXIST) { + KJ_FAIL_SYSCALL("mkdir(path)", error, path); + } + } + } + + void writeFile(kj::StringPtr filename, const kj::StringTree& text) { + if (!filename.startsWith("/")) { + KJ_IF_MAYBE(slashpos, filename.findLast('/')) { + // Make the parent dir. + makeDirectory(kj::str(filename.slice(0, *slashpos))); + } + } + + int fd; + KJ_SYSCALL(fd = open(filename.cStr(), O_CREAT | O_WRONLY | O_TRUNC, 0666), filename); + kj::FdOutputStream out((kj::AutoCloseFd(fd))); + + text.visit( + [&](kj::ArrayPtr text) { + out.write(text.begin(), text.size()); + }); + } + + kj::MainBuilder::Validity run() { + ReaderOptions options; + options.traversalLimitInWords = 1 << 30; // Don't limit. + StreamFdMessageReader reader(STDIN_FILENO, options); + auto request = reader.getRoot(); + + for (auto node: request.getNodes()) { + schemaLoader.load(node); + } + + kj::FdOutputStream rawOut(STDOUT_FILENO); + kj::BufferedOutputStreamWrapper out(rawOut); + + for (auto requestedFile: request.getRequestedFiles()) { + auto schema = schemaLoader.get(requestedFile.getId()); + + auto filename = requestedFile.getFilename(); + size_t stemstart = 0; + size_t stemend = filename.size(); + KJ_IF_MAYBE(slashpos, filename.findLast('/')) { + stemstart = *slashpos + 1; + } + KJ_IF_MAYBE(dotpos, filename.findLast('.')) { + stemend = *dotpos; + } + outerClassName = toTitleCase(kj::str(filename.slice(stemstart, stemend))); + + auto genFileName = kj::str(filename.slice(0, stemstart), outerClassName, ".java"); + + auto fileText = makeFileText(schema, requestedFile); + + writeFile(genFileName, fileText.source); + } + + return true; + } +}; + +} // namespace +} // namespace capnp + +KJ_MAIN(capnp::CapnpcCppMain); diff --git a/generator/src/main/cpp/compiler/java_support/java.capnp b/generator/src/main/cpp/compiler/java_support/java.capnp new file mode 100644 index 0000000..4f44ff9 --- /dev/null +++ b/generator/src/main/cpp/compiler/java_support/java.capnp @@ -0,0 +1,3 @@ +@0xc5f1af96651f70ea; + +annotation package(file): Text; diff --git a/generator/src/main/cpp/compiler/java_support/java.capnp.c++ b/generator/src/main/cpp/compiler/java_support/java.capnp.c++ new file mode 100644 index 0000000..0098f4f --- /dev/null +++ b/generator/src/main/cpp/compiler/java_support/java.capnp.c++ @@ -0,0 +1,35 @@ +// Generated by Cap'n Proto compiler, DO NOT EDIT +// source: java.capnp + +#include "java.capnp.h" + +namespace capnp { +namespace schemas { +static const ::capnp::_::AlignedData<18> b_9ee4c8f803b3b596 = { + { 0, 0, 0, 0, 5, 0, 5, 0, + 150, 181, 179, 3, 248, 200, 228, 158, + 11, 0, 0, 0, 5, 0, 1, 0, + 234, 112, 31, 101, 150, 175, 241, 197, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 17, 0, 0, 0, 154, 0, 0, 0, + 25, 0, 0, 0, 7, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 20, 0, 0, 0, 2, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 106, 97, 118, 97, 46, 99, 97, 112, + 110, 112, 58, 112, 97, 99, 107, 97, + 103, 101, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 1, 0, + 12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, } +}; +const ::capnp::_::RawSchema s_9ee4c8f803b3b596 = { + 0x9ee4c8f803b3b596, b_9ee4c8f803b3b596.words, 18, nullptr, nullptr, + 0, 0, nullptr, nullptr, nullptr +}; +} // namespace schemas +namespace _ { // private +} // namespace _ (private) +} // namespace capnp diff --git a/generator/src/main/cpp/compiler/java_support/java.capnp.h b/generator/src/main/cpp/compiler/java_support/java.capnp.h new file mode 100644 index 0000000..d5f60ad --- /dev/null +++ b/generator/src/main/cpp/compiler/java_support/java.capnp.h @@ -0,0 +1,36 @@ +// Generated by Cap'n Proto compiler, DO NOT EDIT +// source: java.capnp + +#ifndef CAPNP_INCLUDED_c5f1af96651f70ea_ +#define CAPNP_INCLUDED_c5f1af96651f70ea_ + +#include + +#if CAPNP_VERSION != 4001 +#error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." +#endif + + + + +// ======================================================================================= + +namespace capnp { +namespace schemas { + +extern const ::capnp::_::RawSchema s_9ee4c8f803b3b596; + +} // namespace schemas +namespace _ { // private + + +} // namespace _ (private) +} // namespace capnp + +// ======================================================================================= + + +// ======================================================================================= + + +#endif // CAPNP_INCLUDED_c5f1af96651f70ea_ diff --git a/generator/src/main/java/org/capnproto/AnyPointer.java b/generator/src/main/java/org/capnproto/AnyPointer.java new file mode 100644 index 0000000..26346e8 --- /dev/null +++ b/generator/src/main/java/org/capnproto/AnyPointer.java @@ -0,0 +1,17 @@ +package org.capnproto; + +public class AnyPointer { + + public static class Reader { + public PointerReader reader; + + public Reader(PointerReader reader) { + this.reader = reader; + } + + public T getAsStruct(FromStructReader factory) { + return factory.fromStructReader(this.reader.getStruct()); + } + } + +} diff --git a/generator/src/main/java/org/capnproto/DecodeException.java b/generator/src/main/java/org/capnproto/DecodeException.java new file mode 100644 index 0000000..4aad297 --- /dev/null +++ b/generator/src/main/java/org/capnproto/DecodeException.java @@ -0,0 +1,7 @@ +package org.capnproto; + +public class DecodeException extends RuntimeException { + public DecodeException(String message) { + super(message); + } +} diff --git a/generator/src/main/java/org/capnproto/FieldSize.java b/generator/src/main/java/org/capnproto/FieldSize.java new file mode 100644 index 0000000..52886ad --- /dev/null +++ b/generator/src/main/java/org/capnproto/FieldSize.java @@ -0,0 +1,12 @@ +package org.capnproto; + +public class FieldSize { + public static final byte VOID = 0; + public static final byte BIT = 1; + public static final byte BYTE = 2; + public static final byte TWO_BYTES = 3; + public static final byte FOUR_BYTES = 4; + public static final byte EIGHT_BYTES = 5; + public static final byte POINTER = 6; + public static final byte INLINE_COMPOSITE = 7; +} diff --git a/generator/src/main/java/org/capnproto/FromStructBuilder.java b/generator/src/main/java/org/capnproto/FromStructBuilder.java new file mode 100644 index 0000000..4b44e0b --- /dev/null +++ b/generator/src/main/java/org/capnproto/FromStructBuilder.java @@ -0,0 +1,6 @@ +package org.capnproto; + +public interface FromStructBuilder { + public abstract T fromStructBuilder(StructBuilder builder); + public abstract StructSize structSize(); +} diff --git a/generator/src/main/java/org/capnproto/FromStructReader.java b/generator/src/main/java/org/capnproto/FromStructReader.java new file mode 100644 index 0000000..eaf1413 --- /dev/null +++ b/generator/src/main/java/org/capnproto/FromStructReader.java @@ -0,0 +1,5 @@ +package org.capnproto; + +public interface FromStructReader { + public abstract T fromStructReader(StructReader reader); +} diff --git a/generator/src/main/java/org/capnproto/InputStreamMessageReader.java b/generator/src/main/java/org/capnproto/InputStreamMessageReader.java new file mode 100644 index 0000000..1d8f796 --- /dev/null +++ b/generator/src/main/java/org/capnproto/InputStreamMessageReader.java @@ -0,0 +1,84 @@ +package org.capnproto; + +import java.io.InputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Vector; + + +public class InputStreamMessageReader { + + static byte[] readExact(InputStream is, int length) throws IOException { + byte[] bytes = new byte[length]; + + int bytesRead = 0; + while (bytesRead < length) { + int r = is.read(bytes, bytesRead, length - bytesRead); + if (r < 0) { + throw new IOException("premature EOF"); + } + bytesRead += r; + } + + return bytes; + } + + static ByteBuffer makeByteBuffer(byte[] bytes) { + ByteBuffer result = ByteBuffer.wrap(bytes); + result.order(ByteOrder.LITTLE_ENDIAN); + result.mark(); + return result; + } + + public static MessageReader create(InputStream is) throws IOException { + ByteBuffer firstWord = makeByteBuffer(readExact(is, 8)); + + int segmentCount = 1 + firstWord.getInt(0); + + int segment0Size = 0; + if (segmentCount > 0) { + segment0Size = firstWord.getInt(4); + } + + int totalWords = segment0Size; + + if (segmentCount > 512) { + throw new IOException("too many segments"); + } + + // in words + Vector moreSizes = new Vector(); + + if (segmentCount > 1) { + ByteBuffer moreSizesRaw = makeByteBuffer(readExact(is, 4 * (segmentCount & ~1))); + for(int ii = 0; ii < segmentCount - 1; ++ii) { + int size = moreSizesRaw.getInt(ii * 4); + moreSizes.add(size); + totalWords += size; + } + } + + // TODO check that totalWords is reasonable + + byte[] allSegments = readExact(is, totalWords * 8); + + ByteBuffer[] segmentSlices = new ByteBuffer[segmentCount]; + + segmentSlices[0] = ByteBuffer.wrap(allSegments, 0, segment0Size * 8); + segmentSlices[0].order(ByteOrder.LITTLE_ENDIAN); + segmentSlices[0].mark(); + + int offset = segment0Size; + + for (int ii = 1; ii < segmentCount; ++ii) { + segmentSlices[ii] = ByteBuffer.wrap(allSegments, offset * 8, moreSizes.get(ii - 1) * 8); + segmentSlices[ii].order(ByteOrder.LITTLE_ENDIAN); + segmentSlices[ii].mark(); + offset += moreSizes.get(ii - 1); + } + + return new MessageReader(segmentSlices); + } + +} diff --git a/generator/src/main/java/org/capnproto/ListBuilder.java b/generator/src/main/java/org/capnproto/ListBuilder.java new file mode 100644 index 0000000..cb5edbe --- /dev/null +++ b/generator/src/main/java/org/capnproto/ListBuilder.java @@ -0,0 +1,24 @@ +package org.capnproto; + +public final class ListBuilder { + SegmentBuilder segment; + int ptr; // byte offset to front of list + int elementCount; + int step; // in bits + int structDataSize; // in bits + short structPointerCount; + + public ListBuilder(SegmentBuilder segment, int ptr, + int elementCount, int step, + int structDataSize, short structPointerCount) { + this.segment = segment; + this.ptr = ptr; + this.elementCount = elementCount; + this.step = step; + this.structDataSize = structDataSize; + this.structPointerCount = structPointerCount; + } + + + +} diff --git a/generator/src/main/java/org/capnproto/ListPointer.java b/generator/src/main/java/org/capnproto/ListPointer.java new file mode 100644 index 0000000..faafa97 --- /dev/null +++ b/generator/src/main/java/org/capnproto/ListPointer.java @@ -0,0 +1,17 @@ +package org.capnproto; + +import java.nio.ByteBuffer; + +final class ListPointer { + public static byte elementSize(int elementSizeAndCount) { + return (byte) (elementSizeAndCount & 7); + } + + public static int elementCount(int elementSizeAndCount) { + return elementSizeAndCount >> 3; + } + + public static int inlineCompositeWordCount(int elementSizeAndCount) { + return elementCount(elementSizeAndCount); + } +} diff --git a/generator/src/main/java/org/capnproto/ListReader.java b/generator/src/main/java/org/capnproto/ListReader.java new file mode 100644 index 0000000..4c019f9 --- /dev/null +++ b/generator/src/main/java/org/capnproto/ListReader.java @@ -0,0 +1,52 @@ +package org.capnproto; + +public class ListReader { + SegmentReader segment; + int ptr; // byte offset to front of list + int elementCount; + int step; // in bits + int structDataSize; // in bits + short structPointerCount; + int nestingLimit; + + + public ListReader () { + this.segment = null; + this.ptr = 0; + this.elementCount = 0; + this.step = 0; + this.structDataSize = 0; + this.structPointerCount = 0; + this.nestingLimit = 0x7fffffff; + } + + public ListReader(SegmentReader segment, int ptr, + int elementCount, int step, + int structDataSize, short structPointerCount, + int nestingLimit) { + this.segment = segment; + this.ptr = ptr; + this.elementCount = elementCount; + this.step = step; + this.structDataSize = structDataSize; + this.structPointerCount = structPointerCount; + this.nestingLimit = nestingLimit; + + } + + public int size() { + return this.elementCount; + } + + public StructReader getStructElement(int index) { + // TODO check nesting limit + + int indexBit = index * this.step; + + int structData = this.ptr + (indexBit / 8); + int structPointers = structData + (this.structDataSize / 8); + + return new StructReader(this.segment, structData, structPointers / 8, this.structDataSize, + this.structPointerCount, (byte)(indexBit % 8), this.nestingLimit - 1); + } +} diff --git a/generator/src/main/java/org/capnproto/MessageBuilder.java b/generator/src/main/java/org/capnproto/MessageBuilder.java new file mode 100644 index 0000000..d54e9b8 --- /dev/null +++ b/generator/src/main/java/org/capnproto/MessageBuilder.java @@ -0,0 +1,11 @@ +package org.capnproto; + +public final class MessageBuilder { + public T getRoot(FromStructBuilder factory) { + throw new Error("unimplemented"); + } + + public T initRoot(FromStructBuilder factory) { + throw new Error("unimplemented"); + } +} diff --git a/generator/src/main/java/org/capnproto/MessageReader.java b/generator/src/main/java/org/capnproto/MessageReader.java new file mode 100644 index 0000000..1337184 --- /dev/null +++ b/generator/src/main/java/org/capnproto/MessageReader.java @@ -0,0 +1,20 @@ +package org.capnproto; + +import java.nio.ByteBuffer; + +public class MessageReader { + ByteBuffer[] segmentSlices; + + public MessageReader(ByteBuffer[] segmentSlices) { + this.segmentSlices = segmentSlices; + } + + public T getRoot(FromStructReader factory) { + SegmentReader segment = new SegmentReader(this.segmentSlices[0]); + PointerReader pointerReader = PointerReader.getRoot(segment, + new WordPointer(this.segmentSlices[0], 0), + 0x7fffffff /* XXX */); + AnyPointer.Reader any = new AnyPointer.Reader(pointerReader); + return any.getAsStruct(factory); + } +} diff --git a/generator/src/main/java/org/capnproto/PointerBuilder.java b/generator/src/main/java/org/capnproto/PointerBuilder.java new file mode 100644 index 0000000..24faeed --- /dev/null +++ b/generator/src/main/java/org/capnproto/PointerBuilder.java @@ -0,0 +1,20 @@ +package org.capnproto; + +public final class PointerBuilder { + public SegmentBuilder segment; + public int pointer; // word offset + + public PointerBuilder(SegmentBuilder segment, int pointer) { + this.segment = segment; + this.pointer = pointer; + } + + public final ListBuilder initStructList(int elementCount, StructSize elementSize) { + return WireHelpers.initStructListPointer(this.pointer, this.segment, elementCount, elementSize); + } + + public final void setText(Text.Reader value) { + WireHelpers.setTextPointer(this.pointer, this.segment, value); + } + +} diff --git a/generator/src/main/java/org/capnproto/PointerReader.java b/generator/src/main/java/org/capnproto/PointerReader.java new file mode 100644 index 0000000..8f2e27c --- /dev/null +++ b/generator/src/main/java/org/capnproto/PointerReader.java @@ -0,0 +1,49 @@ +package org.capnproto; + +public class PointerReader { + public SegmentReader segment; + public int pointer; // word offset + public int nestingLimit; + + public PointerReader() { + this.segment = null; + this.pointer = 0; // XXX ? + this.nestingLimit = 0x7fffffff; + } + + public PointerReader(SegmentReader segment, int pointer, int nestingLimit) { + this.segment = segment; + this.pointer = pointer; + this.nestingLimit = nestingLimit; + } + + public static PointerReader getRoot(SegmentReader segment, + WordPointer location, + int nestingLimit) { + // TODO bounds check + return new PointerReader(segment, location.offset, nestingLimit); + } + + public boolean isNull() { + return this.segment.buffer.getLong(this.pointer) == 0; + } + + public StructReader getStruct() { + return WireHelpers.readStructPointer(this.segment, + this.pointer, + this.nestingLimit); + } + + public ListReader getList(byte expectedElementSize) { + // TODO check nullness + return WireHelpers.readListPointer(this.segment, + this.pointer, + expectedElementSize, + this.nestingLimit); + } + + public Text.Reader getText() { + return WireHelpers.readTextPointer(this.segment, + this.pointer); + } +} diff --git a/generator/src/main/java/org/capnproto/SegmentBuilder.java b/generator/src/main/java/org/capnproto/SegmentBuilder.java new file mode 100644 index 0000000..08e1160 --- /dev/null +++ b/generator/src/main/java/org/capnproto/SegmentBuilder.java @@ -0,0 +1,34 @@ +package org.capnproto; + +import java.nio.ByteBuffer; + +public class SegmentBuilder extends SegmentReader { + public int pos = 0; // in words + + public static final int FAILED_ALLOCATION = -1; + + public SegmentBuilder(ByteBuffer buf) { + super(buf); + } + + // the total number of words the buffer can hold + private final int capacity() { + this.buffer.reset(); + return this.buffer.remaining() / 8; + } + + // return how many words have already been allocated + private final int currentSize() { + return this.pos; + } + + public final int allocate(int amount) { + if (amount > this.capacity() - this.currentSize()) { + return FAILED_ALLOCATION; // no space left; + } else { + int result = this.pos; + this.pos += amount; + return result; + } + } +} diff --git a/generator/src/main/java/org/capnproto/SegmentReader.java b/generator/src/main/java/org/capnproto/SegmentReader.java new file mode 100644 index 0000000..bc53125 --- /dev/null +++ b/generator/src/main/java/org/capnproto/SegmentReader.java @@ -0,0 +1,11 @@ +package org.capnproto; + +import java.nio.ByteBuffer; + +public class SegmentReader { + ByteBuffer buffer; + + public SegmentReader(ByteBuffer buffer) { + this.buffer = buffer; + } +} diff --git a/generator/src/main/java/org/capnproto/StructBuilder.java b/generator/src/main/java/org/capnproto/StructBuilder.java new file mode 100644 index 0000000..3ff05c0 --- /dev/null +++ b/generator/src/main/java/org/capnproto/StructBuilder.java @@ -0,0 +1,42 @@ +package org.capnproto; + +public final class StructBuilder { + public SegmentBuilder segment; + public int data; //byte offset to data section + public int pointers; // word offset of pointer section + public int dataSize; // in bits + public short pointerCount; + public byte bit0Offset; + + public StructBuilder(SegmentBuilder segment, int data, + int pointers, int dataSize, short pointerCount, + byte bit0Offset) { + this.segment = segment; + this.data = data; + this.pointers = pointers; + this.dataSize = dataSize; + this.pointerCount = pointerCount; + this.bit0Offset = bit0Offset; + } + + public final int getShortField(int offset) { + return this.segment.buffer.getShort(this.data + offset * 2); + } + + public final void setShortField(int offset, short value) { + this.segment.buffer.putShort(this.data + offset * 2, value); + } + + public final int getIntField(int offset) { + return this.segment.buffer.getInt(this.data + offset * 4); + } + + public final void setIntField(int offset, int value) { + this.segment.buffer.putInt(this.data + offset * 4, value); + } + + public final PointerBuilder getPointerField(int index) { + return new PointerBuilder(this.segment, this.pointers + index); + } + +} diff --git a/generator/src/main/java/org/capnproto/StructList.java b/generator/src/main/java/org/capnproto/StructList.java new file mode 100644 index 0000000..4027907 --- /dev/null +++ b/generator/src/main/java/org/capnproto/StructList.java @@ -0,0 +1,34 @@ +package org.capnproto; + + +public final class StructList { + public static final class Reader { + public ListReader reader; + public final FromStructReader factory; + + public Reader(ListReader reader, FromStructReader factory) { + this.reader = reader; + this.factory = factory; + } + + public int size() { + return this.reader.size(); + } + + public T get(int index) { + return this.factory.fromStructReader(this.reader.getStructElement(index)); + } + } + + public static final class Builder { + public ListBuilder builder; + public final FromStructBuilder factory; + + public Builder(ListBuilder builder, FromStructBuilder factory) { + this.builder = builder; + this.factory = factory; + } + + } + +} diff --git a/generator/src/main/java/org/capnproto/StructPointer.java b/generator/src/main/java/org/capnproto/StructPointer.java new file mode 100644 index 0000000..656d696 --- /dev/null +++ b/generator/src/main/java/org/capnproto/StructPointer.java @@ -0,0 +1,17 @@ +package org.capnproto; + +import java.nio.ByteBuffer; + +final class StructPointer{ + public static short dataSize(int structRef) { + return (short)(structRef & 0xffff); + } + + public static short ptrCount(int structRef) { + return (short)(structRef >> 16); + } + + public static int wordSize(int structRef) { + return (int)dataSize(structRef) + (int)ptrCount(structRef); + } +} diff --git a/generator/src/main/java/org/capnproto/StructReader.java b/generator/src/main/java/org/capnproto/StructReader.java new file mode 100644 index 0000000..23c45f9 --- /dev/null +++ b/generator/src/main/java/org/capnproto/StructReader.java @@ -0,0 +1,71 @@ +package org.capnproto; + +public final class StructReader { + public SegmentReader segment; + public int data; //byte offset to data section + public int pointers; // word offset of pointer section + public int dataSize; // in bits + public short pointerCount; + public byte bit0Offset; + public int nestingLimit; + + + public StructReader(SegmentReader segment, int data, + int pointers, int dataSize, short pointerCount, + byte bit0Offset, int nestingLimit) { + this.segment = segment; + this.data = data; + this.pointers = pointers; + this.dataSize = dataSize; + this.pointerCount = pointerCount; + this.bit0Offset = bit0Offset; + this.nestingLimit = nestingLimit; + } + + public final boolean getBoolField(int offset) { + // XXX should use unsigned operations + if (offset < this.dataSize) { + if (offset == 0) { + offset = this.bit0Offset; + } + byte b = this.segment.buffer.get(offset / 8); + return (b & (1 << (offset % 8))) != 0; + } else { + return false; + } + } + + public final byte getByteField(int offset) { + if ((offset + 1) * 8 <= this.dataSize) { + return this.segment.buffer.get(this.data + offset); + } else { + return 0; + } + } + + public final byte getShortField(int offset) { + if ((offset + 1) * 16 <= this.dataSize) { + return this.segment.buffer.get(this.data + offset * 2); + } else { + return 0; + } + } + + public final int getIntField(int offset) { + if ((offset + 1) * 32 <= this.dataSize) { + return this.segment.buffer.getInt(this.data + offset * 4); + } else { + return 0; + } + } + + public final PointerReader getPointerField(int ptrIndex) { + if (ptrIndex < this.pointerCount) { + return new PointerReader(this.segment, + this.pointers + ptrIndex, + this.nestingLimit); + } else { + return new PointerReader(); + } + } +} diff --git a/generator/src/main/java/org/capnproto/StructSize.java b/generator/src/main/java/org/capnproto/StructSize.java new file mode 100644 index 0000000..835561d --- /dev/null +++ b/generator/src/main/java/org/capnproto/StructSize.java @@ -0,0 +1,18 @@ +package org.capnproto; + +public final class StructSize { + public final short data; // number of words in data section + public final short pointers; // number of words in pointer section + public final byte preferredListEncoding; // a FieldSize + + public StructSize(short data, short pointers, byte preferredListEncoding) { + this.data = data; + this.pointers = pointers; + this.preferredListEncoding = preferredListEncoding; + } + + public final int total() { + return (int)this.data + (int)this.pointers; + } + +} diff --git a/generator/src/main/java/org/capnproto/Text.java b/generator/src/main/java/org/capnproto/Text.java new file mode 100644 index 0000000..5bad020 --- /dev/null +++ b/generator/src/main/java/org/capnproto/Text.java @@ -0,0 +1,45 @@ +package org.capnproto; + +import java.nio.ByteBuffer; + +public class Text { + + public static class Reader { + public final ByteBuffer buffer; + public final int offset; // in bytes + public final int size; // in bytes + + public Reader(ByteBuffer buffer, int offset, int size) { + this.buffer = buffer; + this.offset = offset * 8; + this.size = size; + } + + public Reader(String value) { + try { + byte[] bytes = value.getBytes("UTF-8"); + this.buffer = ByteBuffer.wrap(bytes); + this.offset = 0; + this.size = bytes.length; + } catch (java.io.UnsupportedEncodingException e) { + throw new Error("UTF-8 is unsupported"); + } + } + + @Override + public final String toString() { + byte[] bytes = new byte[this.size]; + + this.buffer.position(this.offset); + this.buffer.get(bytes, 0, this.size); + + try { + return new String(bytes, "UTF-8"); + } catch(java.io.UnsupportedEncodingException e) { + return "unsupported encoding"; // XXX + } + } + + } + +} diff --git a/generator/src/main/java/org/capnproto/WireHelpers.java b/generator/src/main/java/org/capnproto/WireHelpers.java new file mode 100644 index 0000000..8314dc7 --- /dev/null +++ b/generator/src/main/java/org/capnproto/WireHelpers.java @@ -0,0 +1,174 @@ +package org.capnproto; + +final class WireHelpers { + + public static int roundBytesUpToWords(int bytes) { + return (bytes + 7) / 8; + } + + public static int allocate(int refOffset, + SegmentBuilder segment, + int amount, + byte kind) { + + // TODO check for nullness, amount == 0 case. + + int allocation = segment.allocate(amount); + if (allocation == SegmentBuilder.FAILED_ALLOCATION) { + //# Need to allocate in a new segment. We'll need to + //# allocate an extra pointer worth of space to act as + //# the landing pad for a far pointer. + throw new Error("unimplemented"); + } else { + WirePointer.setKindAndTarget(segment.buffer, refOffset, kind, allocation); + return allocation; + } + } + + public static ListBuilder initListPointer(int refOffset, + SegmentBuilder segment, + int elementCount, + byte elementSize) { + throw new Error("unimplemented"); + } + + public static ListBuilder initStructListPointer(int refOffset, + SegmentBuilder segment, + int elementCount, + StructSize elementSize) { + if (elementSize.preferredListEncoding != FieldSize.INLINE_COMPOSITE) { + //# Small data-only struct. Allocate a list of primitives instead. + return initListPointer(refOffset, segment, elementCount, + elementSize.preferredListEncoding); + } + + int wordsPerElement = elementSize.total(); + + throw new Error("unimplemented"); + } + + // size is in bytes + public static void initTextPointer(int refOffset, + SegmentBuilder segment, + int size) { + //# The byte list must include a NUL terminator. + int byteSize = size + 1; + + int ptrOffset = allocate(refOffset, segment, roundBytesUpToWords(byteSize), WirePointer.LIST); + + throw new Error("unimplemented"); + } + + public static void setTextPointer(int refOffset, + SegmentBuilder segment, + Text.Reader value) { + throw new Error("unimplemented"); + } + + public static StructReader readStructPointer(SegmentReader segment, + int refOffset, + int nestingLimit) { + + // TODO error handling + + if (nestingLimit < 0) { + throw new DecodeException("Message is too deeply nested or contains cycles."); + } + + long ref = WirePointer.get(segment.buffer, refOffset); + int ptrOffset = WirePointer.target(refOffset, ref); + int structPtr = WirePointer.structPointer(ref); + int dataSizeWords = StructPointer.dataSize(structPtr); + + return new StructReader(segment, + ptrOffset * 8, + (ptrOffset + dataSizeWords), + dataSizeWords * 64, + StructPointer.ptrCount(structPtr), + (byte)0, + nestingLimit - 1); + + } + + + public static ListReader readListPointer(SegmentReader segment, + int refOffset, + byte expectedElementSize, + int nestingLimit) { + + long ref = WirePointer.get(segment.buffer, refOffset); + + // TODO check for null, follow fars, nestingLimit + if (WirePointer.isNull(ref)) { + return new ListReader(); + } + + int listPtr = WirePointer.listPointer(ref); + + int ptrOffset = WirePointer.target(refOffset, ref); + long ptr = WirePointer.get(segment.buffer, ptrOffset); + + switch (ListPointer.elementSize(listPtr)) { + case FieldSize.INLINE_COMPOSITE : { + int wordCount = ListPointer.inlineCompositeWordCount(listPtr); + + long tag = ptr; + ptrOffset += 1; + + // TODO bounds check + + int size = WirePointer.inlineCompositeListElementCount(tag); + + int structPtr = WirePointer.structPointer(tag); + int wordsPerElement = StructPointer.wordSize(structPtr); + + // TODO check that elemements do not overrun word count + + // TODO check whether the size is compatible + + return new ListReader(segment, // TODO follow fars + ptrOffset * 8, // + size, + wordsPerElement * 64, + StructPointer.dataSize(structPtr) * 64, + StructPointer.ptrCount(structPtr), + nestingLimit - 1); + } + case FieldSize.VOID : break; + default : + throw new Error("unrecognized element size"); + } + + throw new Error(); + } + + public static Text.Reader readTextPointer(SegmentReader segment, + int refOffset) { + long ref = WirePointer.get(segment.buffer, refOffset); + + if (WirePointer.isNull(ref)) { + // XXX should use the default value + return new Text.Reader(java.nio.ByteBuffer.wrap(new byte[0]), 0, 0); + } + + int ptrOffset = WirePointer.target(refOffset, ref); + int listPtr = WirePointer.listPointer(ref); + int size = ListPointer.elementCount(listPtr); + + if (WirePointer.kind(ref) != WirePointer.LIST) { + throw new DecodeException("Message contains non-list pointer where text was expected."); + } + + if (ListPointer.elementSize(listPtr) != FieldSize.BYTE) { + throw new DecodeException("Message contains list pointer of non-bytes where text was expected."); + } + + // TODO bounds check? + + if (size == 0 || segment.buffer.get(8 * ptrOffset + size - 1) != 0) { + throw new DecodeException("Message contains text that is not NUL-terminated."); + } + + return new Text.Reader(segment.buffer, ptrOffset, size - 1); + } +} diff --git a/generator/src/main/java/org/capnproto/WirePointer.java b/generator/src/main/java/org/capnproto/WirePointer.java new file mode 100644 index 0000000..9c90f94 --- /dev/null +++ b/generator/src/main/java/org/capnproto/WirePointer.java @@ -0,0 +1,51 @@ +package org.capnproto; + +import java.nio.ByteBuffer; + +final class WirePointer { + public static final byte STRUCT = 0; + public static final byte LIST = 1; + public static final byte FAR = 2; + public static final byte OTHER = 3; + + public static boolean isNull(long wirePointer) { + return wirePointer == 0; + } + + public static int offsetAndKind(long wirePointer) { + return (int)(wirePointer & 0xffffffff); + } + + public static byte kind(long wirePointer) { + return (byte)(offsetAndKind(wirePointer) & 3); + } + + public static int target(int offset, long wirePointer) { + return offset + 1 + (offsetAndKind(wirePointer) >> 2); + } + + public static void setKindAndTarget(ByteBuffer buffer, int offset, byte kind, int targetOffset) { + buffer.putInt(offset * 8, + (((targetOffset - offset) - 1) << 2) | kind); + } + + public static int inlineCompositeListElementCount(long wirePointer) { + return offsetAndKind(wirePointer) >> 2; + } + + public static int upper32Bits(long wirePointer) { + return (int)(wirePointer >> 32); + } + + public static int listPointer(long wirePointer) { + return upper32Bits(wirePointer); + } + + public static int structPointer(long wirePointer) { + return upper32Bits(wirePointer); + } + + public static long get(ByteBuffer buffer, int offset) { + return buffer.getLong(offset * 8); + } +} diff --git a/generator/src/main/java/org/capnproto/WordPointer.java b/generator/src/main/java/org/capnproto/WordPointer.java new file mode 100644 index 0000000..8ba7f20 --- /dev/null +++ b/generator/src/main/java/org/capnproto/WordPointer.java @@ -0,0 +1,13 @@ +package org.capnproto; + +import java.nio.ByteBuffer; + +class WordPointer { + public final ByteBuffer buffer; + public int offset; // in words + + public WordPointer(ByteBuffer buffer, int offset) { + this.buffer = buffer; + this.offset = offset; + } +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..8ac605a --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.2 diff --git a/project/build.scala b/project/build.scala new file mode 100644 index 0000000..3dbb063 --- /dev/null +++ b/project/build.scala @@ -0,0 +1,89 @@ +import sbt.Keys._ +import sbt._ + +object Build extends sbt.Build { + + lazy val root = + project( + id = "capnproto-java", + base = file(".") + ).aggregate(generator, examples) + + lazy val generator = + project( + id = "generator", + base = file("generator") + ).settings(Defaults.itSettings: _*) + .settings(compile <<= compile in Compile dependsOn(compile in Test, compile in IntegrationTest)) + + lazy val examples = + project( + id = "examples", + base = file("examples") + ).dependsOn(generator) + .settings(publish := {}) + .settings(publishLocal := {}) + .settings(fork in run := true) + .settings(outputStrategy := Some(StdoutOutput)) + .settings(javaOptions in run ++= Seq( + "-ms2g", + "-mx2g", + "-XX:+AlwaysPreTouch", + "-XX:+TieredCompilation" + )) + + def project(id: String, base: File) = + Project( + id = id, + base = base, + settings = + Project.defaultSettings ++ + Shared.settings ++ + Seq(libraryDependencies ++= Shared.testDeps) + ).configs(IntegrationTest) +} + +object Shared { + + val testDeps = Seq( + "org.scalatest" %% "scalatest" % "2.1.6" % "it,test", + "org.scalacheck" %% "scalacheck" % "1.11.4" % "it,test" + ) + + val settings = Seq( + scalaVersion := "2.11.0", + scalacOptions := Seq( + "-deprecation", + "-feature", + "-optimise", + "-Yinline-warnings", + "-unchecked", + "-feature" + ), + resolvers += Resolver.sonatypeRepo("snapshots"), + resolvers += Resolver.sonatypeRepo("releases"), + shellPrompt := ShellPrompt.buildShellPrompt + ) +} + +object ShellPrompt { + object devnull extends ProcessLogger { + def info(s: => String) {} + def error(s: => String) {} + def buffer[T](f: => T): T = f + } + + def currBranch = ( + ("git status -sb" lines_! devnull headOption) + getOrElse "-" stripPrefix "## " + ) + + val buildShellPrompt = { + (state: State) => { + val currProject = Project.extract(state).currentProject.id + "[%s](%s)$ ".format( + currProject, currBranch /*, BuildSettings.buildVersion*/ + ) + } + } +} diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..dcb18cc --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,3 @@ +addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.2") + +addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.4") diff --git a/version.sbt b/version.sbt new file mode 100644 index 0000000..e765444 --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version in ThisBuild := "0.1.0"