Commit f08b2f90 authored by Kenton Varda's avatar Kenton Varda

Complete C++ .capnp parser. Also, add a pretty-printing function for arbitrary…

Complete C++ .capnp parser.  Also, add a pretty-printing function for arbitrary Cap'n Proto objects which is like the existing stringification but uses whitespace (newlines an indentation) to make the structures more readable.
parent 27b8137f
...@@ -126,6 +126,7 @@ includecapnp_HEADERS = \ ...@@ -126,6 +126,7 @@ includecapnp_HEADERS = \
src/capnp/schema.h \ src/capnp/schema.h \
src/capnp/schema-loader.h \ src/capnp/schema-loader.h \
src/capnp/dynamic.h \ src/capnp/dynamic.h \
src/capnp/pretty-print.h \
src/capnp/serialize.h \ src/capnp/serialize.h \
src/capnp/serialize-packed.h \ src/capnp/serialize-packed.h \
src/capnp/generated-header-support.h src/capnp/generated-header-support.h
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "lexer.h" #include "lexer.h"
#include "parser.h" #include "parser.h"
#include <capnp/pretty-print.h>
#include <kj/vector.h> #include <kj/vector.h>
#include <kj/io.h> #include <kj/io.h>
#include <unistd.h> #include <unistd.h>
...@@ -40,33 +41,38 @@ public: ...@@ -40,33 +41,38 @@ public:
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
// Eventually this will be capnpc. For now it's just a dummy program that tests parsing. // Eventually this will be capnpc. For now it's just a dummy program that tests parsing.
// kj::Vector<char> input; kj::Vector<char> input;
// char buffer[4096]; char buffer[4096];
// for (;;) { for (;;) {
// ssize_t n; ssize_t n;
// KJ_SYSCALL(n = read(STDIN_FILENO, buffer, sizeof(buffer))); KJ_SYSCALL(n = read(STDIN_FILENO, buffer, sizeof(buffer)));
// if (n == 0) { if (n == 0) {
// break; break;
// } }
// input.addAll(buffer, buffer + n); input.addAll(buffer, buffer + n);
// } }
//
// KJ_DBG(input);
// This input triggers a data corruption bug. Fix it before doing anything else!
kj::StringPtr input = "@0xfa974d18d718428e; const x :Int32 = 1;";
CoutErrorReporter errorReporter; CoutErrorReporter errorReporter;
std::cout << "=========================================================================\n"
<< "lex\n"
<< "========================================================================="
<< std::endl;
capnp::MallocMessageBuilder lexerArena; capnp::MallocMessageBuilder lexerArena;
auto lexedFile = lexerArena.initRoot<capnp::compiler::LexedStatements>(); auto lexedFile = lexerArena.initRoot<capnp::compiler::LexedStatements>();
capnp::compiler::lex(input, lexedFile, errorReporter); capnp::compiler::lex(input, lexedFile, errorReporter);
KJ_DBG(lexedFile); std::cout << capnp::prettyPrint(lexedFile).cStr() << std::endl;
std::cout << "=========================================================================\n"
<< "parse\n"
<< "========================================================================="
<< std::endl;
capnp::MallocMessageBuilder parserArena; capnp::MallocMessageBuilder parserArena;
auto parsedFile = parserArena.initRoot<capnp::compiler::ParsedFile>(); auto parsedFile = parserArena.initRoot<capnp::compiler::ParsedFile>();
capnp::compiler::parseFile(lexedFile.getStatements(), parsedFile, errorReporter); capnp::compiler::parseFile(lexedFile.getStatements(), parsedFile, errorReporter);
KJ_DBG(parsedFile); std::cout << capnp::prettyPrint(parsedFile).cStr() << std::endl;
return 0; return 0;
} }
...@@ -22,6 +22,11 @@ ...@@ -22,6 +22,11 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@0xc56be168dcbbc3c6; @0xc56be168dcbbc3c6;
# The structures in this file correspond to the AST of the Cap'n Proto schema language.
#
# This file is intended to be used internally by capnpc. Mostly, it is useful because it is more
# convenient that defining data classes in C++, particularly where variant types (unions) are
# needed. Over time, this file may change in backwards-incompatible ways.
using Cxx = import "/capnp/c++.capnp"; using Cxx = import "/capnp/c++.capnp";
...@@ -121,7 +126,11 @@ struct Declaration { ...@@ -121,7 +126,11 @@ struct Declaration {
annotations @5 :List(AnnotationApplication); annotations @5 :List(AnnotationApplication);
struct AnnotationApplication { struct AnnotationApplication {
name @0 :DeclName; name @0 :DeclName;
value @1 :ValueExpression;
value @1 union {
none @2 :Void; # None specified; implies void value.
expression @3 :ValueExpression;
}
} }
startByte @18 :UInt32; startByte @18 :UInt32;
...@@ -137,6 +146,7 @@ struct Declaration { ...@@ -137,6 +146,7 @@ struct Declaration {
structDecl @11 :Struct; structDecl @11 :Struct;
fieldDecl @12 :Field; fieldDecl @12 :Field;
unionDecl @13 :Union; unionDecl @13 :Union;
groupDecl @23 :Group;
interfaceDecl @14 :Interface; interfaceDecl @14 :Interface;
methodDecl @15 :Method; methodDecl @15 :Method;
annotationDecl @16 :Annotation; annotationDecl @16 :Annotation;
...@@ -178,13 +188,19 @@ struct Declaration { ...@@ -178,13 +188,19 @@ struct Declaration {
struct Method { struct Method {
params @0 :List(Param); params @0 :List(Param);
struct Param { struct Param {
type @0 :TypeExpression; name @0 :LocatedText; # If null, param failed to parse.
annotations @4 :List(AnnotationApplication); type @1 :TypeExpression;
defaultValue @1 union { annotations @2 :List(AnnotationApplication);
none @2 :Void; defaultValue @3 union {
value @3 :ValueExpression; none @4 :Void;
value @5 :ValueExpression;
} }
} }
returnType @1 union {
none @2 :Void; # No return type specified; implied Void.
expression @3 :TypeExpression;
}
} }
struct Annotation { struct Annotation {
......
This diff is collapsed.
...@@ -106,6 +106,7 @@ public: ...@@ -106,6 +106,7 @@ public:
Parser<Orphan<Declaration::AnnotationApplication>> annotation; Parser<Orphan<Declaration::AnnotationApplication>> annotation;
Parser<Orphan<LocatedInteger>> uid; Parser<Orphan<LocatedInteger>> uid;
Parser<Orphan<LocatedInteger>> ordinal; Parser<Orphan<LocatedInteger>> ordinal;
Parser<Orphan<Declaration::Method::Param>> param;
DeclParser usingDecl; DeclParser usingDecl;
DeclParser constDecl; DeclParser constDecl;
...@@ -114,6 +115,7 @@ public: ...@@ -114,6 +115,7 @@ public:
DeclParser structDecl; DeclParser structDecl;
DeclParser fieldDecl; DeclParser fieldDecl;
DeclParser unionDecl; DeclParser unionDecl;
DeclParser groupDecl;
DeclParser interfaceDecl; DeclParser interfaceDecl;
DeclParser methodDecl; DeclParser methodDecl;
DeclParser paramDecl; DeclParser paramDecl;
......
...@@ -104,21 +104,13 @@ BuilderFor<DynamicTypeFor<FromBuilder<T>>> toDynamic(T&& value); ...@@ -104,21 +104,13 @@ BuilderFor<DynamicTypeFor<FromBuilder<T>>> toDynamic(T&& value);
template <typename T> template <typename T>
DynamicTypeFor<TypeIfEnum<T>> toDynamic(T&& value); DynamicTypeFor<TypeIfEnum<T>> toDynamic(T&& value);
template <typename T> struct EnableIfNotDynamic_ { typedef T Type; };
template <> struct EnableIfNotDynamic_<DynamicUnion> {};
template <> struct EnableIfNotDynamic_<DynamicStruct> {};
template <> struct EnableIfNotDynamic_<DynamicList> {};
template <> struct EnableIfNotDynamic_<DynamicValue> {};
template <typename T>
using EnableIfNotDynamic = typename EnableIfNotDynamic_<T>::Type;
// ------------------------------------------------------------------- // -------------------------------------------------------------------
class DynamicEnum { class DynamicEnum {
public: public:
DynamicEnum() = default; DynamicEnum() = default;
template <typename T, typename = TypeIfEnum<T>> template <typename T, typename = kj::EnableIf<kind<T>() == Kind::ENUM>>
inline DynamicEnum(T&& value): DynamicEnum(toDynamic(value)) {} inline DynamicEnum(T&& value): DynamicEnum(toDynamic(value)) {}
template <typename T> template <typename T>
...@@ -271,7 +263,7 @@ public: ...@@ -271,7 +263,7 @@ public:
Reader() = default; Reader() = default;
template <typename T, typename = EnableIfNotDynamic<FromReader<T>>> template <typename T, typename = kj::EnableIf<kind<FromReader<T>>() == Kind::STRUCT>>
inline Reader(T&& value): Reader(toDynamic(value)) {} inline Reader(T&& value): Reader(toDynamic(value)) {}
template <typename T> template <typename T>
...@@ -322,7 +314,7 @@ public: ...@@ -322,7 +314,7 @@ public:
Builder() = default; Builder() = default;
template <typename T, typename = EnableIfNotDynamic<FromBuilder<T>>> template <typename T, typename = kj::EnableIf<kind<FromBuilder<T>>() == Kind::STRUCT>>
inline Builder(T&& value): Builder(toDynamic(value)) {} inline Builder(T&& value): Builder(toDynamic(value)) {}
template <typename T> template <typename T>
...@@ -433,7 +425,7 @@ public: ...@@ -433,7 +425,7 @@ public:
Reader() = default; Reader() = default;
template <typename T, typename = EnableIfNotDynamic<FromReader<T>>> template <typename T, typename = kj::EnableIf<kind<FromReader<T>>() == Kind::LIST>>
inline Reader(T&& value): Reader(toDynamic(value)) {} inline Reader(T&& value): Reader(toDynamic(value)) {}
template <typename T> template <typename T>
...@@ -472,7 +464,7 @@ public: ...@@ -472,7 +464,7 @@ public:
Builder() = default; Builder() = default;
template <typename T, typename = EnableIfNotDynamic<FromBuilder<T>>> template <typename T, typename = kj::EnableIf<kind<FromBuilder<T>>() == Kind::LIST>>
inline Builder(T&& value): Builder(toDynamic(value)) {} inline Builder(T&& value): Builder(toDynamic(value)) {}
template <typename T> template <typename T>
......
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CAPNP_PRETTY_PRINT_H_
#define CAPNP_PRETTY_PRINT_H_
#include "dynamic.h"
#include <kj/string.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);
// 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.
//
// If you don't want indentation, just use the value's KJ stringifier (e.g. pass it to kj::str(),
// any of the KJ debug macros, etc.).
} // namespace capnp
#endif // PRETTY_PRINT_H_
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
#include "message.h" #include "message.h"
#include "dynamic.h" #include "dynamic.h"
#include "pretty-print.h"
#include <kj/debug.h> #include <kj/debug.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "test-util.h" #include "test-util.h"
...@@ -60,7 +61,7 @@ namespace capnp { ...@@ -60,7 +61,7 @@ namespace capnp {
namespace _ { // private namespace _ { // private
namespace { namespace {
TEST(Stringify, DebugString) { TEST(Stringify, KjStringification) {
MallocMessageBuilder builder; MallocMessageBuilder builder;
auto root = builder.initRoot<TestAllTypes>(); auto root = builder.initRoot<TestAllTypes>();
...@@ -142,6 +143,205 @@ TEST(Stringify, DebugString) { ...@@ -142,6 +143,205 @@ TEST(Stringify, DebugString) {
kj::str(root)); kj::str(root));
} }
TEST(Stringify, PrettyPrint) {
MallocMessageBuilder builder;
auto root = builder.initRoot<TestAllTypes>();
EXPECT_EQ("()", prettyPrint(root));
initTestMessage(root);
EXPECT_EQ(
"( boolField = true,\n"
" int8Field = -123,\n"
" int16Field = -12345,\n"
" int32Field = -12345678,\n"
" int64Field = -123456789012345,\n"
" uInt8Field = 234,\n"
" uInt16Field = 45678,\n"
" uInt32Field = 3456789012,\n"
" uInt64Field = 12345678901234567890,\n"
" float32Field = 1234.5,\n"
" float64Field = -1.23e47,\n"
" textField = \"foo\",\n"
" dataField = \"bar\",\n"
" structField = (\n"
" boolField = true,\n"
" int8Field = -12,\n"
" int16Field = 3456,\n"
" int32Field = -78901234,\n"
" int64Field = 56789012345678,\n"
" uInt8Field = 90,\n"
" uInt16Field = 1234,\n"
" uInt32Field = 56789012,\n"
" uInt64Field = 345678901234567890,\n"
" float32Field = -1.25e-10,\n"
" float64Field = 345,\n"
" textField = \"baz\",\n"
" dataField = \"qux\",\n"
" structField = (\n"
" textField = \"nested\",\n"
" structField = (textField = \"really nested\")),\n"
" enumField = baz,\n"
" voidList = [void, void, void],\n"
" boolList = [false, true, false, true, true],\n"
" int8List = [12, -34, -128, 127],\n"
" int16List = [1234, -5678, -32768, 32767],\n"
" int32List = [12345678, -90123456, -2147483648, 2147483647],\n"
" int64List = [123456789012345, -678901234567890, "
"-9223372036854775808, 9223372036854775807],\n"
" uInt8List = [12, 34, 0, 255],\n"
" uInt16List = [1234, 5678, 0, 65535],\n"
" uInt32List = [12345678, 90123456, 0, 4294967295],\n"
" uInt64List = [123456789012345, 678901234567890, 0, 18446744073709551615],\n"
" float32List = [0, 1234567, 1e37, -1e37, 1e-37, -1e-37],\n"
" float64List = [0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306],\n"
" 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"
" enumField = corge,\n"
" voidList = [void, void, void, void, void, void],\n"
" boolList = [true, false, false, true],\n"
" int8List = [111, -111],\n"
" int16List = [11111, -11111],\n"
" int32List = [111111111, -111111111],\n"
" int64List = [1111111111111111111, -1111111111111111111],\n"
" uInt8List = [111, 222],\n"
" uInt16List = [33333, 44444],\n"
" uInt32List = [3333333333],\n"
" uInt64List = [11111111111111111111],\n"
" float32List = [5555.5, inf, -inf, nan],\n"
" float64List = [7777.75, inf, -inf, nan],\n"
" 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));
}
TEST(Stringify, PrettyPrintAdvanced) {
MallocMessageBuilder builder;
{
auto root = builder.initRoot<TestAllTypes>();
auto list = root.initStructList(3);
list[0].setInt32Field(123);
list[0].setTextField("foo");
list[1].setInt32Field(456);
list[1].setTextField("bar");
list[2].setInt32Field(789);
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));
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));
}
{
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});
EXPECT_EQ(
"[ [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]]",
prettyPrint(ll));
EXPECT_EQ(
"(int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
root.initList8(0);
EXPECT_EQ(
"( list8 = [],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
auto l8 = root.initList8(1);
l8[0].setF(12);
EXPECT_EQ(
"( list8 = [(f = 12)],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
l8 = root.initList8(2);
l8[0].setF(12);
l8[1].setF(34);
EXPECT_EQ(
"( list8 = [\n"
" (f = 12),\n"
" (f = 34)],\n"
" int32ListList = [\n"
" [123, 456, 789],\n"
" [234, 567, 891],\n"
" [345, 678, 912]])",
prettyPrint(root));
}
{
auto root = builder.initRoot<test::TestStructUnion>();
auto s = root.getUn().initAllTypes();
EXPECT_EQ(
"(un = allTypes())",
prettyPrint(root));
s.setInt32Field(123);
EXPECT_EQ(
"(un = allTypes(int32Field = 123))",
prettyPrint(root));
s.setTextField("foo");
EXPECT_EQ(
"(un = allTypes(\n"
" int32Field = 123,\n"
" textField = \"foo\"))",
prettyPrint(root));
}
}
TEST(Stringify, Unions) { TEST(Stringify, Unions) {
MallocMessageBuilder builder; MallocMessageBuilder builder;
auto root = builder.initRoot<TestUnion>(); auto root = builder.initRoot<TestUnion>();
......
...@@ -33,8 +33,63 @@ namespace { ...@@ -33,8 +33,63 @@ namespace {
static const char HEXDIGITS[] = "0123456789abcdef"; static const char HEXDIGITS[] = "0123456789abcdef";
class Indent {
public:
explicit Indent(bool enable): amount(enable ? 1 : 0), hasPrefix(false), isFirst(true) {}
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.
};
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.
if (isFirst) {
isFirst = false;
if (type == INLINE || amount == 0) {
return Indent(amount, true);
}
if (hasPrefix) {
os << '\n';
for (uint i = 0; i < amount; i++) {
os << " ";
}
} else {
os << ' ';
}
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);
}
}
Indent withPrefix() {
return Indent(amount, true);
}
private:
Indent(uint amount, bool hasPrefix): amount(amount), hasPrefix(hasPrefix), isFirst(true) {}
uint amount;
bool hasPrefix;
bool isFirst;
};
static void print(std::ostream& os, const DynamicValue::Reader& value, static void print(std::ostream& os, const DynamicValue::Reader& value,
schema::Type::Body::Which which, bool alreadyParenthesized = false) { schema::Type::Body::Which which, Indent indent,
bool alreadyParenthesized = false) {
// Print an arbitrary message via the dynamic API by // Print an arbitrary message via the dynamic API by
// iterating over the schema. Look at the handling // iterating over the schema. Look at the handling
// of STRUCT in particular. // of STRUCT in particular.
...@@ -103,15 +158,24 @@ static void print(std::ostream& os, const DynamicValue::Reader& value, ...@@ -103,15 +158,24 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
} }
case DynamicValue::LIST: { case DynamicValue::LIST: {
os << "["; os << "[";
bool first = true;
auto listValue = value.as<DynamicList>(); 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) { for (auto element: listValue) {
if (first) { print(os, element, listValue.getSchema().whichElementType(),
first = false; indent.startItem(os, itemType));
} else {
os << ", ";
}
print(os, element, listValue.getSchema().whichElementType());
} }
os << "]"; os << "]";
break; break;
...@@ -129,28 +193,41 @@ static void print(std::ostream& os, const DynamicValue::Reader& value, ...@@ -129,28 +193,41 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
case DynamicValue::STRUCT: { case DynamicValue::STRUCT: {
if (!alreadyParenthesized) os << "("; if (!alreadyParenthesized) os << "(";
auto structValue = value.as<DynamicStruct>(); auto structValue = value.as<DynamicStruct>();
bool first = true; Indent::ItemType itemType = Indent::INLINE;
// If there is more than one member, arrange for indentation.
bool sawOne = false;
for (auto member: structValue.getSchema().getMembers()) { for (auto member: structValue.getSchema().getMembers()) {
if (structValue.has(member)) { if (structValue.has(member)) {
if (first) { if (sawOne) {
first = false; itemType = Indent::PREFIXED;
break;
} else { } else {
os << ", "; sawOne = true;
} }
}
}
// Print the members.
for (auto member: structValue.getSchema().getMembers()) {
if (structValue.has(member)) {
Indent subIndent = indent.startItem(os, itemType);
os << member.getProto().getName().cStr() << " = "; os << member.getProto().getName().cStr() << " = ";
auto memberBody = member.getProto().getBody(); auto memberBody = member.getProto().getBody();
switch (memberBody.which()) { switch (memberBody.which()) {
case schema::StructNode::Member::Body::UNION_MEMBER: case schema::StructNode::Member::Body::UNION_MEMBER:
print(os, structValue.get(member), schema::Type::Body::VOID_TYPE); print(os, structValue.get(member), schema::Type::Body::VOID_TYPE, subIndent);
break; break;
case schema::StructNode::Member::Body::FIELD_MEMBER: case schema::StructNode::Member::Body::FIELD_MEMBER:
print(os, structValue.get(member), print(os, structValue.get(member),
memberBody.getFieldMember().getType().getBody().which()); memberBody.getFieldMember().getType().getBody().which(), subIndent);
break; break;
} }
} }
} }
if (!alreadyParenthesized) os << ")"; if (!alreadyParenthesized) os << ")";
break; break;
} }
...@@ -160,7 +237,7 @@ static void print(std::ostream& os, const DynamicValue::Reader& value, ...@@ -160,7 +237,7 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
os << tag->getProto().getName().cStr() << "("; os << tag->getProto().getName().cStr() << "(";
print(os, unionValue.get(), print(os, unionValue.get(),
tag->getProto().getBody().getFieldMember().getType().getBody().which(), tag->getProto().getBody().getFieldMember().getType().getBody().which(),
true /* alreadyParenthesized */); indent.withPrefix(), true /* alreadyParenthesized */);
os << ")"; os << ")";
} else { } else {
// Unknown union member; must have come from newer // Unknown union member; must have come from newer
...@@ -182,13 +259,30 @@ static void print(std::ostream& os, const DynamicValue::Reader& value, ...@@ -182,13 +259,30 @@ static void print(std::ostream& os, const DynamicValue::Reader& value,
kj::String stringify(DynamicValue::Reader value) { kj::String stringify(DynamicValue::Reader value) {
std::stringstream out; std::stringstream out;
print(out, value, schema::Type::Body::STRUCT_TYPE); print(out, value, schema::Type::Body::STRUCT_TYPE, Indent(false));
auto content = out.str(); auto content = out.str();
return kj::heapString(content.data(), content.size()); return kj::heapString(content.data(), content.size());
} }
} // namespace } // 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::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::String prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
kj::String 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::Reader& value) { return stringify(value); }
kj::String KJ_STRINGIFY(const DynamicValue::Builder& value) { return stringify(value.asReader()); } 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(DynamicEnum value) { return stringify(value); }
......
...@@ -36,6 +36,19 @@ TEST(String, Str) { ...@@ -36,6 +36,19 @@ TEST(String, Str) {
EXPECT_EQ("foo", str('f', 'o', 'o')); EXPECT_EQ("foo", str('f', 'o', 'o'));
} }
TEST(String, StartsEndsWith) {
EXPECT_TRUE(StringPtr("foobar").startsWith("foo"));
EXPECT_FALSE(StringPtr("foobar").startsWith("bar"));
EXPECT_FALSE(StringPtr("foobar").endsWith("foo"));
EXPECT_TRUE(StringPtr("foobar").endsWith("bar"));
EXPECT_FALSE(StringPtr("fo").startsWith("foo"));
EXPECT_FALSE(StringPtr("fo").endsWith("foo"));
EXPECT_TRUE(StringPtr("foobar").startsWith(""));
EXPECT_TRUE(StringPtr("foobar").endsWith(""));
}
} // namespace } // namespace
} // namespace _ (private) } // namespace _ (private)
} // namespace kj } // namespace kj
...@@ -81,6 +81,9 @@ public: ...@@ -81,6 +81,9 @@ public:
// A string slice is only NUL-terminated if it is a suffix, so slice() has a one-parameter // A string slice is only NUL-terminated if it is a suffix, so slice() has a one-parameter
// version that assumes end = size(). // version that assumes end = size().
inline bool startsWith(const StringPtr& other) const;
inline bool endsWith(const StringPtr& other) const;
private: private:
inline StringPtr(ArrayPtr<const char> content): content(content) {} inline StringPtr(ArrayPtr<const char> content): content(content) {}
...@@ -132,6 +135,9 @@ public: ...@@ -132,6 +135,9 @@ public:
inline bool operator==(const StringPtr& other) const { return StringPtr(*this) == other; } inline bool operator==(const StringPtr& other) const { return StringPtr(*this) == other; }
inline bool operator!=(const StringPtr& other) const { return !(*this == other); } inline bool operator!=(const StringPtr& other) const { return !(*this == other); }
inline bool startsWith(const StringPtr& other) const { return StringPtr(*this).startsWith(other);}
inline bool endsWith(const StringPtr& other) const { return StringPtr(*this).endsWith(other); }
private: private:
Array<char> content; Array<char> content;
}; };
...@@ -353,6 +359,15 @@ inline ArrayPtr<const char> StringPtr::slice(size_t start, size_t end) const { ...@@ -353,6 +359,15 @@ inline ArrayPtr<const char> StringPtr::slice(size_t start, size_t end) const {
return content.slice(start, end); return content.slice(start, end);
} }
inline bool StringPtr::startsWith(const StringPtr& other) const {
return other.content.size() <= content.size() &&
memcmp(content.begin(), other.content.begin(), other.size()) == 0;
}
inline bool StringPtr::endsWith(const StringPtr& other) const {
return other.content.size() <= content.size() &&
memcmp(end() - other.size(), other.content.begin(), other.size()) == 0;
}
inline String::operator ArrayPtr<char>() { inline String::operator ArrayPtr<char>() {
return content == nullptr ? ArrayPtr<char>(nullptr) : content.slice(0, content.size() - 1); return content == nullptr ? ArrayPtr<char>(nullptr) : content.slice(0, content.size() - 1);
} }
......
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