Commit 10697ea1 authored by Kenton Varda's avatar Kenton Varda

Pull TextBlob out of capnpc-capnp and into KJ as kj::StringTree. Use it in the…

Pull TextBlob out of capnpc-capnp and into KJ as kj::StringTree.  Use it in the stringification code to make that code cleaner and avoid using iostreams.
parent 7b59b551
......@@ -114,6 +114,7 @@ includekj_HEADERS = \
src/kj/array.h \
src/kj/vector.h \
src/kj/string.h \
src/kj/string-tree.h \
src/kj/exception.h \
src/kj/debug.h \
src/kj/arena.h \
......@@ -156,6 +157,7 @@ libcapnp_la_SOURCES= \
src/kj/memory.c++ \
src/kj/array.c++ \
src/kj/string.c++ \
src/kj/string-tree.c++ \
src/kj/exception.c++ \
src/kj/debug.c++ \
src/kj/arena.c++ \
......@@ -233,6 +235,7 @@ capnp_test_SOURCES = \
src/kj/memory-test.c++ \
src/kj/array-test.c++ \
src/kj/string-test.c++ \
src/kj/string-tree-test.c++ \
src/kj/exception-test.c++ \
src/kj/debug-test.c++ \
src/kj/arena-test.c++ \
......
......@@ -451,18 +451,14 @@ private:
{
ParseErrorCatcher catcher;
if (pretty) {
text = prettyPrint(root);
text = kj::str(prettyPrint(root), '\n');
} else {
text = kj::str(root);
text = kj::str(root, '\n');
}
exception = kj::mv(catcher.exception);
}
kj::ArrayPtr<const byte> pieces[2];
pieces[0] = kj::arrayPtr(reinterpret_cast<const byte*>(text.begin()), text.size());
pieces[1] = kj::arrayPtr(reinterpret_cast<const byte*>("\n"), 1);
kj::FdOutputStream(STDOUT_FILENO).write(kj::arrayPtr(pieces, KJ_ARRAY_SIZE(pieces)));
kj::FdOutputStream(STDOUT_FILENO).write(text.begin(), text.size());
KJ_IF_MAYBE(e, exception) {
context.error(kj::str("*** error in previous message ***\n", *e, "\n*** end error ***"));
......
......@@ -28,6 +28,7 @@
#include "../serialize.h"
#include <kj/debug.h>
#include <kj/io.h>
#include <kj/string-tree.h>
#include "../schema-loader.h"
#include "../dynamic.h"
#include <unistd.h>
......@@ -37,146 +38,6 @@
namespace capnp {
namespace {
class TextBlob {
public:
TextBlob() = default;
template <typename... Params>
explicit TextBlob(Params&&... params);
TextBlob(kj::Array<TextBlob>&& params);
void writeTo(kj::OutputStream& out) const;
private:
kj::Array<char> text;
struct Branch;
kj::Array<Branch> branches;
void allocate(size_t textSize, size_t branchCount);
template <typename First, typename... Rest>
void allocate(size_t textSize, size_t branchCount, const First& first, Rest&&... rest);
template <typename... Rest>
void allocate(size_t textSize, size_t branchCount, const TextBlob& first, Rest&&... rest);
void fill(char* textPos, Branch* branchesPos);
template <typename First, typename... Rest>
void fill(char* textPos, Branch* branchesPos, First&& first, Rest&&... rest);
template <typename... Rest>
void fill(char* textPos, Branch* branchesPos, TextBlob&& first, Rest&&... rest);
template <typename T>
auto toContainer(T&& t) -> decltype(kj::toCharSequence(kj::fwd<T>(t))) {
return kj::toCharSequence(kj::fwd<T>(t));
}
TextBlob&& toContainer(TextBlob&& t) {
return kj::mv(t);
}
TextBlob& toContainer(TextBlob& t) {
return t;
}
const TextBlob& toContainer(const TextBlob& t) {
return t;
}
template <typename... Params>
void init(Params&&... params);
};
struct TextBlob::Branch {
char* pos;
TextBlob content;
};
template <typename... Params>
TextBlob::TextBlob(Params&&... params) {
init(toContainer(kj::fwd<Params>(params))...);
}
TextBlob::TextBlob(kj::Array<TextBlob>&& params) {
branches = kj::heapArray<Branch>(params.size());
for (size_t i = 0; i < params.size(); i++) {
branches[i].pos = nullptr;
branches[i].content = kj::mv(params[i]);
}
}
void TextBlob::writeTo(kj::OutputStream& out) const {
const char* pos = text.begin();
for (auto& branch: branches) {
out.write(pos, branch.pos - pos);
pos = branch.pos;
branch.content.writeTo(out);
}
out.write(pos, text.end() - pos);
}
void TextBlob::allocate(size_t textSize, size_t branchCount) {
text = kj::heapArray<char>(textSize);
branches = kj::heapArray<Branch>(branchCount);
}
template <typename First, typename... Rest>
void TextBlob::allocate(size_t textSize, size_t branchCount, const First& first, Rest&&... rest) {
allocate(textSize + first.size(), branchCount, kj::fwd<Rest>(rest)...);
}
template <typename... Rest>
void TextBlob::allocate(size_t textSize, size_t branchCount,
const TextBlob& first, Rest&&... rest) {
allocate(textSize, branchCount + 1, kj::fwd<Rest>(rest)...);
}
void TextBlob::fill(char* textPos, Branch* branchesPos) {
KJ_ASSERT(textPos == text.end(), textPos - text.end());
KJ_ASSERT(branchesPos == branches.end(), branchesPos - branches.end());
}
template <typename First, typename... Rest>
void TextBlob::fill(char* textPos, Branch* branchesPos, First&& first, Rest&&... rest) {
textPos = kj::_::fill(textPos, kj::fwd<First>(first));
fill(textPos, branchesPos, kj::fwd<Rest>(rest)...);
}
template <typename... Rest>
void TextBlob::fill(char* textPos, Branch* branchesPos, TextBlob&& first, Rest&&... rest) {
branchesPos->pos = textPos;
branchesPos->content = kj::mv(first);
++branchesPos;
fill(textPos, branchesPos, kj::fwd<Rest>(rest)...);
}
template <typename... Params>
void TextBlob::init(Params&&... params) {
allocate(0, 0, params...);
fill(text.begin(), branches.begin(), kj::fwd<Params>(params)...);
}
template <typename... Params>
TextBlob text(Params&&... params) {
return TextBlob(kj::fwd<Params>(params)...);
}
template <typename List, typename Func>
TextBlob forText(List&& list, Func&& func) {
kj::Array<TextBlob> items = kj::heapArray<TextBlob>(list.size());
for (size_t i = 0; i < list.size(); i++) {
items[i] = func(list[i]);
}
return TextBlob(kj::mv(items));
}
template <typename T>
struct ForTextHack {
T list;
ForTextHack(T list): list(kj::fwd<T>(list)) {}
template <typename Func>
TextBlob operator*(Func&& func) {
return forText(list, func);
}
};
#define FOR_EACH(list, name) ForTextHack<decltype(list)>(list) * \
[&](decltype((list)[0]) name) -> TextBlob
struct Indent {
uint amount;
Indent() = default;
......@@ -224,7 +85,7 @@ Text::Reader getUnqualifiedName(Schema schema) {
return "(?)";
}
TextBlob nodeName(Schema target, Schema scope) {
kj::StringTree nodeName(Schema target, Schema scope) {
std::vector<Schema> targetParents;
std::vector<Schema> scopeParts;
......@@ -254,49 +115,49 @@ TextBlob nodeName(Schema target, Schema scope) {
// TODO(someday): This is broken in that we aren't checking for shadowing.
TextBlob path = text();
kj::StringTree path = kj::strTree();
while (!targetParents.empty()) {
auto part = targetParents.back();
auto proto = part.getProto();
if (proto.getScopeId() == 0) {
path = text(kj::mv(path), "import \"/", proto.getDisplayName(), "\".");
path = kj::strTree(kj::mv(path), "import \"/", proto.getDisplayName(), "\".");
} else {
path = text(kj::mv(path), getUnqualifiedName(part), ".");
path = kj::strTree(kj::mv(path), getUnqualifiedName(part), ".");
}
targetParents.pop_back();
}
return text(kj::mv(path), getUnqualifiedName(target));
return kj::strTree(kj::mv(path), getUnqualifiedName(target));
}
TextBlob genType(schema::Type::Reader type, Schema scope) {
kj::StringTree genType(schema::Type::Reader type, Schema scope) {
auto body = type.getBody();
switch (body.which()) {
case schema::Type::Body::VOID_TYPE: return text("Void");
case schema::Type::Body::BOOL_TYPE: return text("Bool");
case schema::Type::Body::INT8_TYPE: return text("Int8");
case schema::Type::Body::INT16_TYPE: return text("Int16");
case schema::Type::Body::INT32_TYPE: return text("Int32");
case schema::Type::Body::INT64_TYPE: return text("Int64");
case schema::Type::Body::UINT8_TYPE: return text("UInt8");
case schema::Type::Body::UINT16_TYPE: return text("UInt16");
case schema::Type::Body::UINT32_TYPE: return text("UInt32");
case schema::Type::Body::UINT64_TYPE: return text("UInt64");
case schema::Type::Body::FLOAT32_TYPE: return text("Float32");
case schema::Type::Body::FLOAT64_TYPE: return text("Float64");
case schema::Type::Body::TEXT_TYPE: return text("Text");
case schema::Type::Body::DATA_TYPE: return text("Data");
case schema::Type::Body::VOID_TYPE: return kj::strTree("Void");
case schema::Type::Body::BOOL_TYPE: return kj::strTree("Bool");
case schema::Type::Body::INT8_TYPE: return kj::strTree("Int8");
case schema::Type::Body::INT16_TYPE: return kj::strTree("Int16");
case schema::Type::Body::INT32_TYPE: return kj::strTree("Int32");
case schema::Type::Body::INT64_TYPE: return kj::strTree("Int64");
case schema::Type::Body::UINT8_TYPE: return kj::strTree("UInt8");
case schema::Type::Body::UINT16_TYPE: return kj::strTree("UInt16");
case schema::Type::Body::UINT32_TYPE: return kj::strTree("UInt32");
case schema::Type::Body::UINT64_TYPE: return kj::strTree("UInt64");
case schema::Type::Body::FLOAT32_TYPE: return kj::strTree("Float32");
case schema::Type::Body::FLOAT64_TYPE: return kj::strTree("Float64");
case schema::Type::Body::TEXT_TYPE: return kj::strTree("Text");
case schema::Type::Body::DATA_TYPE: return kj::strTree("Data");
case schema::Type::Body::LIST_TYPE:
return text("List(", genType(body.getListType(), scope), ")");
return kj::strTree("List(", genType(body.getListType(), scope), ")");
case schema::Type::Body::ENUM_TYPE:
return nodeName(scope.getDependency(body.getEnumType()), scope);
case schema::Type::Body::STRUCT_TYPE:
return nodeName(scope.getDependency(body.getStructType()), scope);
case schema::Type::Body::INTERFACE_TYPE:
return nodeName(scope.getDependency(body.getInterfaceType()), scope);
case schema::Type::Body::OBJECT_TYPE: return text("Object");
case schema::Type::Body::OBJECT_TYPE: return kj::strTree("Object");
}
return text();
return kj::strTree();
}
int typeSizeBits(schema::Type::Reader type) {
......@@ -350,28 +211,31 @@ bool isEmptyValue(schema::Value::Reader value) {
return true;
}
TextBlob genValue(schema::Type::Reader type, schema::Value::Reader value, Schema scope) {
kj::StringTree genValue(schema::Type::Reader type, schema::Value::Reader value, Schema scope) {
auto body = value.getBody();
switch (body.which()) {
case schema::Value::Body::VOID_VALUE: return text("void");
case schema::Value::Body::BOOL_VALUE: return text(body.getBoolValue() ? "true" : "false");
case schema::Value::Body::INT8_VALUE: return text((int)body.getInt8Value());
case schema::Value::Body::INT16_VALUE: return text(body.getInt16Value());
case schema::Value::Body::INT32_VALUE: return text(body.getInt32Value());
case schema::Value::Body::INT64_VALUE: return text(body.getInt64Value());
case schema::Value::Body::UINT8_VALUE: return text((uint)body.getUint8Value());
case schema::Value::Body::UINT16_VALUE: return text(body.getUint16Value());
case schema::Value::Body::UINT32_VALUE: return text(body.getUint32Value());
case schema::Value::Body::UINT64_VALUE: return text(body.getUint64Value());
case schema::Value::Body::FLOAT32_VALUE: return text(body.getFloat32Value());
case schema::Value::Body::FLOAT64_VALUE: return text(body.getFloat64Value());
case schema::Value::Body::TEXT_VALUE: return text(DynamicValue::Reader(body.getTextValue()));
case schema::Value::Body::DATA_VALUE: return text(DynamicValue::Reader(body.getDataValue()));
case schema::Value::Body::VOID_VALUE: return kj::strTree("void");
case schema::Value::Body::BOOL_VALUE:
return kj::strTree(body.getBoolValue() ? "true" : "false");
case schema::Value::Body::INT8_VALUE: return kj::strTree((int)body.getInt8Value());
case schema::Value::Body::INT16_VALUE: return kj::strTree(body.getInt16Value());
case schema::Value::Body::INT32_VALUE: return kj::strTree(body.getInt32Value());
case schema::Value::Body::INT64_VALUE: return kj::strTree(body.getInt64Value());
case schema::Value::Body::UINT8_VALUE: return kj::strTree((uint)body.getUint8Value());
case schema::Value::Body::UINT16_VALUE: return kj::strTree(body.getUint16Value());
case schema::Value::Body::UINT32_VALUE: return kj::strTree(body.getUint32Value());
case schema::Value::Body::UINT64_VALUE: return kj::strTree(body.getUint64Value());
case schema::Value::Body::FLOAT32_VALUE: return kj::strTree(body.getFloat32Value());
case schema::Value::Body::FLOAT64_VALUE: return kj::strTree(body.getFloat64Value());
case schema::Value::Body::TEXT_VALUE:
return kj::strTree(DynamicValue::Reader(body.getTextValue()));
case schema::Value::Body::DATA_VALUE:
return kj::strTree(DynamicValue::Reader(body.getDataValue()));
case schema::Value::Body::LIST_VALUE: {
KJ_REQUIRE(type.getBody().which() == schema::Type::Body::LIST_TYPE, "type/value mismatch");
auto value = body.getListValue<DynamicList>(
ListSchema::of(type.getBody().getListType(), scope));
return text(value);
return kj::strTree(value);
}
case schema::Value::Body::ENUM_VALUE: {
KJ_REQUIRE(type.getBody().which() == schema::Type::Body::ENUM_TYPE, "type/value mismatch");
......@@ -380,25 +244,25 @@ TextBlob genValue(schema::Type::Reader type, schema::Value::Reader value, Schema
auto enumerants = enumType.getEnumerants();
KJ_REQUIRE(body.getEnumValue() < enumerants.size(),
"Enum value out-of-range.", body.getEnumValue(), enumNode.getDisplayName());
return text(enumerants[body.getEnumValue()].getName());
return kj::strTree(enumerants[body.getEnumValue()].getName());
}
case schema::Value::Body::STRUCT_VALUE: {
KJ_REQUIRE(type.getBody().which() == schema::Type::Body::STRUCT_TYPE, "type/value mismatch");
auto value = body.getStructValue<DynamicStruct>(
scope.getDependency(type.getBody().getStructType()).asStruct());
return text(value);
return kj::strTree(value);
}
case schema::Value::Body::INTERFACE_VALUE: {
return text("");
return kj::strTree("");
}
case schema::Value::Body::OBJECT_VALUE: {
return text("");
return kj::strTree("");
}
}
return text("");
return kj::strTree("");
}
TextBlob genAnnotation(schema::Annotation::Reader annotation,
kj::StringTree genAnnotation(schema::Annotation::Reader annotation,
Schema scope,
const char* prefix = " ", const char* suffix = "") {
auto decl = schemaLoader.get(annotation.getId());
......@@ -406,14 +270,14 @@ TextBlob genAnnotation(schema::Annotation::Reader annotation,
KJ_REQUIRE(body.which() == schema::Node::Body::ANNOTATION_NODE);
auto annDecl = body.getAnnotationNode();
return text(prefix, "$", nodeName(decl, scope), "(",
return kj::strTree(prefix, "$", nodeName(decl, scope), "(",
genValue(annDecl.getType(), annotation.getValue(), scope), ")", suffix);
}
TextBlob genAnnotations(List<schema::Annotation>::Reader list, Schema scope) {
return FOR_EACH(list, ann) { return genAnnotation(ann, scope); };
kj::StringTree genAnnotations(List<schema::Annotation>::Reader list, Schema scope) {
return kj::strTree(KJ_MAP(list, ann) { return genAnnotation(ann, scope); });
}
TextBlob genAnnotations(Schema schema) {
kj::StringTree genAnnotations(Schema schema) {
auto proto = schema.getProto();
return genAnnotations(proto.getAnnotations(), schemaLoader.get(proto.getScopeId()));
}
......@@ -432,42 +296,55 @@ const char* elementSizeName(schema::ElementSize size) {
return "";
}
TextBlob genStructMember(schema::StructNode::Member::Reader member,
kj::StringTree genStructMember(schema::StructNode::Member::Reader member,
Schema scope, Indent indent, int unionTag = -1) {
switch (member.getBody().which()) {
case schema::StructNode::Member::Body::FIELD_MEMBER: {
auto field = member.getBody().getFieldMember();
int size = typeSizeBits(field.getType());
return text(indent, member.getName(), " @", member.getOrdinal(),
return kj::strTree(
indent, member.getName(), " @", member.getOrdinal(),
" :", genType(field.getType(), scope),
isEmptyValue(field.getDefaultValue()) ? text("") :
text(" = ", genValue(field.getType(), field.getDefaultValue(), scope)),
isEmptyValue(field.getDefaultValue()) ? kj::strTree("") :
kj::strTree(" = ", genValue(field.getType(), field.getDefaultValue(), scope)),
genAnnotations(member.getAnnotations(), scope),
"; # ", size == -1 ? text("ptr[", field.getOffset(), "]")
: text("bits[", field.getOffset() * size, ", ",
"; # ", size == -1 ? kj::strTree("ptr[", field.getOffset(), "]")
: kj::strTree("bits[", field.getOffset() * size, ", ",
(field.getOffset() + 1) * size, ")"),
unionTag != -1 ? text(", union tag = ", unionTag) : text(),
unionTag != -1 ? kj::strTree(", union tag = ", unionTag) : kj::strTree(),
"\n");
}
case schema::StructNode::Member::Body::UNION_MEMBER: {
auto un = member.getBody().getUnionMember();
int i = 0;
return text(indent, member.getName(), " @", member.getOrdinal(),
return kj::strTree(
indent, member.getName(), " @", member.getOrdinal(),
" union", genAnnotations(member.getAnnotations(), scope),
" { # tag bits[", un.getDiscriminantOffset() * 16, ", ",
un.getDiscriminantOffset() * 16 + 16, ")\n",
FOR_EACH(un.getMembers(), member) {
KJ_MAP(un.getMembers(), member) {
return genStructMember(member, scope, indent.next(), i++);
},
indent, "}\n");
}
case schema::StructNode::Member::Body::GROUP_MEMBER: {
auto group = member.getBody().getGroupMember();
return kj::strTree(
indent, member.getName(),
" group", genAnnotations(member.getAnnotations(), scope), " {",
unionTag != -1 ? kj::strTree(" # union tag = ", unionTag) : kj::strTree(), "\n",
KJ_MAP(group.getMembers(), member) {
return genStructMember(member, scope, indent.next());
},
indent, "}\n");
}
}
return text();
return kj::strTree();
}
TextBlob genNestedDecls(Schema schema, Indent indent);
kj::StringTree genNestedDecls(Schema schema, Indent indent);
TextBlob genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent indent) {
kj::StringTree genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent indent) {
auto proto = schema.getProto();
if (proto.getScopeId() != scopeId) {
// This appears to be an alias for something declared elsewhere.
......@@ -480,15 +357,15 @@ TextBlob genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent inde
break;
case schema::Node::Body::STRUCT_NODE: {
auto body = proto.getBody().getStructNode();
return text(
return kj::strTree(
indent, "struct ", name, " @0x", kj::hex(proto.getId()), genAnnotations(schema), " { # ",
body.getDataSectionWordSize() * 8, " bytes, ",
body.getPointerSectionSize(), " ptrs",
body.getPreferredListEncoding() == schema::ElementSize::INLINE_COMPOSITE
? text()
: text(", packed as ", elementSizeName(body.getPreferredListEncoding())),
? kj::strTree()
: kj::strTree(", packed as ", elementSizeName(body.getPreferredListEncoding())),
"\n",
FOR_EACH(body.getMembers(), member) {
KJ_MAP(body.getMembers(), member) {
return genStructMember(member, schema, indent.next());
},
genNestedDecls(schema, indent.next()),
......@@ -497,10 +374,10 @@ TextBlob genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent inde
case schema::Node::Body::ENUM_NODE: {
auto body = proto.getBody().getEnumNode();
uint i = 0;
return text(
return kj::strTree(
indent, "enum ", name, " @0x", kj::hex(proto.getId()), genAnnotations(schema), " {\n",
FOR_EACH(body.getEnumerants(), enumerant) {
return text(indent.next(), enumerant.getName(), " @", i++,
KJ_MAP(body.getEnumerants(), enumerant) {
return kj::strTree(indent.next(), enumerant.getName(), " @", i++,
genAnnotations(enumerant.getAnnotations(), schema), ";\n");
},
genNestedDecls(schema, indent.next()),
......@@ -509,22 +386,23 @@ TextBlob genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent inde
case schema::Node::Body::INTERFACE_NODE: {
auto body = proto.getBody().getInterfaceNode();
uint i = 0;
return text(
return kj::strTree(
indent, "interface ", name, " @0x", kj::hex(proto.getId()),
genAnnotations(schema), " {\n",
FOR_EACH(body.getMethods(), method) {
KJ_MAP(body.getMethods(), method) {
int j = 0;
return text(
return kj::strTree(
indent.next(), method.getName(), " @", i++, "(",
FOR_EACH(method.getParams(), param) {
KJ_MAP(method.getParams(), param) {
bool hasDefault = j >= method.getRequiredParamCount() ||
!isEmptyValue(param.getDefaultValue());
return text(
return kj::strTree(
j++ > 0 ? ", " : "",
param.getName(), ": ", genType(param.getType(), schema),
hasDefault
? text(" = ", genValue(param.getType(), param.getDefaultValue(), schema))
: text(),
? kj::strTree(" = ", genValue(
param.getType(), param.getDefaultValue(), schema))
: kj::strTree(),
genAnnotations(param.getAnnotations(), schema));
},
") :", genType(method.getReturnType(), schema),
......@@ -535,7 +413,7 @@ TextBlob genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent inde
}
case schema::Node::Body::CONST_NODE: {
auto body = proto.getBody().getConstNode();
return text(
return kj::strTree(
indent, "const ", name, " @0x", kj::hex(proto.getId()), " :",
genType(body.getType(), schema), " = ",
genValue(body.getType(), body.getValue(), schema), ";\n");
......@@ -561,33 +439,33 @@ TextBlob genDecl(Schema schema, Text::Reader name, uint64_t scopeId, Indent inde
} else {
targets.setSize(i);
}
return text(
return kj::strTree(
indent, "annotation ", name, " @0x", kj::hex(proto.getId()),
" (", strArray(targets, ", "), ") :",
genType(body.getType(), schema), genAnnotations(schema), ";\n");
}
}
return text();
return kj::strTree();
}
TextBlob genNestedDecls(Schema schema, Indent indent) {
kj::StringTree genNestedDecls(Schema schema, Indent indent) {
uint64_t id = schema.getProto().getId();
return FOR_EACH(schema.getProto().getNestedNodes(), nested) {
return kj::strTree(KJ_MAP(schema.getProto().getNestedNodes(), nested) {
return genDecl(schemaLoader.get(nested.getId()), nested.getName(), id, indent);
};
});
}
TextBlob genFile(Schema file) {
kj::StringTree genFile(Schema file) {
auto proto = file.getProto();
auto body = proto.getBody();
KJ_REQUIRE(body.which() == schema::Node::Body::FILE_NODE, "Expected a file node.",
(uint)body.which());
return text(
return kj::strTree(
"# ", proto.getDisplayName(), "\n",
"@0x", kj::hex(proto.getId()), ";\n",
FOR_EACH(proto.getAnnotations(), ann) { return genAnnotation(ann, file, "", ";\n"); },
KJ_MAP(proto.getAnnotations(), ann) { return genAnnotation(ann, file, "", ";\n"); },
genNestedDecls(file, Indent(0)));
}
......@@ -605,7 +483,10 @@ int main(int argc, char* argv[]) {
kj::BufferedOutputStreamWrapper out(rawOut);
for (auto fileId: request.getRequestedFiles()) {
genFile(schemaLoader.get(fileId)).writeTo(out);
genFile(schemaLoader.get(fileId)).visit(
[&](kj::ArrayPtr<const char> text) {
out.write(text.begin(), text.size());
});
}
return 0;
......
......@@ -139,7 +139,7 @@ constexpr auto docComment = p::optional(p::sequence(
} // namespace
Lexer::Lexer(Orphanage orphanageParam, const ErrorReporter& errorReporterParam)
: orphanage(orphanageParam), errorReporter(errorReporterParam) {
: orphanage(orphanageParam) {
// Note that because passing an lvalue to a parser constructor uses it by-referencee, it's safe
// for us to use parsers.tokenSequence even though we haven't yet constructed it.
......
......@@ -91,7 +91,6 @@ public:
private:
Orphanage orphanage;
const ErrorReporter& errorReporter;
kj::Arena arena;
Parsers parsers;
};
......
......@@ -201,7 +201,7 @@ private:
friend struct DynamicStruct;
friend class DynamicUnion::Builder;
friend kj::String _::unionString(
friend kj::StringTree _::unionString(
_::StructReader reader, const _::RawSchema& schema, uint memberIndex);
};
......@@ -305,7 +305,7 @@ private:
friend class MessageBuilder;
template <typename T, ::capnp::Kind k>
friend struct ::capnp::ToDynamic_;
friend kj::String _::structString(
friend kj::StringTree _::structString(
_::StructReader reader, const _::RawSchema& schema);
friend class Orphanage;
friend class Orphan<DynamicStruct>;
......@@ -666,16 +666,16 @@ private:
// specialization. Has a method apply() which does the work.
};
kj::String KJ_STRINGIFY(const DynamicValue::Reader& value);
kj::String KJ_STRINGIFY(const DynamicValue::Builder& value);
kj::String KJ_STRINGIFY(DynamicEnum value);
kj::String KJ_STRINGIFY(const DynamicObject& value);
kj::String KJ_STRINGIFY(const DynamicUnion::Reader& value);
kj::String KJ_STRINGIFY(const DynamicUnion::Builder& value);
kj::String KJ_STRINGIFY(const DynamicStruct::Reader& value);
kj::String KJ_STRINGIFY(const DynamicStruct::Builder& value);
kj::String KJ_STRINGIFY(const DynamicList::Reader& value);
kj::String KJ_STRINGIFY(const DynamicList::Builder& value);
kj::StringTree KJ_STRINGIFY(const DynamicValue::Reader& value);
kj::StringTree KJ_STRINGIFY(const DynamicValue::Builder& value);
kj::StringTree KJ_STRINGIFY(DynamicEnum value);
kj::StringTree KJ_STRINGIFY(const DynamicObject& value);
kj::StringTree KJ_STRINGIFY(const DynamicUnion::Reader& value);
kj::StringTree KJ_STRINGIFY(const DynamicUnion::Builder& value);
kj::StringTree KJ_STRINGIFY(const DynamicStruct::Reader& value);
kj::StringTree KJ_STRINGIFY(const DynamicStruct::Builder& value);
kj::StringTree KJ_STRINGIFY(const DynamicList::Reader& value);
kj::StringTree KJ_STRINGIFY(const DynamicList::Builder& value);
// -------------------------------------------------------------------
// Orphan <-> Dynamic glue
......
......@@ -30,6 +30,7 @@
#include "list.h"
#include "orphan.h"
#include <kj/string.h>
#include <kj/string-tree.h>
namespace capnp {
......@@ -216,18 +217,18 @@ struct UnionParentType_;
template <typename T>
using UnionParentType = typename UnionParentType_<T>::Type;
kj::String structString(StructReader reader, const RawSchema& schema);
kj::String unionString(StructReader reader, const RawSchema& schema, uint memberIndex);
kj::StringTree structString(StructReader reader, const RawSchema& schema);
kj::StringTree unionString(StructReader reader, const RawSchema& schema, uint memberIndex);
// Declared here so that we can declare inline stringify methods on generated types.
// Defined in stringify.c++, which depends on dynamic.c++, which is allowed not to be linked in.
template <typename T>
inline kj::String structString(StructReader reader) {
inline kj::StringTree structString(StructReader reader) {
return structString(reader, rawSchema<T>());
}
template <typename T>
inline kj::String unionString(StructReader reader) {
inline kj::StringTree unionString(StructReader reader) {
return unionString(reader, rawSchema<UnionParentType<T>>(), unionMemberIndex<T>());
}
......
......@@ -25,14 +25,14 @@
#define CAPNP_PRETTY_PRINT_H_
#include "dynamic.h"
#include <kj/string.h>
#include <kj/string-tree.h>
namespace capnp {
kj::String prettyPrint(DynamicStruct::Reader value);
kj::String prettyPrint(DynamicStruct::Builder value);
kj::String prettyPrint(DynamicList::Reader value);
kj::String prettyPrint(DynamicList::Builder value);
kj::StringTree prettyPrint(DynamicStruct::Reader value);
kj::StringTree prettyPrint(DynamicStruct::Builder value);
kj::StringTree prettyPrint(DynamicList::Reader value);
kj::StringTree prettyPrint(DynamicList::Builder value);
// Print the given Cap'n Proto struct or list with nice indentation. Note that you can pass any
// struct or list reader or builder type to this method, since they can be implicitly converted
// to one of the dynamic types.
......
......@@ -127,9 +127,9 @@ private:
return StructSchema(&_::rawSchema<T>());
}
friend class Schema;
friend kj::String _::structString(
friend kj::StringTree _::structString(
_::StructReader reader, const _::RawSchema& schema);
friend kj::String _::unionString(
friend kj::StringTree _::unionString(
_::StructReader reader, const _::RawSchema& schema, uint memberIndex);
};
......
......@@ -147,7 +147,7 @@ TEST(Stringify, PrettyPrint) {
MallocMessageBuilder builder;
auto root = builder.initRoot<TestAllTypes>();
EXPECT_EQ("()", prettyPrint(root));
EXPECT_EQ("()", prettyPrint(root).flatten());
initTestMessage(root);
......@@ -181,7 +181,8 @@ TEST(Stringify, PrettyPrint) {
" dataField = \"qux\",\n"
" structField = (\n"
" textField = \"nested\",\n"
" structField = (textField = \"really nested\")),\n"
" structField = (\n"
" textField = \"really nested\" ) ),\n"
" enumField = baz,\n"
" voidList = [void, void, void],\n"
" boolList = [false, true, false, true, true],\n"
......@@ -199,10 +200,10 @@ TEST(Stringify, PrettyPrint) {
" textList = [\"quux\", \"corge\", \"grault\"],\n"
" dataList = [\"garply\", \"waldo\", \"fred\"],\n"
" structList = [\n"
" (textField = \"x structlist 1\"),\n"
" (textField = \"x structlist 2\"),\n"
" (textField = \"x structlist 3\")],\n"
" enumList = [qux, bar, grault]),\n"
" ( textField = \"x structlist 1\" ),\n"
" ( textField = \"x structlist 2\" ),\n"
" ( textField = \"x structlist 3\" ) ],\n"
" enumList = [qux, bar, grault] ),\n"
" enumField = corge,\n"
" voidList = [void, void, void, void, void, void],\n"
" boolList = [true, false, false, true],\n"
......@@ -219,11 +220,11 @@ TEST(Stringify, PrettyPrint) {
" textList = [\"plugh\", \"xyzzy\", \"thud\"],\n"
" dataList = [\"oops\", \"exhausted\", \"rfc3092\"],\n"
" structList = [\n"
" (textField = \"structlist 1\"),\n"
" (textField = \"structlist 2\"),\n"
" (textField = \"structlist 3\")],\n"
" enumList = [foo, garply])",
prettyPrint(root));
" ( textField = \"structlist 1\" ),\n"
" ( textField = \"structlist 2\" ),\n"
" ( textField = \"structlist 3\" ) ],\n"
" enumList = [foo, garply] )",
prettyPrint(root).flatten());
}
TEST(Stringify, PrettyPrintAdvanced) {
......@@ -241,58 +242,52 @@ TEST(Stringify, PrettyPrintAdvanced) {
list[2].setTextField("baz");
EXPECT_EQ(
"(structList = [\n"
" ( int32Field = 123,\n"
" textField = \"foo\"),\n"
" ( int32Field = 456,\n"
" textField = \"bar\"),\n"
" ( int32Field = 789,\n"
" textField = \"baz\")])",
prettyPrint(root));
"( structList = [\n"
" (int32Field = 123, textField = \"foo\"),\n"
" (int32Field = 456, textField = \"bar\"),\n"
" (int32Field = 789, textField = \"baz\") ] )",
prettyPrint(root).flatten());
root.setInt32Field(55);
EXPECT_EQ(
"( int32Field = 55,\n"
" structList = [\n"
" ( int32Field = 123,\n"
" textField = \"foo\"),\n"
" ( int32Field = 456,\n"
" textField = \"bar\"),\n"
" ( int32Field = 789,\n"
" textField = \"baz\")])",
prettyPrint(root));
" (int32Field = 123, textField = \"foo\"),\n"
" (int32Field = 456, textField = \"bar\"),\n"
" (int32Field = 789, textField = \"baz\") ] )",
prettyPrint(root).flatten());
}
{
auto root = builder.initRoot<test::TestLists>();
auto ll = root.initInt32ListList(3);
ll.set(0, {123, 456, 789});
ll.set(1, {234, 567, 891});
ll.set(2, {345, 678, 912});
ll.set(0, {123, 456, 789, 1234567890});
ll.set(1, {234, 567, 891, 1234567890});
ll.set(2, {345, 678, 912, 1234567890});
EXPECT_EQ(
"[ [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]]",
prettyPrint(ll));
"[ [123, 456, 789, 1234567890],\n"
" [234, 567, 891, 1234567890],\n"
" [345, 678, 912, 1234567890] ]",
prettyPrint(ll).flatten());
EXPECT_EQ(
"(int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
"( int32ListList = [\n"
" [123, 456, 789, 1234567890],\n"
" [234, 567, 891, 1234567890],\n"
" [345, 678, 912, 1234567890] ] )",
prettyPrint(root).flatten());
root.initList8(0);
EXPECT_EQ(
"( list8 = [],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
" [123, 456, 789, 1234567890],\n"
" [234, 567, 891, 1234567890],\n"
" [345, 678, 912, 1234567890] ] )",
prettyPrint(root).flatten());
auto l8 = root.initList8(1);
l8[0].setF(12);
......@@ -300,24 +295,22 @@ TEST(Stringify, PrettyPrintAdvanced) {
EXPECT_EQ(
"( list8 = [(f = 12)],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
" [123, 456, 789, 1234567890],\n"
" [234, 567, 891, 1234567890],\n"
" [345, 678, 912, 1234567890] ] )",
prettyPrint(root).flatten());
l8 = root.initList8(2);
l8[0].setF(12);
l8[1].setF(34);
EXPECT_EQ(
"( list8 = [\n"
" (f = 12),\n"
" (f = 34)],\n"
"( list8 = [(f = 12), (f = 34)],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
" [123, 456, 789, 1234567890],\n"
" [234, 567, 891, 1234567890],\n"
" [345, 678, 912, 1234567890] ] )",
prettyPrint(root).flatten());
}
{
......@@ -326,19 +319,21 @@ TEST(Stringify, PrettyPrintAdvanced) {
auto s = root.getUn().initAllTypes();
EXPECT_EQ(
"(un = allTypes())",
prettyPrint(root));
prettyPrint(root).flatten());
s.setInt32Field(123);
EXPECT_EQ(
"(un = allTypes(int32Field = 123))",
prettyPrint(root));
"( un = allTypes(int32Field = 123) )",
prettyPrint(root).flatten());
s.setTextField("foo");
s.setUInt64Field(0xffffffffffffffffull);
EXPECT_EQ(
"(un = allTypes(\n"
"( un = allTypes(\n"
" int32Field = 123,\n"
" textField = \"foo\"))",
prettyPrint(root));
" uInt64Field = 18446744073709551615,\n"
" textField = \"foo\" ) )",
prettyPrint(root).flatten());
}
}
......
......@@ -23,9 +23,7 @@
#include "dynamic.h"
#include <kj/debug.h>
#include <sstream>
// TODO(cleanup): Rewrite this using something other than iostream?
#include <kj/vector.h>
namespace capnp {
......@@ -33,96 +31,109 @@ namespace {
static const char HEXDIGITS[] = "0123456789abcdef";
class Indent {
public:
explicit Indent(bool enable): amount(enable ? 1 : 0), hasPrefix(false), isFirst(true) {}
enum PrintMode {
BARE,
// The value is planned to be printed on its own line, unless it is very short and contains
// no inner newlines.
enum ItemType {
INLINE, // Items are simple values that don't need to be on their own lines.
PREFIXED, // Each item is on a new line with some prefix attached (e.g. a field name).
STANDALONE // Each item is on a new line with no prefix.
};
PREFIXED,
// The value is planned to be printed with a prefix, like "memberName = " (a struct field).
Indent startItem(std::ostream& os, ItemType type) {
// Start a new item (list element or struct field) within the parent value, writing a newline
// and indentation if necessary.
PARENTHESIZED
// The value is printed in parenthesized (a union value).
};
if (isFirst) {
isFirst = false;
if (type == INLINE || amount == 0) {
return Indent(amount, true);
}
class Indent {
public:
explicit Indent(bool enable): amount(enable ? 1 : 0) {}
if (hasPrefix) {
os << '\n';
for (uint i = 0; i < amount; i++) {
os << " ";
Indent next() {
return Indent(amount == 0 ? 0 : amount + 1);
}
kj::StringTree delimit(kj::Array<kj::StringTree> items, PrintMode mode) {
if (amount == 0 || canPrintAllInline(items)) {
return kj::StringTree(kj::mv(items), ", ");
} else {
os << ' ';
char delim[amount * 2 + 3];
delim[0] = ',';
delim[1] = '\n';
memset(delim + 2, ' ', amount * 2);
delim[amount * 2 + 2] = '\0';
// If the outer value isn't being printed on its own line, we need to add a newline/indent
// before the first item, otherwise we only add a space on the assumption that it is preceded
// by an open bracket or parenthesis.
return kj::strTree(mode == BARE ? " " : delim + 1,
kj::StringTree(kj::mv(items), kj::StringPtr(delim, amount * 2 + 2)), ' ');
}
return Indent(amount + 1, type == PREFIXED);
} else {
if (type == INLINE || amount == 0) {
os << ", ";
return Indent(amount, true);
}
os << ",\n";
for (uint i = 0; i < amount; i++) {
os << " ";
}
return Indent(amount + 1, type == PREFIXED);
private:
uint amount;
explicit Indent(uint amount): amount(amount) {}
static constexpr size_t maxInlineValueSize = 24;
static bool canPrintInline(const kj::StringTree& text) {
if (text.size() > maxInlineValueSize) {
return false;
}
char flat[maxInlineValueSize + 1];
text.flattenTo(flat);
flat[text.size()] = '\0';
if (strchr(flat, '\n') != nullptr) {
return false;
}
Indent withPrefix() {
return Indent(amount, true);
return true;
}
private:
Indent(uint amount, bool hasPrefix): amount(amount), hasPrefix(hasPrefix), isFirst(true) {}
uint amount;
bool hasPrefix;
bool isFirst;
static bool canPrintAllInline(const kj::Array<kj::StringTree>& items) {
for (auto& item: items) {
if (!canPrintInline(item)) return false;
}
return true;
}
};
static void print(std::ostream& os, const DynamicValue::Reader& value,
schema::Type::Body::Which which, Indent indent,
bool alreadyParenthesized = false) {
// Print an arbitrary message via the dynamic API by
// iterating over the schema. Look at the handling
// of STRUCT in particular.
schema::Type::Body::Which whichMemberType(const StructSchema::Member& member) {
auto body = member.getProto().getBody();
switch (body.which()) {
case schema::StructNode::Member::Body::UNION_MEMBER:
return schema::Type::Body::VOID_TYPE;
case schema::StructNode::Member::Body::GROUP_MEMBER:
return schema::Type::Body::STRUCT_TYPE;
case schema::StructNode::Member::Body::FIELD_MEMBER:
return body.getFieldMember().getType().getBody().which();
}
KJ_UNREACHABLE;
}
static kj::StringTree print(const DynamicValue::Reader& value,
schema::Type::Body::Which which, Indent indent,
PrintMode mode) {
switch (value.getType()) {
case DynamicValue::UNKNOWN:
os << "?";
break;
return kj::strTree("?");
case DynamicValue::VOID:
os << "void";
break;
return kj::strTree("void");
case DynamicValue::BOOL:
os << (value.as<bool>() ? "true" : "false");
break;
return kj::strTree(value.as<bool>() ? "true" : "false");
case DynamicValue::INT:
os << value.as<int64_t>();
break;
return kj::strTree(value.as<int64_t>());
case DynamicValue::UINT:
os << value.as<uint64_t>();
break;
case DynamicValue::FLOAT: {
return kj::strTree(value.as<uint64_t>());
case DynamicValue::FLOAT:
if (which == schema::Type::Body::FLOAT32_TYPE) {
auto buf = kj::toCharSequence(value.as<float>());
os.write(buf.begin(), buf.size());
return kj::strTree(value.as<float>());
} else {
auto buf = kj::toCharSequence(value.as<double>());
os.write(buf.begin(), buf.size());
}
break;
return kj::strTree(value.as<double>());
}
case DynamicValue::TEXT:
case DynamicValue::DATA: {
os << '\"';
// TODO(someday): Data probably shouldn't be printed as a string.
kj::ArrayPtr<const char> chars;
if (value.getType() == DynamicValue::DATA) {
......@@ -131,176 +142,131 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
} else {
chars = value.as<Text>();
}
kj::Vector<char> escaped(chars.size());
for (char c: chars) {
switch (c) {
case '\a': os << "\\a"; break;
case '\b': os << "\\b"; break;
case '\f': os << "\\f"; break;
case '\n': os << "\\n"; break;
case '\r': os << "\\r"; break;
case '\t': os << "\\t"; break;
case '\v': os << "\\v"; break;
case '\'': os << "\\\'"; break;
case '\"': os << "\\\""; break;
case '\\': os << "\\\\"; break;
case '\a': escaped.addAll(kj::StringPtr("\\a")); break;
case '\b': escaped.addAll(kj::StringPtr("\\b")); break;
case '\f': escaped.addAll(kj::StringPtr("\\f")); break;
case '\n': escaped.addAll(kj::StringPtr("\\n")); break;
case '\r': escaped.addAll(kj::StringPtr("\\r")); break;
case '\t': escaped.addAll(kj::StringPtr("\\t")); break;
case '\v': escaped.addAll(kj::StringPtr("\\v")); break;
case '\'': escaped.addAll(kj::StringPtr("\\\'")); break;
case '\"': escaped.addAll(kj::StringPtr("\\\"")); break;
case '\\': escaped.addAll(kj::StringPtr("\\\\")); break;
default:
if (c < 0x20) {
escaped.add('\\');
escaped.add('x');
uint8_t c2 = c;
os << "\\x" << HEXDIGITS[c2 / 16] << HEXDIGITS[c2 % 16];
escaped.add(HEXDIGITS[c2 / 16]);
escaped.add(HEXDIGITS[c2 % 16]);
} else {
os << c;
escaped.add(c);
}
break;
}
}
os << '\"';
break;
return kj::strTree('"', escaped, '"');
}
case DynamicValue::LIST: {
os << "[";
auto listValue = value.as<DynamicList>();
// If the members are not primitives and there is more than one member, arrange for
// identation.
Indent::ItemType itemType;
switch (listValue.getSchema().whichElementType()) {
case schema::Type::Body::STRUCT_TYPE:
case schema::Type::Body::LIST_TYPE:
itemType = listValue.size() <= 1 ? Indent::INLINE : Indent::STANDALONE;
break;
default:
itemType = Indent::INLINE;
break;
}
for (auto element: listValue) {
print(os, element, listValue.getSchema().whichElementType(),
indent.startItem(os, itemType));
}
os << "]";
break;
auto which = listValue.getSchema().whichElementType();
kj::Array<kj::StringTree> elements = KJ_MAP(listValue, element) {
return print(element, which, indent.next(), BARE);
};
return kj::strTree('[', indent.delimit(kj::mv(elements), mode), ']');
}
case DynamicValue::ENUM: {
auto enumValue = value.as<DynamicEnum>();
KJ_IF_MAYBE(enumerant, enumValue.getEnumerant()) {
os << enumerant->getProto().getName().cStr();
return kj::strTree(enumerant->getProto().getName());
} else {
// Unknown enum value; output raw number.
os << enumValue.getRaw();
return kj::strTree(enumValue.getRaw());
}
break;
}
case DynamicValue::STRUCT: {
if (!alreadyParenthesized) os << "(";
auto structValue = value.as<DynamicStruct>();
Indent::ItemType itemType = Indent::INLINE;
auto memberSchemas = structValue.getSchema().getMembers();
// If there is more than one member, arrange for indentation.
bool sawOne = false;
for (auto member: structValue.getSchema().getMembers()) {
kj::Vector<kj::StringTree> printedMembers(memberSchemas.size());
for (auto member: memberSchemas) {
if (structValue.has(member)) {
if (sawOne) {
itemType = Indent::PREFIXED;
break;
} else {
sawOne = true;
}
printedMembers.add(kj::strTree(
member.getProto().getName(), " = ",
print(structValue.get(member), whichMemberType(member), indent.next(), PREFIXED)));
}
}
// Print the members.
for (auto member: structValue.getSchema().getMembers()) {
if (structValue.has(member)) {
Indent subIndent = indent.startItem(os, itemType);
os << member.getProto().getName().cStr() << " = ";
auto memberBody = member.getProto().getBody();
switch (memberBody.which()) {
case schema::StructNode::Member::Body::UNION_MEMBER:
print(os, structValue.get(member), schema::Type::Body::VOID_TYPE, subIndent);
break;
case schema::StructNode::Member::Body::FIELD_MEMBER:
print(os, structValue.get(member),
memberBody.getFieldMember().getType().getBody().which(), subIndent);
break;
}
}
if (mode == PARENTHESIZED) {
return indent.delimit(printedMembers.releaseAsArray(), mode);
} else {
return kj::strTree('(', indent.delimit(printedMembers.releaseAsArray(), mode), ')');
}
if (!alreadyParenthesized) os << ")";
break;
}
case DynamicValue::UNION: {
auto unionValue = value.as<DynamicUnion>();
KJ_IF_MAYBE(tag, unionValue.which()) {
os << tag->getProto().getName().cStr() << "(";
print(os, unionValue.get(),
tag->getProto().getBody().getFieldMember().getType().getBody().which(),
indent.withPrefix(), true /* alreadyParenthesized */);
os << ")";
return kj::strTree(
tag->getProto().getName(), '(',
print(unionValue.get(), whichMemberType(*tag), indent, PARENTHESIZED), ')');
} else {
// Unknown union member; must have come from newer
// version of the protocol.
os << "?";
return kj::strTree("<unknown union member>");
}
break;
}
case DynamicValue::INTERFACE:
KJ_FAIL_ASSERT("Don't know how to print interfaces.") {
break;
return kj::String();
}
break;
case DynamicValue::OBJECT:
os << "<opaque object>";
break;
return kj::strTree("<opaque object>");
}
KJ_UNREACHABLE;
}
kj::String stringify(DynamicValue::Reader value) {
std::stringstream out;
print(out, value, schema::Type::Body::STRUCT_TYPE, Indent(false));
auto content = out.str();
return kj::heapString(content.data(), content.size());
kj::StringTree stringify(DynamicValue::Reader value) {
return print(value, schema::Type::Body::STRUCT_TYPE, Indent(false), BARE);
}
} // namespace
kj::String prettyPrint(DynamicStruct::Reader value) {
std::stringstream out;
print(out, value, schema::Type::Body::STRUCT_TYPE, Indent(true));
auto content = out.str();
return kj::heapString(content.data(), content.size());
kj::StringTree prettyPrint(DynamicStruct::Reader value) {
return print(value, schema::Type::Body::STRUCT_TYPE, Indent(true), BARE);
}
kj::String prettyPrint(DynamicList::Reader value) {
std::stringstream out;
print(out, value, schema::Type::Body::LIST_TYPE, Indent(true));
auto content = out.str();
return kj::heapString(content.data(), content.size());
kj::StringTree prettyPrint(DynamicList::Reader value) {
return print(value, schema::Type::Body::LIST_TYPE, Indent(true), BARE);
}
kj::String prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
kj::String prettyPrint(DynamicList::Builder value) { return prettyPrint(value.asReader()); }
kj::StringTree prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
kj::StringTree prettyPrint(DynamicList::Builder value) { return prettyPrint(value.asReader()); }
kj::String KJ_STRINGIFY(const DynamicValue::Reader& value) { return stringify(value); }
kj::String KJ_STRINGIFY(const DynamicValue::Builder& value) { return stringify(value.asReader()); }
kj::String KJ_STRINGIFY(DynamicEnum value) { return stringify(value); }
kj::String KJ_STRINGIFY(const DynamicObject& value) { return stringify(value); }
kj::String KJ_STRINGIFY(const DynamicUnion::Reader& value) { return stringify(value); }
kj::String KJ_STRINGIFY(const DynamicUnion::Builder& value) { return stringify(value.asReader()); }
kj::String KJ_STRINGIFY(const DynamicStruct::Reader& value) { return stringify(value); }
kj::String KJ_STRINGIFY(const DynamicStruct::Builder& value) { return stringify(value.asReader()); }
kj::String KJ_STRINGIFY(const DynamicList::Reader& value) { return stringify(value); }
kj::String KJ_STRINGIFY(const DynamicList::Builder& value) { return stringify(value.asReader()); }
kj::StringTree KJ_STRINGIFY(const DynamicValue::Reader& value) { return stringify(value); }
kj::StringTree KJ_STRINGIFY(const DynamicValue::Builder& value) { return stringify(value.asReader()); }
kj::StringTree KJ_STRINGIFY(DynamicEnum value) { return stringify(value); }
kj::StringTree KJ_STRINGIFY(const DynamicObject& value) { return stringify(value); }
kj::StringTree KJ_STRINGIFY(const DynamicUnion::Reader& value) { return stringify(value); }
kj::StringTree KJ_STRINGIFY(const DynamicUnion::Builder& value) { return stringify(value.asReader()); }
kj::StringTree KJ_STRINGIFY(const DynamicStruct::Reader& value) { return stringify(value); }
kj::StringTree KJ_STRINGIFY(const DynamicStruct::Builder& value) { return stringify(value.asReader()); }
kj::StringTree KJ_STRINGIFY(const DynamicList::Reader& value) { return stringify(value); }
kj::StringTree KJ_STRINGIFY(const DynamicList::Builder& value) { return stringify(value.asReader()); }
namespace _ { // private
kj::String structString(StructReader reader, const RawSchema& schema) {
kj::StringTree structString(StructReader reader, const RawSchema& schema) {
return stringify(DynamicStruct::Reader(StructSchema(&schema), reader));
}
kj::String unionString(StructReader reader, const RawSchema& schema, uint memberIndex) {
kj::StringTree unionString(StructReader reader, const RawSchema& schema, uint memberIndex) {
return stringify(DynamicUnion::Reader(
StructSchema(&schema).getMembers()[memberIndex].asUnion(), reader));
}
......
......@@ -329,5 +329,11 @@ TEST(Array, OwnConst) {
EXPECT_EQ(456, ci2[1]);
}
TEST(Array, Map) {
StringPtr foo = "abcd";
Array<char> bar = KJ_MAP(foo, c) -> char { return c + 1; };
EXPECT_STREQ("bcde", str(bar).cStr());
}
} // namespace
} // namespace kj
......@@ -460,6 +460,36 @@ private:
T content[fixedSize];
};
// =======================================================================================
// KJ_MAP_ARRAY
#define KJ_MAP(array, elementName) \
::kj::_::Mapper<decltype(array)>(array) * [&](decltype(*(array).begin()) elementName)
// Applies some function to every element of an array, returning an Array of the results, with
// nice syntax. Example:
//
// StringPtr foo = "abcd";
// Array<char> bar = KJ_MAP(foo, c) -> char { return c + 1; };
// KJ_ASSERT(str(bar) == "bcde");
namespace _ { // private
template <typename T>
struct Mapper {
T array;
Mapper(T array): array(kj::fwd<T>(array)) {}
template <typename Func>
auto operator*(Func&& func) -> Array<decltype(func(*array.begin()))> {
auto builder = heapArrayBuilder<decltype(func(*array.begin()))>(array.size());
for (auto iter = array.begin(); iter != array.end(); ++iter) {
builder.add(func(*iter));
}
return builder.finish();
}
};
} // namespace _ (private)
// =======================================================================================
// Inline implementation details
......
// 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 "string-tree.h"
#include <gtest/gtest.h>
namespace kj {
namespace _ { // private
namespace {
TEST(StringTree, StrTree) {
EXPECT_EQ("foobar", strTree("foo", "bar").flatten());
EXPECT_EQ("1 2 3 4", strTree(1, " ", 2u, " ", 3l, " ", 4ll).flatten());
EXPECT_EQ("1.5 foo 1e15 bar -3", strTree(1.5f, " foo ", 1e15, " bar ", -3).flatten());
EXPECT_EQ("foo", strTree('f', 'o', 'o').flatten());
{
StringTree tree = strTree(strTree(str("foo"), str("bar")), "baz");
EXPECT_EQ("foobarbaz", tree.flatten());
uint pieceCount = 0;
tree.visit([&](ArrayPtr<const char> part) { ++pieceCount; EXPECT_EQ(3, part.size()); });
EXPECT_EQ(3, pieceCount);
}
EXPECT_EQ("<foobarbaz>", str('<', strTree(str("foo"), "bar", str("baz")), '>'));
}
TEST(StringTree, DelimitedArray) {
Array<StringTree> arr = heapArray<StringTree>(4);
arr[0] = strTree("foo");
arr[1] = strTree("bar");
arr[2] = strTree("baz");
arr[3] = strTree("qux");
EXPECT_EQ("foo, bar, baz, qux", StringTree(kj::mv(arr), ", ").flatten());
}
} // namespace
} // namespace _ (private)
} // namespace kj
// 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 "string-tree.h"
namespace kj {
StringTree::StringTree(Array<StringTree>&& pieces, StringPtr delim)
: size_(0),
branches(heapArray<Branch>(pieces.size())) {
if (pieces.size() > 0) {
if (pieces.size() > 1 && delim.size() > 0) {
text = heapString((pieces.size() - 1) * delim.size());
size_ = text.size();
}
branches[0].index = 0;
branches[0].content = kj::mv(pieces[0]);
size_ += pieces[0].size();
for (uint i = 1; i < pieces.size(); i++) {
if (delim.size() > 0) {
memcpy(text.begin() + (i - 1) * delim.size(), delim.begin(), delim.size());
}
branches[i].index = i * delim.size();
branches[i].content = kj::mv(pieces[i]);
size_ += pieces[i].size();
}
}
}
String StringTree::flatten() const {
String result = heapString(size());
flattenTo(result.begin());
return result;
}
void StringTree::flattenTo(char* __restrict__ target) const {
visit([&target](ArrayPtr<const char> text) {
memcpy(target, text.begin(), text.size());
target += text.size();
});
}
} // namespace kj
// 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 KJ_STRING_TREE_H_
#define KJ_STRING_TREE_H_
#include "string.h"
namespace kj {
class StringTree {
// A long string, represented internally as a tree of strings. This data structure is like a
// String, but optimized for concatenation and iteration at the expense of seek time. The
// structure is intended to be used for building large text blobs from many small pieces, where
// repeatedly concatenating smaller strings into larger ones would waste copies. This structure
// is NOT intended for use cases requiring random access or computing substrings. For those,
// you should use a Rope, which is a much more complicated data structure.
//
// The proper way to construct a StringTree is via kj::strTree(...), which works just like
// kj::str(...) but returns a StringTree rather than a String.
//
// KJ_STRINGIFY() functions that construct large strings from many smaller strings are encouraged
// to return StringTree rather than a flat char container.
public:
inline StringTree(): size_(0) {}
inline StringTree(String&& text): size_(text.size()), text(kj::mv(text)) {}
StringTree(Array<StringTree>&& pieces, StringPtr delim);
// Build a StringTree by concatenating the given pieces, delimited by the given delimiter
// (e.g. ", ").
inline size_t size() const { return size_; }
template <typename Func>
void visit(Func&& func) const;
String flatten() const;
// Return the contents as a string.
// TODO(someday): flatten() when *this is an rvalue and when branches.size() == 0 could simply
// return `kj::mv(text)`. Requires reference qualifiers (Clang 3.3 / GCC 4.8).
void flattenTo(char* __restrict__ target) const;
// Copy the contents to the given character array. Does not add a NUL terminator.
private:
size_t size_;
String text;
struct Branch;
Array<Branch> branches; // In order.
inline void fill(char* pos, size_t branchIndex);
template <typename First, typename... Rest>
void fill(char* pos, size_t branchIndex, First&& first, Rest&&... rest);
template <typename... Rest>
void fill(char* pos, size_t branchIndex, StringTree&& first, Rest&&... rest);
template <typename... Rest>
void fill(char* pos, size_t branchIndex, Array<char>&& first, Rest&&... rest);
template <typename... Rest>
void fill(char* pos, size_t branchIndex, String&& first, Rest&&... rest);
template <typename... Params>
static StringTree concat(Params&&... params);
static StringTree&& concat(StringTree&& param) { return kj::mv(param); }
template <typename T>
static inline size_t flatSize(const T& t) { return t.size(); }
static inline size_t flatSize(String&& s) { return 0; }
static inline size_t flatSize(StringTree&& s) { return 0; }
template <typename T>
static inline size_t branchCount(const T& t) { return 0; }
static inline size_t branchCount(String&& s) { return 1; }
static inline size_t branchCount(StringTree&& s) { return 1; }
template <typename... Params>
friend StringTree strTree(Params&&... params);
};
inline StringTree&& KJ_STRINGIFY(StringTree&& tree) { return kj::mv(tree); }
inline const StringTree& KJ_STRINGIFY(const StringTree& tree) { return tree; }
inline StringTree KJ_STRINGIFY(Array<StringTree>&& trees) { return StringTree(kj::mv(trees), ""); }
template <typename... Params>
StringTree strTree(Params&&... params);
// Build a StringTree by stringifying the given parameters and concatenating the results.
// If any of the parameters stringify to StringTree rvalues, they will be incorporated as
// branches to avoid a copy.
// =======================================================================================
// Inline implementation details
namespace _ { // private
template <typename... Rest>
char* fill(char* __restrict__ target, const StringTree& first, Rest&&... rest) {
// Make str() work with stringifiers that return StringTree by patching fill().
first.flattenTo(target);
return fill(target + first.size(), kj::fwd<Rest>(rest)...);
}
template <typename T> constexpr bool isStringTree() { return false; }
template <> constexpr bool isStringTree<StringTree>() { return true; }
inline StringTree&& toStringTreeOrCharSequnece(StringTree&& tree) { return kj::mv(tree); }
inline StringTree toStringTreeOrCharSequnece(String&& str) { return StringTree(kj::mv(str)); }
template <typename T>
inline auto toStringTreeOrCharSequnece(T&& value)
-> decltype(toCharSequence(kj::fwd<T>(value))) {
static_assert(!isStringTree<Decay<T>>(),
"When passing a StringTree into kj::strTree(), either pass it by rvalue "
"(use kj::mv(value)) or explicitly call value.flatten() to make a copy.");
return toCharSequence(kj::fwd<T>(value));
}
} // namespace _ (private)
struct StringTree::Branch {
size_t index;
// Index in `text` where this branch should be inserted.
StringTree content;
};
template <typename Func>
void StringTree::visit(Func&& func) const {
size_t pos = 0;
for (auto& branch: branches) {
if (branch.index > pos) {
func(text.slice(pos, branch.index));
pos = branch.index;
}
branch.content.visit(func);
}
if (text.size() > pos) {
func(text.slice(pos, text.size()));
}
}
inline void StringTree::fill(char* pos, size_t branchIndex) {
KJ_IREQUIRE(pos == text.end() && branchIndex == branches.size(),
kj::str(text.end() - pos, ' ', branches.size() - branchIndex).cStr());
}
template <typename First, typename... Rest>
void StringTree::fill(char* pos, size_t branchIndex, First&& first, Rest&&... rest) {
pos = _::fill(pos, kj::fwd<First>(first));
fill(pos, branchIndex, kj::fwd<Rest>(rest)...);
}
template <typename... Rest>
void StringTree::fill(char* pos, size_t branchIndex, StringTree&& first, Rest&&... rest) {
branches[branchIndex].index = pos - text.begin();
branches[branchIndex].content = kj::mv(first);
fill(pos, branchIndex + 1, kj::fwd<Rest>(rest)...);
}
template <typename... Rest>
void StringTree::fill(char* pos, size_t branchIndex, String&& first, Rest&&... rest) {
branches[branchIndex].index = pos - text.begin();
branches[branchIndex].content = StringTree(kj::mv(first));
fill(pos, branchIndex + 1, kj::fwd<Rest>(rest)...);
}
template <typename... Params>
StringTree StringTree::concat(Params&&... params) {
StringTree result;
result.size_ = _::sum({params.size()...});
result.text = heapString(
_::sum({StringTree::flatSize(kj::fwd<Params>(params))...}));
result.branches = heapArray<StringTree::Branch>(
_::sum({StringTree::branchCount(kj::fwd<Params>(params))...}));
result.fill(result.text.begin(), 0, kj::fwd<Params>(params)...);
return result;
}
template <typename... Params>
StringTree strTree(Params&&... params) {
return StringTree::concat(_::toStringTreeOrCharSequnece(kj::fwd<Params>(params))...);
}
} // namespace kj
#endif // KJ_STRING_TREE_H_
......@@ -33,6 +33,8 @@ namespace kj {
class StringPtr;
class String;
class StringTree; // string-tree.h
// =======================================================================================
// StringPtr -- A NUL-terminated ArrayPtr<const char> containing UTF-8 text.
//
......@@ -183,6 +185,12 @@ inline size_t sum(std::initializer_list<size_t> nums) {
inline char* fill(char* ptr) { return ptr; }
template <typename... Rest>
char* fill(char* __restrict__ target, const StringTree& first, Rest&&... rest);
// Make str() work with stringifiers that return StringTree by patching fill().
//
// Defined in string-tree.h.
template <typename First, typename... Rest>
char* fill(char* __restrict__ target, const First& first, Rest&&... rest) {
auto i = first.begin();
......@@ -250,9 +258,9 @@ struct Stringifier {
CappedArray<char, sizeof(const void*) * 4> operator*(const void* s) const;
template <typename T>
Array<char> operator*(ArrayPtr<T> arr) const;
String operator*(ArrayPtr<T> arr) const;
template <typename T>
Array<char> operator*(const Array<T>& arr) const;
String operator*(const Array<T>& arr) const;
};
static constexpr Stringifier STR = Stringifier();
......@@ -316,12 +324,12 @@ String strArray(T&& arr, const char* delim) {
namespace _ { // private
template <typename T>
inline Array<char> Stringifier::operator*(ArrayPtr<T> arr) const {
inline String Stringifier::operator*(ArrayPtr<T> arr) const {
return strArray(arr, ", ");
}
template <typename T>
inline Array<char> Stringifier::operator*(const Array<T>& arr) const {
inline String Stringifier::operator*(const Array<T>& arr) const {
return strArray(arr, ", ");
}
......
......@@ -193,10 +193,10 @@ private:
friend struct ::capnp::List;
friend class ::capnp::MessageBuilder;
friend class ::capnp::Orphanage;
friend ::kj::String KJ_STRINGIFY({{typeFullName}}::Reader reader);
friend ::kj::StringTree KJ_STRINGIFY({{typeFullName}}::Reader reader);
};
inline ::kj::String KJ_STRINGIFY({{typeFullName}}::Reader reader) {
inline ::kj::StringTree KJ_STRINGIFY({{typeFullName}}::Reader reader) {
{{#typeStruct}}
return ::capnp::_::structString<{{typeFullName}}>(reader._reader);
{{/typeStruct}}
......@@ -273,10 +273,10 @@ private:
template <typename T, ::capnp::Kind k>
friend struct ::capnp::ToDynamic_;
friend class ::capnp::Orphanage;
friend ::kj::String KJ_STRINGIFY({{typeFullName}}::Builder builder);
friend ::kj::StringTree KJ_STRINGIFY({{typeFullName}}::Builder builder);
};
inline ::kj::String KJ_STRINGIFY({{typeFullName}}::Builder builder) {
inline ::kj::StringTree KJ_STRINGIFY({{typeFullName}}::Builder builder) {
{{#typeStruct}}
return ::capnp::_::structString<{{typeFullName}}>(builder._builder.asReader());
{{/typeStruct}}
......
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