Commit c4c5e404 authored by Kenton Varda's avatar Kenton Varda

Add new `capnp convert` command with JSON support.

This supersedes the `capnp encode` and `capnp decode` commands. It didn't make sense to add JSON to those commands since it was unclear if JSON should be thought of as the "encoded" or "decoded" format. `convert` allows mapping anything to anything.

This command is also useful for, say, converting unpacked format to packed format or vice versa, which can now be done without a schema.
parent 9b5fc00a
......@@ -324,7 +324,7 @@ libcapnpc_la_SOURCES= \
bin_PROGRAMS = capnp capnpc-capnp capnpc-c++
capnp_LDADD = libcapnpc.la libcapnp.la libkj.la $(PTHREAD_LIBS)
capnp_LDADD = libcapnpc.la libcapnp-json.la libcapnp.la libkj.la $(PTHREAD_LIBS)
capnp_SOURCES = \
src/capnp/compiler/module-loader.h \
src/capnp/compiler/module-loader.c++ \
......
......@@ -156,7 +156,7 @@ if(NOT CAPNP_LITE)
compiler/module-loader.c++
compiler/capnp.c++
)
target_link_libraries(capnp_tool capnpc capnp kj)
target_link_libraries(capnp_tool capnpc capnp-json capnp kj)
set_target_properties(capnp_tool PROPERTIES OUTPUT_NAME capnp)
set_target_properties(capnp_tool PROPERTIES CAPNP_INCLUDE_DIRECTORY
$<JOIN:$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>,$<INSTALL_INTERFACE:${CMAKE_INSTALL_BINDIR}/..>>
......
......@@ -461,6 +461,8 @@ public:
inline Reader(T&& value)
: _reader(_::PointerHelpers<FromReader<T>>::getInternalReader(kj::fwd<T>(value))) {}
inline MessageSize totalSize() const { return _reader.totalSize().asPublic(); }
kj::ArrayPtr<const byte> getDataSection() {
return _reader.getDataSectionAsBlob();
}
......@@ -483,6 +485,11 @@ public:
// T must be a struct type.
return typename T::Reader(_reader);
}
template <typename T>
ReaderFor<T> as(StructSchema schema) const;
// T must be DynamicStruct. Defined in dynamic.h.
private:
_::StructReader _reader;
......@@ -529,6 +536,11 @@ public:
// T must be a struct type.
return typename T::Builder(_builder);
}
template <typename T>
BuilderFor<T> as(StructSchema schema);
// T must be DynamicStruct. Defined in dynamic.h.
private:
_::StructBuilder _builder;
friend class Orphanage;
......
......@@ -224,10 +224,14 @@ private:
// =======================================================================================
// inline implementation details
template <bool isDynamic>
struct EncodeImpl;
template <typename T>
kj::String JsonCodec::encode(T&& value) {
Type type = Type::from(value);
typedef FromAny<kj::Decay<T>> Base;
return encode(DynamicValue::Reader(ReaderFor<Base>(kj::fwd<T>(value))), Type::from<Base>());
return encode(DynamicValue::Reader(ReaderFor<Base>(kj::fwd<T>(value))), type);
}
template <typename T>
......
......@@ -40,6 +40,36 @@ fi
SCHEMA=`dirname "$0"`/../test.capnp
TESTDATA=`dirname "$0"`/../testdata
# ========================================================================================
# convert
$CAPNP convert text:binary $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/binary - || fail encode
$CAPNP convert text:flat $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/flat - || fail encode flat
$CAPNP convert text:packed $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/packed - || fail encode packed
$CAPNP convert text:flat-packed $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/packedflat - || fail encode packedflat
$CAPNP convert text:binary $SCHEMA TestAllTypes < $TESTDATA/pretty.txt | cmp $TESTDATA/binary - || fail parse pretty
$CAPNP convert binary:text $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/pretty.txt - || fail decode
$CAPNP convert flat:text $SCHEMA TestAllTypes < $TESTDATA/flat | cmp $TESTDATA/pretty.txt - || fail decode flat
$CAPNP convert packed:text $SCHEMA TestAllTypes < $TESTDATA/packed | cmp $TESTDATA/pretty.txt - || fail decode packed
$CAPNP convert flat-packed:text $SCHEMA TestAllTypes < $TESTDATA/packedflat | cmp $TESTDATA/pretty.txt - || fail decode packedflat
$CAPNP convert binary:text --short $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/short.txt - || fail decode short
$CAPNP convert binary:text $SCHEMA TestAllTypes < $TESTDATA/segmented | cmp $TESTDATA/pretty.txt - || fail decode segmented
$CAPNP convert packed:text $SCHEMA TestAllTypes < $TESTDATA/segmented-packed | cmp $TESTDATA/pretty.txt - || fail decode segmented-packed
$CAPNP convert binary:packed < $TESTDATA/binary | cmp $TESTDATA/packed - || fail binary to packed
$CAPNP convert packed:binary < $TESTDATA/packed | cmp $TESTDATA/binary - || fail packed to binary
$CAPNP convert binary:json $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/pretty.json - || fail binary to json
$CAPNP convert binary:json --short $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/short.json - || fail binary to short json
$CAPNP convert json:binary $SCHEMA TestAllTypes < $TESTDATA/pretty.json | cmp $TESTDATA/binary - || fail json to binary
$CAPNP convert json:binary $SCHEMA TestAllTypes < $TESTDATA/short.json | cmp $TESTDATA/binary - || fail short json to binary
# ========================================================================================
# DEPRECATED encode/decode
$CAPNP encode $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/binary - || fail encode
$CAPNP encode --flat $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/flat - || fail encode flat
$CAPNP encode --packed $SCHEMA TestAllTypes < $TESTDATA/short.txt | cmp $TESTDATA/packed - || fail encode packed
......@@ -55,6 +85,9 @@ $CAPNP decode --short $SCHEMA TestAllTypes < $TESTDATA/binary | cmp $TESTDATA/sh
$CAPNP decode $SCHEMA TestAllTypes < $TESTDATA/segmented | cmp $TESTDATA/pretty.txt - || fail decode segmented
$CAPNP decode --packed $SCHEMA TestAllTypes < $TESTDATA/segmented-packed | cmp $TESTDATA/pretty.txt - || fail decode segmented-packed
# ========================================================================================
# eval
test_eval() {
test "x`$CAPNP eval $SCHEMA $1 | tr -d '\r'`" = "x$2" || fail eval "$1 == $2"
}
......@@ -67,5 +100,7 @@ test_eval globalPrintableStruct '(someText = "foo")'
test_eval TestConstants.enumConst corge
test_eval 'TestListDefaults.lists.int32ListList[2][0]' 12341234
test "x`$CAPNP eval $SCHEMA -ojson globalPrintableStruct | tr -d '\r'`" = "x{\"someText\": \"foo\"}" || fail eval json "globalPrintableStruct == {someText = \"foo\"}"
$CAPNP compile -ofoo $TESTDATA/errors.capnp.nobuild 2>&1 | sed -e "s,^.*/errors[.]capnp[.]nobuild,file,g" | tr -d '\r' |
cmp $TESTDATA/errors.txt - || fail error output
......@@ -38,6 +38,8 @@
#include <sys/types.h>
#include <capnp/serialize.h>
#include <capnp/serialize-packed.h>
#include <capnp/serialize-text.h>
#include <capnp/compat/json.h>
#include <errno.h>
#include <stdlib.h>
......@@ -82,10 +84,12 @@ public:
"Generate source code from schema files.")
.addSubCommand("id", KJ_BIND_METHOD(*this, getGenIdMain),
"Generate a new unique ID.")
.addSubCommand("convert", KJ_BIND_METHOD(*this, getConvertMain),
"Convert messages between binary, text, JSON, etc.")
.addSubCommand("decode", KJ_BIND_METHOD(*this, getDecodeMain),
"Decode binary Cap'n Proto message to text.")
"DEPRECATED (use `convert`)")
.addSubCommand("encode", KJ_BIND_METHOD(*this, getEncodeMain),
"Encode text Cap'n Proto message to binary.")
"DEPRECATED (use `convert`)")
.addSubCommand("eval", KJ_BIND_METHOD(*this, getEvalMain),
"Evaluate a const from a schema file.");
addGlobalOptions(builder);
......@@ -109,6 +113,46 @@ public:
.build();
}
kj::MainFunc getConvertMain() {
// Only parse the schemas we actually need for decoding.
compileEagerness = Compiler::NODE;
// Drop annotations since we don't need them. This avoids importing files like c++.capnp.
annotationFlag = Compiler::DROP_ANNOTATIONS;
kj::MainBuilder builder(context, VERSION_STRING,
"Convers messages between formats. Reads a stream of messages from stdin in format "
"<from> and writes them to stdout in format <to>. Valid formats are:\n"
" binary standard binary format\n"
" packed packed binary format (deflates zeroes)\n"
" flat binary single segment, no segment table (rare)\n"
" flat-packed flat and packed\n"
" text schema language struct literal format\n"
" json JSON format\n"
"When using \"text\" or \"json\" format, you must specify <schema-file> and <type> "
"(but they are ignored and can be omitted for binary-to-binary conversions). "
"<type> names names a struct type defined in <schema-file>, which is the root type "
"of the message(s).");
addGlobalOptions(builder);
builder.addOption({"short"}, KJ_BIND_METHOD(*this, printShort),
"Write text or JSON output in short (non-pretty) format. Each message will "
"be printed on one line, without using whitespace to improve readability.")
.addOptionWithArg({"segment-size"}, KJ_BIND_METHOD(*this, setSegmentSize), "<n>",
"For binary output, sets the preferred segment size on the MallocMessageBuilder to <n> "
"words and turns off heuristic growth. This flag is mainly useful "
"for testing. Without it, each message will be written as a single "
"segment.")
.addOption({"quiet"}, KJ_BIND_METHOD(*this, setQuiet),
"Do not print warning messages about the input being in the wrong format. "
"Use this if you find the warnings are wrong (but also let us know so "
"we can improve them).")
.expectArg("<from>:<to>", KJ_BIND_METHOD(*this, setConversion))
.expectOptionalArg("<schema-file>", KJ_BIND_METHOD(*this, addSource))
.expectOptionalArg("<type>", KJ_BIND_METHOD(*this, setRootType))
.callAfterParsing(KJ_BIND_METHOD(*this, convert));
return builder.build();
}
kj::MainFunc getDecodeMain() {
// Only parse the schemas we actually need for decoding.
compileEagerness = Compiler::NODE;
......@@ -189,6 +233,9 @@ public:
// Drop annotations since we don't need them. This avoids importing files like c++.capnp.
annotationFlag = Compiler::DROP_ANNOTATIONS;
// Default convert to text unless -o is given.
convertTo = Format::TEXT;
kj::MainBuilder builder(context, VERSION_STRING,
"Prints (or encodes) the value of <name>, which must be defined in <schema-file>. "
"<name> must refer to a const declaration, a field of a struct type (prints the default "
......@@ -206,20 +253,19 @@ public:
"and --flat flags specify binary output, in which case the const must be of struct "
"type.");
addGlobalOptions(builder);
builder.addOption({'b', "binary"}, KJ_BIND_METHOD(*this, codeBinary),
"Write the output as binary instead of text, using standard Cap'n Proto "
"serialization. (This writes the message using capnp::writeMessage() "
"from <capnp/serialize.h>.)")
builder.addOptionWithArg({'o', "output"}, KJ_BIND_METHOD(*this, setEvalOutputFormat),
"<format>", "Encode the output in the given format. See `capnp help convert` "
"for a list of formats. Defaults to \"text\".")
.addOption({'b', "binary"}, KJ_BIND_METHOD(*this, codeBinary),
"same as -obinary")
.addOption({"flat"}, KJ_BIND_METHOD(*this, codeFlat),
"Write the output as a flat single-segment binary message, with no framing.")
"same as -oflat")
.addOption({'p', "packed"}, KJ_BIND_METHOD(*this, codePacked),
"Write the output as packed binary instead of text, using standard Cap'n "
"Proto packing, which deflates zero-valued bytes. (This writes the "
"message using capnp::writePackedMessage() from "
"<capnp/serialize-packed.h>.)")
"same as -opacked")
.addOption({"short"}, KJ_BIND_METHOD(*this, printShort),
"Print in short (non-pretty) text format. The message will be printed on "
"one line, without using whitespace to improve readability.")
"If output format is text or JSON, write in short (non-pretty) format. The "
"message will be printed on one line, without using whitespace to improve "
"readability.")
.expectArg("<schema-file>", KJ_BIND_METHOD(*this, addSource))
.expectArg("<name>", KJ_BIND_METHOD(*this, evalConst));
return builder.build();
......@@ -586,6 +632,469 @@ public:
return true;
}
// =====================================================================================
// "convert" command
private:
enum class Format {
BINARY,
PACKED,
FLAT,
FLAT_PACKED,
TEXT,
JSON
};
kj::Maybe<Format> parseFormatName(kj::StringPtr name) {
if (name == "binary" ) return Format::BINARY;
if (name == "packed" ) return Format::PACKED;
if (name == "flat" ) return Format::FLAT;
if (name == "flat-packed") return Format::FLAT_PACKED;
if (name == "text" ) return Format::TEXT;
if (name == "json" ) return Format::JSON;
return nullptr;
}
kj::StringPtr toString(Format format) {
switch (format) {
case Format::BINARY : return "binary";
case Format::PACKED : return "packed";
case Format::FLAT : return "flat";
case Format::FLAT_PACKED: return "flat-packed";
case Format::TEXT : return "text";
case Format::JSON : return "json";
}
KJ_UNREACHABLE;
}
Format formatFromDeprecatedFlags(Format defaultFormat) {
// For deprecated commands "decode" and "encode".
if (flat) {
if (packed) {
return Format::FLAT_PACKED;
} else {
return Format::FLAT;
}
} if (packed) {
return Format::PACKED;
} else if (binary) {
return Format::BINARY;
} else {
return defaultFormat;
}
}
kj::MainBuilder::Validity verifyRequirements(Format format) {
if ((format == Format::TEXT || format == Format::JSON) && rootType == StructSchema()) {
return kj::str("format requires schema: ", toString(format));
} else {
return true;
}
}
public:
kj::MainBuilder::Validity setConversion(kj::StringPtr conversion) {
KJ_IF_MAYBE(colon, conversion.findFirst(':')) {
auto from = kj::str(conversion.slice(0, *colon));
auto to = conversion.slice(*colon + 1);
KJ_IF_MAYBE(f, parseFormatName(from)) {
convertFrom = *f;
} else {
return kj::str("unknown format: ", from);
}
KJ_IF_MAYBE(t, parseFormatName(to)) {
convertTo = *t;
} else {
return kj::str("unknown format: ", to);
}
return true;
} else {
return "invalid conversion, format is: <from>:<to>";
}
}
kj::MainBuilder::Validity convert() {
{
auto result = verifyRequirements(convertFrom);
if (result.getError() != nullptr) return result;
}
{
auto result = verifyRequirements(convertTo);
if (result.getError() != nullptr) return result;
}
kj::FdInputStream rawInput(STDIN_FILENO);
kj::BufferedInputStreamWrapper input(rawInput);
kj::FdOutputStream output(STDOUT_FILENO);
if (!quiet) {
auto result = checkPlausibility(convertFrom, input.getReadBuffer());
if (result.getError() != nullptr) {
return kj::mv(result);
}
}
while (input.tryGetReadBuffer().size() > 0) {
readOneAndConvert(input, output);
}
context.exit();
KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
}
private:
kj::Vector<byte> readAll(kj::BufferedInputStreamWrapper& input) {
kj::Vector<byte> allBytes;
for (;;) {
auto buffer = input.tryGetReadBuffer();
if (buffer.size() == 0) break;
allBytes.addAll(buffer);
input.skip(buffer.size());
}
return allBytes;
}
kj::String readOneText(kj::BufferedInputStreamWrapper& input) {
// Consume and return one parentheses-delimited message from the input.
//
// Accounts for nested parentheses, comments, and string literals.
enum {
NORMAL,
COMMENT,
QUOTE,
QUOTE_ESCAPE,
DQUOTE,
DQUOTE_ESCAPE
} state = NORMAL;
uint depth = 0;
bool sawClose = false;
kj::Vector<char> chars;
for (;;) {
auto buffer = input.tryGetReadBuffer();
if (buffer == nullptr) {
// EOF
chars.add('\0');
return kj::String(chars.releaseAsArray());
}
for (auto i: kj::indices(buffer)) {
char c = buffer[i];
switch (state) {
case NORMAL:
switch (c) {
case '#': state = COMMENT; break;
case '(':
if (depth == 0 && sawClose) {
// We already got one complete message. This is the start of the next message.
// Stop here.
chars.addAll(buffer.slice(0, i));
chars.add('\0');
input.skip(i);
return kj::String(chars.releaseAsArray());
}
++depth;
break;
case ')':
if (depth > 0) {
if (--depth == 0) {
sawClose = true;
}
}
break;
default: break;
}
break;
case COMMENT:
switch (c) {
case '\n': state = NORMAL; break;
default: break;
}
break;
case QUOTE:
switch (c) {
case '\'': state = NORMAL; break;
case '\\': state = QUOTE_ESCAPE; break;
default: break;
}
break;
case QUOTE_ESCAPE:
break;
case DQUOTE:
switch (c) {
case '\"': state = NORMAL; break;
case '\\': state = DQUOTE_ESCAPE; break;
default: break;
}
break;
case DQUOTE_ESCAPE:
break;
}
}
chars.addAll(buffer);
input.skip(buffer.size());
}
}
kj::String readOneJson(kj::BufferedInputStreamWrapper& input) {
// Consume and return one brace-delimited message from the input.
//
// Accounts for nested braces, string literals, and comments starting with # or //. Technically
// JSON does not permit comments but this code is lenient in case we change things later.
enum {
NORMAL,
SLASH,
COMMENT,
QUOTE,
QUOTE_ESCAPE,
DQUOTE,
DQUOTE_ESCAPE
} state = NORMAL;
uint depth = 0;
bool sawClose = false;
kj::Vector<char> chars;
for (;;) {
auto buffer = input.tryGetReadBuffer();
if (buffer == nullptr) {
// EOF
chars.add('\0');
return kj::String(chars.releaseAsArray());
}
for (auto i: kj::indices(buffer)) {
char c = buffer[i];
switch (state) {
case SLASH:
if (c == '/') {
state = COMMENT;
break;
}
// fallthrough
case NORMAL:
switch (c) {
case '#': state = COMMENT; break;
case '/': state = SLASH; break;
case '{':
if (depth == 0 && sawClose) {
// We already got one complete message. This is the start of the next message.
// Stop here.
chars.addAll(buffer.slice(0, i));
chars.add('\0');
input.skip(i);
return kj::String(chars.releaseAsArray());
}
++depth;
break;
case '}':
if (depth > 0) {
if (--depth == 0) {
sawClose = true;
}
}
break;
default: break;
}
break;
case COMMENT:
switch (c) {
case '\n': state = NORMAL; break;
default: break;
}
break;
case QUOTE:
switch (c) {
case '\'': state = NORMAL; break;
case '\\': state = QUOTE_ESCAPE; break;
default: break;
}
break;
case QUOTE_ESCAPE:
break;
case DQUOTE:
switch (c) {
case '\"': state = NORMAL; break;
case '\\': state = DQUOTE_ESCAPE; break;
default: break;
}
break;
case DQUOTE_ESCAPE:
break;
}
}
chars.addAll(buffer);
input.skip(buffer.size());
}
}
class ParseErrorCatcher: public kj::ExceptionCallback {
public:
ParseErrorCatcher(kj::ProcessContext& context): context(context) {}
~ParseErrorCatcher() noexcept(false) {
if (!unwindDetector.isUnwinding()) {
KJ_IF_MAYBE(e, exception) {
context.error(kj::str(
"*** ERROR CONVERTING PREVIOUS MESSAGE ***\n"
"The following error occurred while converting the message above.\n"
"This probably means the input data is invalid/corrupted.\n",
"Exception description: ", e->getDescription(), "\n"
"Code location: ", e->getFile(), ":", e->getLine(), "\n"
"*** END ERROR ***"));
}
}
}
void onRecoverableException(kj::Exception&& e) {
// Only capture the first exception, on the assumption that later exceptions are probably
// just cascading problems.
if (exception == nullptr) {
exception = kj::mv(e);
}
}
private:
kj::ProcessContext& context;
kj::Maybe<kj::Exception> exception;
kj::UnwindDetector unwindDetector;
};
void readOneAndConvert(kj::BufferedInputStreamWrapper& input, kj::OutputStream& output) {
// Since this is a debug tool, lift the usual security limits. Worse case is the process
// crashes or has to be killed.
ReaderOptions options;
options.nestingLimit = kj::maxValue;
options.traversalLimitInWords = kj::maxValue;
ParseErrorCatcher parseErrorCatcher(context);
switch (convertFrom) {
case Format::BINARY: {
capnp::InputStreamMessageReader message(input, options);
return writeConversion(message.getRoot<AnyStruct>(), output);
}
case Format::PACKED: {
capnp::PackedMessageReader message(input, options);
return writeConversion(message.getRoot<AnyStruct>(), output);
}
case Format::FLAT: {
auto allBytes = readAll(input);
// Technically we don't know if the bytes are aligned so we'd better copy them to a new
// array. Note that if we have a non-whole number of words we chop off the straggler
// bytes. This is fine because if those bytes are actually part of the message we will
// hit an error later and if they are not then who cares?
auto words = kj::heapArray<word>(allBytes.size() / sizeof(word));
memcpy(words.begin(), allBytes.begin(), words.size() * sizeof(word));
kj::ArrayPtr<const word> segments[1] = { words };
SegmentArrayMessageReader message(segments, options);
return writeConversion(message.getRoot<AnyStruct>(), output);
}
case Format::FLAT_PACKED: {
auto allBytes = readAll(input);
auto words = kj::heapArray<word>(computeUnpackedSizeInWords(allBytes));
kj::ArrayInputStream input(allBytes);
capnp::_::PackedInputStream unpacker(input);
unpacker.read(words.asBytes().begin(), words.asBytes().size());
word dummy;
KJ_ASSERT(unpacker.tryRead(&dummy, sizeof(dummy), sizeof(dummy)) == 0);
kj::ArrayPtr<const word> segments[1] = { words };
SegmentArrayMessageReader message(segments, options);
return writeConversion(message.getRoot<AnyStruct>(), output);
}
case Format::TEXT: {
auto text = readOneText(input);
MallocMessageBuilder message;
TextCodec codec;
codec.setPrettyPrint(pretty);
auto root = message.initRoot<DynamicStruct>(rootType);
codec.decode(text, root);
return writeConversion(root.asReader(), output);
}
case Format::JSON: {
auto text = readOneJson(input);
MallocMessageBuilder message;
JsonCodec codec;
codec.setPrettyPrint(pretty);
auto root = message.initRoot<DynamicStruct>(rootType);
codec.decode(text, root);
return writeConversion(root.asReader(), output);
}
}
KJ_UNREACHABLE;
}
void writeConversion(AnyStruct::Reader reader, kj::OutputStream& output) {
switch (convertTo) {
case Format::BINARY: {
MallocMessageBuilder message(
segmentSize == 0 ? SUGGESTED_FIRST_SEGMENT_WORDS : segmentSize,
segmentSize == 0 ? SUGGESTED_ALLOCATION_STRATEGY : AllocationStrategy::FIXED_SIZE);
message.setRoot(reader);
capnp::writeMessage(output, message);
return;
}
case Format::PACKED: {
MallocMessageBuilder message(
segmentSize == 0 ? SUGGESTED_FIRST_SEGMENT_WORDS : segmentSize,
segmentSize == 0 ? SUGGESTED_ALLOCATION_STRATEGY : AllocationStrategy::FIXED_SIZE);
message.setRoot(reader);
capnp::writePackedMessage(output, message);
return;
}
case Format::FLAT: {
auto words = kj::heapArray<word>(reader.totalSize().wordCount + 1);
memset(words.begin(), 0, words.asBytes().size());
copyToUnchecked(reader, words);
output.write(words.begin(), words.asBytes().size());
return;
}
case Format::FLAT_PACKED: {
auto words = kj::heapArray<word>(reader.totalSize().wordCount + 1);
memset(words.begin(), 0, words.asBytes().size());
copyToUnchecked(reader, words);
kj::BufferedOutputStreamWrapper buffered(output);
capnp::_::PackedOutputStream packed(buffered);
packed.write(words.begin(), words.asBytes().size());
return;
}
case Format::TEXT: {
TextCodec codec;
codec.setPrettyPrint(pretty);
auto text = codec.encode(reader.as<DynamicStruct>(rootType));
output.write({text.asBytes(), kj::StringPtr("\n").asBytes()});
return;
}
case Format::JSON: {
JsonCodec codec;
codec.setPrettyPrint(pretty);
auto text = codec.encode(reader.as<DynamicStruct>(rootType));
output.write({text.asBytes(), kj::StringPtr("\n").asBytes()});
return;
}
}
KJ_UNREACHABLE;
}
public:
// =====================================================================================
// "decode" command
......@@ -662,111 +1171,12 @@ private:
public:
kj::MainBuilder::Validity decode() {
kj::FdInputStream rawInput(STDIN_FILENO);
kj::BufferedInputStreamWrapper input(rawInput);
if (!quiet) {
auto result = checkPlausibility(input.getReadBuffer());
if (result.getError() != nullptr) {
return kj::mv(result);
}
}
if (flat) {
// Read in the whole input to decode as one segment.
kj::Array<word> words;
{
kj::Vector<byte> allBytes;
for (;;) {
auto buffer = input.tryGetReadBuffer();
if (buffer.size() == 0) break;
allBytes.addAll(buffer);
input.skip(buffer.size());
}
if (packed) {
words = kj::heapArray<word>(computeUnpackedSizeInWords(allBytes));
kj::ArrayInputStream input(allBytes);
capnp::_::PackedInputStream unpacker(input);
unpacker.read(words.asBytes().begin(), words.asBytes().size());
word dummy;
KJ_ASSERT(unpacker.tryRead(&dummy, sizeof(dummy), sizeof(dummy)) == 0);
} else {
// Technically we don't know if the bytes are aligned so we'd better copy them to a new
// array. Note that if we have a non-whole number of words we chop off the straggler
// bytes. This is fine because if those bytes are actually part of the message we will
// hit an error later and if they are not then who cares?
words = kj::heapArray<word>(allBytes.size() / sizeof(word));
memcpy(words.begin(), allBytes.begin(), words.size() * sizeof(word));
}
}
kj::ArrayPtr<const word> segments = words;
decodeInner<SegmentArrayMessageReader>(arrayPtr(&segments, 1));
} else {
while (input.tryGetReadBuffer().size() > 0) {
if (packed) {
decodeInner<PackedMessageReader>(input);
} else {
decodeInner<InputStreamMessageReader>(input);
}
}
}
context.exit();
KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
convertTo = Format::TEXT;
convertFrom = formatFromDeprecatedFlags(Format::BINARY);
return convert();
}
private:
struct ParseErrorCatcher: public kj::ExceptionCallback {
void onRecoverableException(kj::Exception&& e) {
// Only capture the first exception, on the assumption that later exceptions are probably
// just cascading problems.
if (exception == nullptr) {
exception = kj::mv(e);
}
}
kj::Maybe<kj::Exception> exception;
};
template <typename MessageReaderType, typename Input>
void decodeInner(Input&& input) {
// Since this is a debug tool, lift the usual security limits. Worse case is the process
// crashes or has to be killed.
ReaderOptions options;
options.nestingLimit = kj::maxValue;
options.traversalLimitInWords = kj::maxValue;
MessageReaderType reader(input, options);
kj::String text;
kj::Maybe<kj::Exception> exception;
{
ParseErrorCatcher catcher;
auto root = reader.template getRoot<DynamicStruct>(rootType);
if (pretty) {
text = kj::str(prettyPrint(root), '\n');
} else {
text = kj::str(root, '\n');
}
exception = kj::mv(catcher.exception);
}
kj::FdOutputStream(STDOUT_FILENO).write(text.begin(), text.size());
KJ_IF_MAYBE(e, exception) {
context.error(kj::str(
"*** ERROR DECODING PREVIOUS MESSAGE ***\n"
"The following error occurred while decoding the message above.\n"
"This probably means the input data is invalid/corrupted.\n",
"Exception description: ", e->getDescription(), "\n"
"Code location: ", e->getFile(), ":", e->getLine(), "\n"
"*** END ERROR ***"));
}
}
enum Plausibility {
IMPOSSIBLE,
IMPLAUSIBLE,
......@@ -935,359 +1345,225 @@ private:
});
}
kj::MainBuilder::Validity checkPlausibility(kj::ArrayPtr<const byte> prefix) {
if (flat && packed) {
switch (isPlausiblyPackedFlat(prefix)) {
case PLAUSIBLE:
break;
case IMPOSSIBLE:
if (plausibleOrWrongType(isPlausiblyPacked(prefix))) {
return "The input is not in --packed --flat format. It looks like it is in --packed "
"format. Try removing --flat.";
} else if (plausibleOrWrongType(isPlausiblyFlat(prefix))) {
return "The input is not in --packed --flat format. It looks like it is in --flat "
"format. Try removing --packed.";
} else if (plausibleOrWrongType(isPlausiblyBinary(prefix))) {
return "The input is not in --packed --flat format. It looks like it is in regular "
"binary format. Try removing the --packed and --flat flags.";
} else {
return "The input is not a Cap'n Proto message.";
Plausibility isPlausiblyText(kj::ArrayPtr<const byte> prefix) {
enum { PREAMBLE, COMMENT, BODY } state;
for (char c: prefix.asChars()) {
switch (state) {
case PREAMBLE:
// Before opening parenthesis.
switch (c) {
case '(': state = BODY; continue;
case '#': state = COMMENT; continue;
case ' ':
case '\n':
case '\r':
case '\t':
case '\v':
// whitespace
break;
default:
// Not whitespace, not comment, not open parenthesis. Impossible!
return IMPOSSIBLE;
}
case IMPLAUSIBLE:
if (plausibleOrWrongType(isPlausiblyPacked(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --packed --flat format. It looks like\n"
"it may be in --packed format. I'll try to parse it in --packed --flat format\n"
"as you requested, but if it doesn't work, try removing --flat. Use --quiet to\n"
"suppress this warning.\n"
"*** END WARNING ***\n");
} else if (plausibleOrWrongType(isPlausiblyFlat(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --packed --flat format. It looks like\n"
"it may be in --flat format. I'll try to parse it in --packed --flat format as\n"
"you requested, but if it doesn't work, try removing --packed. Use --quiet to\n"
"suppress this warning.\n"
"*** END WARNING ***\n");
} else if (plausibleOrWrongType(isPlausiblyBinary(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --packed --flat format. It looks like\n"
"it may be in regular binary format. I'll try to parse it in --packed --flat\n"
"format as you requested, but if it doesn't work, try removing --packed and\n"
"--flat. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n");
} else {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be a Cap'n Proto message in any known\n"
"binary format. I'll try to parse it anyway, but if it doesn't work, please\n"
"check your input. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n");
break;
case COMMENT:
switch (c) {
case '\n': state = PREAMBLE; continue;
default: break;
}
break;
case WRONG_TYPE:
if (plausibleOrWrongType(isPlausiblyPacked(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be the type that you specified. I'll try\n"
"to parse it anyway, but if it doesn't look right, please verify that you\n"
"have the right type. This could also be because the input is not in --flat\n"
"format; indeed, it looks like this input may be in regular --packed format,\n"
"so you might want to try removing --flat. Use --quiet to suppress this\n"
"warning.\n"
"*** END WARNING ***\n");
} else {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be the type that you specified. I'll try\n"
"to parse it anyway, but if it doesn't look right, please verify that you\n"
"have the right type. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n");
case BODY:
switch (c) {
case '\"':
case '\'':
// String literal. Let's stop here before things get complicated.
return PLAUSIBLE;
default:
break;
}
break;
}
} else if (flat) {
switch (isPlausiblyFlat(prefix)) {
case PLAUSIBLE:
break;
case IMPOSSIBLE:
if (plausibleOrWrongType(isPlausiblyPacked(prefix))) {
return "The input is not in --flat format. It looks like it is in --packed format. "
"Try that instead.";
} else if (plausibleOrWrongType(isPlausiblyPackedFlat(prefix))) {
return "The input is not in --flat format. It looks like it is in --packed --flat "
"format. Try adding --packed.";
} else if (plausibleOrWrongType(isPlausiblyBinary(prefix))) {
return "The input is not in --flat format. It looks like it is in regular binary "
"format. Try removing the --flat flag.";
} else {
return "The input is not a Cap'n Proto message.";
}
case IMPLAUSIBLE:
if (plausibleOrWrongType(isPlausiblyPacked(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --flat format. It looks like it may\n"
"be in --packed format. I'll try to parse it in --flat format as you\n"
"requested, but if it doesn't work, try --packed instead. Use --quiet to\n"
"suppress this warning.\n"
"*** END WARNING ***\n");
} else if (plausibleOrWrongType(isPlausiblyPackedFlat(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --flat format. It looks like it may\n"
"be in --packed --flat format. I'll try to parse it in --flat format as you\n"
"requested, but if it doesn't work, try adding --packed. Use --quiet to\n"
"suppress this warning.\n"
"*** END WARNING ***\n");
} else if (plausibleOrWrongType(isPlausiblyBinary(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --flat format. It looks like it may\n"
"be in regular binary format. I'll try to parse it in --flat format as you\n"
"requested, but if it doesn't work, try removing --flat. Use --quiet to\n"
"suppress this warning.\n"
"*** END WARNING ***\n");
} else {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be a Cap'n Proto message in any known\n"
"binary format. I'll try to parse it anyway, but if it doesn't work, please\n"
"check your input. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n");
if ((c >= 0 && c < ' ' && c != '\n' && c != '\r' && c != '\t' && c != '\v') || c == 0x7f) {
// Unprintable character.
return IMPOSSIBLE;
}
}
return PLAUSIBLE;
}
Plausibility isPlausiblyJson(kj::ArrayPtr<const byte> prefix) {
enum { PREAMBLE, COMMENT, BODY } state;
for (char c: prefix.asChars()) {
switch (state) {
case PREAMBLE:
// Before opening parenthesis.
switch (c) {
case '{': state = BODY; continue;
case '#': state = COMMENT; continue;
case '/': state = COMMENT; continue;
case ' ':
case '\n':
case '\r':
case '\t':
case '\v':
// whitespace
break;
default:
// Not whitespace, not comment, not open brace. Impossible!
return IMPOSSIBLE;
}
break;
case WRONG_TYPE:
if (plausibleOrWrongType(isPlausiblyBinary(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be the type that you specified. I'll try\n"
"to parse it anyway, but if it doesn't look right, please verify that you\n"
"have the right type. This could also be because the input is not in --flat\n"
"format; indeed, it looks like this input may be in regular binary format,\n"
"so you might want to try removing --flat. Use --quiet to suppress this\n"
"warning.\n"
"*** END WARNING ***\n");
} else {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be the type that you specified. I'll try\n"
"to parse it anyway, but if it doesn't look right, please verify that you\n"
"have the right type. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n");
case COMMENT:
switch (c) {
case '\n': state = PREAMBLE; continue;
default: break;
}
break;
}
} else if (packed) {
switch (isPlausiblyPacked(prefix)) {
case PLAUSIBLE:
break;
case IMPOSSIBLE:
if (plausibleOrWrongType(isPlausiblyBinary(prefix))) {
return "The input is not in --packed format. It looks like it is in regular binary "
"format. Try removing the --packed flag.";
} else if (plausibleOrWrongType(isPlausiblyPackedFlat(prefix))) {
return "The input is not in --packed format, nor does it look like it is in regular "
"binary format. It looks like it could be in --packed --flat format, although "
"that is unusual so I could be wrong.";
} else if (plausibleOrWrongType(isPlausiblyFlat(prefix))) {
return "The input is not in --packed format, nor does it look like it is in regular "
"binary format. It looks like it could be in --flat format, although that "
"is unusual so I could be wrong.";
} else {
return "The input is not a Cap'n Proto message.";
}
case IMPLAUSIBLE:
if (plausibleOrWrongType(isPlausiblyPackedFlat(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --packed format. It looks like it may\n"
"be in --packed --flat format. I'll try to parse it in --packed format as you\n"
"requested, but if it doesn't work, try adding --flat. Use --quiet to\n"
"suppress this warning.\n"
"*** END WARNING ***\n");
} else if (plausibleOrWrongType(isPlausiblyBinary(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --packed format. It looks like it\n"
"may be in regular binary format. I'll try to parse it in --packed format as\n"
"you requested, but if it doesn't work, try removing --packed. Use --quiet to\n"
"suppress this warning.\n"
"*** END WARNING ***\n");
} else if (plausibleOrWrongType(isPlausiblyFlat(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in --packed format, nor does it look\n"
"like it's in regular binary format. It looks like it could be in --flat\n"
"format, although that is unusual so I could be wrong. I'll try to parse\n"
"it in --flat format as you requested, but if it doesn't work, you might\n"
"want to try --flat, or the data may not be Cap'n Proto at all. Use\n"
"--quiet to suppress this warning.\n"
"*** END WARNING ***\n");
} else {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be a Cap'n Proto message in any known\n"
"binary format. I'll try to parse it anyway, but if it doesn't work, please\n"
"check your input. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n");
case BODY:
switch (c) {
case '\"':
// String literal. Let's stop here before things get complicated.
return PLAUSIBLE;
default:
break;
}
break;
case WRONG_TYPE:
}
if ((c > 0 && c < ' ' && c != '\n' && c != '\r' && c != '\t' && c != '\v') || c == 0x7f) {
// Unprintable character.
return IMPOSSIBLE;
}
}
return PLAUSIBLE;
}
Plausibility isPlausibly(Format format, kj::ArrayPtr<const byte> prefix) {
switch (format) {
case Format::BINARY : return isPlausiblyBinary (prefix);
case Format::PACKED : return isPlausiblyPacked (prefix);
case Format::FLAT : return isPlausiblyFlat (prefix);
case Format::FLAT_PACKED: return isPlausiblyPackedFlat(prefix);
case Format::TEXT : return isPlausiblyText (prefix);
case Format::JSON : return isPlausiblyJson (prefix);
}
}
kj::Maybe<Format> guessFormat(kj::ArrayPtr<const byte> prefix) {
Format candidates[] = {
Format::BINARY,
Format::TEXT,
Format::PACKED,
Format::JSON,
Format::FLAT,
Format::FLAT_PACKED
};
for (Format candidate: candidates) {
if (plausibleOrWrongType(isPlausibly(candidate, prefix))) {
return candidate;
}
}
return nullptr;
}
kj::MainBuilder::Validity checkPlausibility(Format format, kj::ArrayPtr<const byte> prefix) {
switch (isPlausibly(format, prefix)) {
case PLAUSIBLE:
return true;
case IMPOSSIBLE:
KJ_IF_MAYBE(guess, guessFormat(prefix)) {
return kj::str(
"The input is not in \"", toString(format), "\" format. It looks like it is in \"",
toString(*guess), "\" format. Try that instead.");
} else {
return kj::str(
"The input is not in \"", toString(format), "\" format.");
}
case IMPLAUSIBLE:
KJ_IF_MAYBE(guess, guessFormat(prefix)) {
context.warning(kj::str(
"*** WARNING ***\n"
"The input data does not appear to be in \"", toString(format), "\" format. It\n"
"looks like it may be in \"", toString(*guess), "\" format. I'll try to parse\n"
"it in \"", toString(format), "\" format as you requested, but if it doesn't work,\n"
"try \"", toString(format), "\" instead. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n"));
} else {
context.warning(kj::str(
"*** WARNING ***\n"
"The input data does not appear to be in \"", toString(format), "\" format, nor\n"
"in any other known format. I'll try to parse it in \"", toString(format), "\"\n"
"format anyway, as you requested. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n"));
}
return true;
case WRONG_TYPE:
if (format == Format::FLAT && plausibleOrWrongType(isPlausiblyBinary(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be the type that you specified. I'll try\n"
"The input data does not appear to match the schema that you specified. I'll try\n"
"to parse it anyway, but if it doesn't look right, please verify that you\n"
"have the right type. Use --quiet to suppress this warning.\n"
"have the right schema. This could also be because the input is not in \"flat\"\n"
"format; indeed, it looks like this input may be in regular binary format,\n"
"so you might want to try \"binary\" instead. Use --quiet to suppress this\n"
"warning.\n"
"*** END WARNING ***\n");
break;
}
} else {
switch (isPlausiblyBinary(prefix)) {
case PLAUSIBLE:
break;
case IMPOSSIBLE:
if (plausibleOrWrongType(isPlausiblyPacked(prefix))) {
return "The input is not in regular binary format. It looks like it is in --packed "
"format. Try adding the --packed flag.";
} else if (plausibleOrWrongType(isPlausiblyFlat(prefix))) {
return "The input is not in regular binary format, nor does it look like it is in "
"--packed format. It looks like it could be in --flat format, although that "
"is unusual so I could be wrong.";
} else if (plausibleOrWrongType(isPlausiblyPackedFlat(prefix))) {
return "The input is not in regular binary format, nor does it look like it is in "
"--packed format. It looks like it could be in --packed --flat format, "
"although that is unusual so I could be wrong.";
} else {
return "The input is not a Cap'n Proto message.";
}
case IMPLAUSIBLE:
if (plausibleOrWrongType(isPlausiblyPacked(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in regular binary format. It looks like\n"
"it may be in --packed format. I'll try to parse it in regular format as you\n"
"requested, but if it doesn't work, try adding --packed. Use --quiet to\n"
"suppress this warning.\n"
"*** END WARNING ***\n");
} else if (plausibleOrWrongType(isPlausiblyPacked(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in regular binary format. It looks like\n"
"it may be in --packed --flat format. I'll try to parse it in regular format as\n"
"you requested, but if it doesn't work, try adding --packed --flat. Use --quiet\n"
"to suppress this warning.\n"
"*** END WARNING ***\n");
} else if (plausibleOrWrongType(isPlausiblyFlat(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be in regular binary format, nor does it\n"
"look like it's in --packed format. It looks like it could be in --flat\n"
"format, although that is unusual so I could be wrong. I'll try to parse\n"
"it in regular format as you requested, but if it doesn't work, you might\n"
"want to try --flat, or the data may not be Cap'n Proto at all. Use\n"
"--quiet to suppress this warning.\n"
"*** END WARNING ***\n");
} else {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be a Cap'n Proto message in any known\n"
"binary format. I'll try to parse it anyway, but if it doesn't work, please\n"
"check your input. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n");
}
break;
case WRONG_TYPE:
} else if (format == Format::FLAT_PACKED &&
plausibleOrWrongType(isPlausiblyPacked(prefix))) {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to match the schema that you specified. I'll try\n"
"to parse it anyway, but if it doesn't look right, please verify that you\n"
"have the right schema. This could also be because the input is not in \"flat-packed\"\n"
"format; indeed, it looks like this input may be in regular packed format,\n"
"so you might want to try \"packed\" instead. Use --quiet to suppress this\n"
"warning.\n"
"*** END WARNING ***\n");
} else {
context.warning(
"*** WARNING ***\n"
"The input data does not appear to be the type that you specified. I'll try\n"
"to parse it anyway, but if it doesn't look right, please verify that you\n"
"have the right type. Use --quiet to suppress this warning.\n"
"*** END WARNING ***\n");
break;
}
}
return true;
}
return true;
KJ_UNREACHABLE;
}
public:
// -----------------------------------------------------------------
kj::MainBuilder::Validity encode() {
kj::Vector<char> allText;
{
kj::FdInputStream rawInput(STDIN_FILENO);
kj::BufferedInputStreamWrapper input(rawInput);
for (;;) {
auto buf = input.tryGetReadBuffer();
if (buf.size() == 0) break;
allText.addAll(buf.asChars());
input.skip(buf.size());
}
}
EncoderErrorReporter errorReporter(*this, allText);
MallocMessageBuilder arena;
// Lex the input.
auto lexedTokens = arena.initRoot<LexedTokens>();
lex(allText, lexedTokens, errorReporter);
// Set up the parser.
CapnpParser parser(arena.getOrphanage(), errorReporter);
auto tokens = lexedTokens.asReader().getTokens();
CapnpParser::ParserInput parserInput(tokens.begin(), tokens.end());
// Set up stuff for the ValueTranslator.
ValueResolverGlue resolver(compiler->getLoader(), errorReporter);
// Set up output stream.
kj::FdOutputStream rawOutput(STDOUT_FILENO);
kj::BufferedOutputStreamWrapper output(rawOutput);
while (parserInput.getPosition() != tokens.end()) {
KJ_IF_MAYBE(expression, parser.getParsers().expression(parserInput)) {
MallocMessageBuilder item(
segmentSize == 0 ? SUGGESTED_FIRST_SEGMENT_WORDS : segmentSize,
segmentSize == 0 ? SUGGESTED_ALLOCATION_STRATEGY : AllocationStrategy::FIXED_SIZE);
ValueTranslator translator(resolver, errorReporter, item.getOrphanage());
convertFrom = Format::TEXT;
convertTo = formatFromDeprecatedFlags(Format::BINARY);
return convert();
}
KJ_IF_MAYBE(value, translator.compileValue(expression->getReader(), rootType)) {
if (segmentSize == 0) {
writeFlat(value->getReader().as<DynamicStruct>(), output);
} else {
item.adoptRoot(value->releaseAs<DynamicStruct>());
if (packed) {
writePackedMessage(output, item);
} else {
writeMessage(output, item);
}
}
} else {
// Errors were reported, so we'll exit with a failure status later.
}
} else {
auto best = parserInput.getBest();
if (best == tokens.end()) {
context.exitError("Premature EOF.");
} else {
errorReporter.addErrorOn(*best, "Parse error.");
context.exit();
}
}
kj::MainBuilder::Validity setEvalOutputFormat(kj::StringPtr format) {
KJ_IF_MAYBE(f, parseFormatName(format)) {
convertTo = *f;
return true;
} else {
return kj::str("unknown format: ", format);
}
output.flush();
context.exit();
KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
}
kj::MainBuilder::Validity evalConst(kj::StringPtr name) {
convertTo = formatFromDeprecatedFlags(convertTo);
KJ_ASSERT(sourceFiles.size() == 1);
auto parser = kj::parse::sequence(
......@@ -1408,15 +1684,15 @@ public:
}
// OK, we have a value. Print it.
if (binary || packed || flat) {
if (convertTo != Format::TEXT) {
if (value.getType() != DynamicValue::STRUCT) {
return "not a struct; binary output is only available on structs";
}
kj::FdOutputStream rawOutput(STDOUT_FILENO);
kj::BufferedOutputStreamWrapper output(rawOutput);
writeFlat(value.as<DynamicStruct>(), output);
output.flush();
auto structValue = value.as<DynamicStruct>();
rootType = structValue.getSchema();
kj::FdOutputStream output(STDOUT_FILENO);
writeConversion(structValue, output);
context.exit();
} else {
if (pretty && value.getType() == DynamicValue::STRUCT) {
......@@ -1431,74 +1707,6 @@ public:
KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
}
private:
void writeFlat(DynamicStruct::Reader value, kj::BufferedOutputStream& output) {
// Always copy the message to a flat array so that the output is predictable (one segment,
// in canonical order).
size_t size = value.totalSize().wordCount + 1;
kj::Array<word> space = kj::heapArray<word>(size);
memset(space.begin(), 0, size * sizeof(word));
FlatMessageBuilder flatMessage(space);
flatMessage.setRoot(value);
flatMessage.requireFilled();
if (flat && packed) {
capnp::_::PackedOutputStream packer(output);
packer.write(space.begin(), space.size() * sizeof(word));
} else if (flat) {
output.write(space.begin(), space.size() * sizeof(word));
} else if (packed) {
writePackedMessage(output, flatMessage);
} else {
writeMessage(output, flatMessage);
}
}
class EncoderErrorReporter final: public ErrorReporter {
public:
EncoderErrorReporter(GlobalErrorReporter& globalReporter,
kj::ArrayPtr<const char> content)
: globalReporter(globalReporter), lineBreaks(content) {}
void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
globalReporter.addError("<stdin>", lineBreaks.toSourcePos(startByte),
lineBreaks.toSourcePos(endByte), message);
}
bool hadErrors() override {
return globalReporter.hadErrors();
}
private:
GlobalErrorReporter& globalReporter;
LineBreakTable lineBreaks;
};
class ValueResolverGlue final: public ValueTranslator::Resolver {
public:
ValueResolverGlue(const SchemaLoader& loader, ErrorReporter& errorReporter)
: loader(loader), errorReporter(errorReporter) {}
kj::Maybe<Schema> resolveType(uint64_t id) {
// Don't use tryGet() here because we shouldn't even be here if there were compile errors.
return loader.get(id);
}
kj::Maybe<DynamicValue::Reader> resolveConstant(Expression::Reader name) override {
errorReporter.addErrorOn(name, kj::str("External constants not allowed in encode input."));
return nullptr;
}
kj::Maybe<kj::Array<const byte>> readEmbed(LocatedText::Reader filename) override {
errorReporter.addErrorOn(filename, kj::str("External embeds not allowed in encode input."));
return nullptr;
}
private:
const SchemaLoader& loader;
ErrorReporter& errorReporter;
};
public:
// =====================================================================================
......@@ -1544,6 +1752,10 @@ private:
kj::Vector<kj::String> sourcePrefixes;
bool addStandardImportPaths = true;
Format convertFrom = Format::BINARY;
Format convertTo = Format::BINARY;
// For the "convert" command.
bool binary = false;
bool flat = false;
bool packed = false;
......
......@@ -179,6 +179,8 @@ public:
template <typename T, typename = kj::EnableIf<kind<FromReader<T>>() == Kind::STRUCT>>
inline Reader(T&& value): Reader(toDynamic(value)) {}
inline operator AnyStruct::Reader() const { return AnyStruct::Reader(reader); }
inline MessageSize totalSize() const { return reader.totalSize().asPublic(); }
template <typename T>
......@@ -234,6 +236,7 @@ private:
friend class Orphan<DynamicStruct>;
friend class Orphan<DynamicValue>;
friend class Orphan<AnyPointer>;
friend class AnyStruct::Reader;
};
class DynamicStruct::Builder {
......@@ -246,6 +249,8 @@ public:
template <typename T, typename = kj::EnableIf<kind<FromBuilder<T>>() == Kind::STRUCT>>
inline Builder(T&& value): Builder(toDynamic(value)) {}
inline operator AnyStruct::Reader() { return AnyStruct::Builder(builder); }
inline MessageSize totalSize() const { return asReader().totalSize(); }
template <typename T>
......@@ -323,6 +328,7 @@ private:
friend class Orphan<DynamicStruct>;
friend class Orphan<DynamicValue>;
friend class Orphan<AnyPointer>;
friend class AnyStruct::Builder;
};
class DynamicStruct::Pipeline {
......@@ -364,6 +370,8 @@ public:
template <typename T, typename = kj::EnableIf<kind<FromReader<T>>() == Kind::LIST>>
inline Reader(T&& value): Reader(toDynamic(value)) {}
inline operator AnyList::Reader() const { return AnyList::Reader(reader); }
template <typename T>
typename T::Reader as() const;
// Try to convert to any List<T>, Data, or Text. Throws an exception if the underlying data
......@@ -407,6 +415,8 @@ public:
template <typename T, typename = kj::EnableIf<kind<FromBuilder<T>>() == Kind::LIST>>
inline Builder(T&& value): Builder(toDynamic(value)) {}
inline operator AnyList::Builder() { return AnyList::Builder(builder); }
template <typename T>
typename T::Builder as();
// Try to convert to any List<T>, Data, or Text. Throws an exception if the underlying data
......@@ -1516,6 +1526,16 @@ inline AnyStruct::Builder DynamicStruct::Builder::as<AnyStruct>() {
return AnyStruct::Builder(builder);
}
template <>
inline DynamicStruct::Reader AnyStruct::Reader::as<DynamicStruct>(StructSchema schema) const {
return DynamicStruct::Reader(schema, _reader);
}
template <>
inline DynamicStruct::Builder AnyStruct::Builder::as<DynamicStruct>(StructSchema schema) {
return DynamicStruct::Builder(schema, _builder);
}
template <typename T>
typename T::Pipeline DynamicStruct::Pipeline::releaseAs() {
static_assert(kind<T>() == Kind::STRUCT,
......
......@@ -599,6 +599,8 @@ public:
template <typename T>
inline static Type from();
template <typename T>
inline static Type from(T&& value);
inline schema::Type::Which which() const;
......@@ -680,6 +682,9 @@ private:
void requireUsableAs(Type expected) const;
template <typename T, Kind k>
struct FromValueImpl;
friend class ListSchema; // only for requireUsableAs()
};
......@@ -899,6 +904,29 @@ inline schema::Type::AnyPointer::Unconstrained::Which Type::whichAnyPointerKind(
template <typename T>
inline Type Type::from() { return Type(Schema::from<T>()); }
template <typename T, Kind k>
struct Type::FromValueImpl {
template <typename U>
static inline Type type(U&& value) {
return Type::from<T>();
}
};
template <typename T>
struct Type::FromValueImpl<T, Kind::OTHER> {
template <typename U>
static inline Type type(U&& value) {
// All dynamic types have getSchema().
return value.getSchema();
}
};
template <typename T>
inline Type Type::from(T&& value) {
typedef FromAny<kj::Decay<T>> Base;
return Type::FromValueImpl<Base, kind<Base>()>::type(kj::fwd<T>(value));
}
inline bool Type::isVoid () const { return baseType == schema::Type::VOID && listDepth == 0; }
inline bool Type::isBool () const { return baseType == schema::Type::BOOL && listDepth == 0; }
inline bool Type::isInt8 () const { return baseType == schema::Type::INT8 && listDepth == 0; }
......
{ "voidField": null,
"boolField": true,
"int8Field": -123,
"int16Field": -12345,
"int32Field": -12345678,
"int64Field": "-123456789012345",
"uInt8Field": 234,
"uInt16Field": 45678,
"uInt32Field": 3456789012,
"uInt64Field": "12345678901234567890",
"float32Field": 1234.5,
"float64Field": -1.23e47,
"textField": "foo",
"dataField": [98, 97, 114],
"structField": {
"voidField": null,
"boolField": true,
"int8Field": -12,
"int16Field": 3456,
"int32Field": -78901234,
"int64Field": "56789012345678",
"uInt8Field": 90,
"uInt16Field": 1234,
"uInt32Field": 56789012,
"uInt64Field": "345678901234567890",
"float32Field": -1.2499999646475857e-10,
"float64Field": 345,
"textField": "baz",
"dataField": [113, 117, 120],
"structField": {
"voidField": null,
"boolField": false,
"int8Field": 0,
"int16Field": 0,
"int32Field": 0,
"int64Field": "0",
"uInt8Field": 0,
"uInt16Field": 0,
"uInt32Field": 0,
"uInt64Field": "0",
"float32Field": 0,
"float64Field": 0,
"textField": "nested",
"structField": {"voidField": null, "boolField": false, "int8Field": 0, "int16Field": 0, "int32Field": 0, "int64Field": "0", "uInt8Field": 0, "uInt16Field": 0, "uInt32Field": 0, "uInt64Field": "0", "float32Field": 0, "float64Field": 0, "textField": "really nested", "enumField": "foo", "interfaceField": null},
"enumField": "foo",
"interfaceField": null },
"enumField": "baz",
"interfaceField": null,
"voidList": [null, null, null],
"boolList": [false, true, false, true, true],
"int8List": [12, -34, -128, 127],
"int16List": [1234, -5678, -32768, 32767],
"int32List": [12345678, -90123456, -2147483648, 2147483647],
"int64List": ["123456789012345", "-678901234567890", "-9223372036854775808", "9223372036854775807"],
"uInt8List": [12, 34, 0, 255],
"uInt16List": [1234, 5678, 0, 65535],
"uInt32List": [12345678, 90123456, 0, 4294967295],
"uInt64List": ["123456789012345", "678901234567890", "0", "18446744073709551615"],
"float32List": [0, 1234567, 9.9999999338158125e36, -9.9999999338158125e36, 9.99999991097579e-38, -9.99999991097579e-38],
"float64List": [0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306],
"textList": ["quux", "corge", "grault"],
"dataList": [[103, 97, 114, 112, 108, 121], [119, 97, 108, 100, 111], [102, 114, 101, 100]],
"structList": [
{"voidField": null, "boolField": false, "int8Field": 0, "int16Field": 0, "int32Field": 0, "int64Field": "0", "uInt8Field": 0, "uInt16Field": 0, "uInt32Field": 0, "uInt64Field": "0", "float32Field": 0, "float64Field": 0, "textField": "x structlist 1", "enumField": "foo", "interfaceField": null},
{"voidField": null, "boolField": false, "int8Field": 0, "int16Field": 0, "int32Field": 0, "int64Field": "0", "uInt8Field": 0, "uInt16Field": 0, "uInt32Field": 0, "uInt64Field": "0", "float32Field": 0, "float64Field": 0, "textField": "x structlist 2", "enumField": "foo", "interfaceField": null},
{"voidField": null, "boolField": false, "int8Field": 0, "int16Field": 0, "int32Field": 0, "int64Field": "0", "uInt8Field": 0, "uInt16Field": 0, "uInt32Field": 0, "uInt64Field": "0", "float32Field": 0, "float64Field": 0, "textField": "x structlist 3", "enumField": "foo", "interfaceField": null} ],
"enumList": ["qux", "bar", "grault"] },
"enumField": "corge",
"interfaceField": null,
"voidList": [null, null, null, null, null, null],
"boolList": [true, false, false, true],
"int8List": [111, -111],
"int16List": [11111, -11111],
"int32List": [111111111, -111111111],
"int64List": ["1111111111111111111", "-1111111111111111111"],
"uInt8List": [111, 222],
"uInt16List": [33333, 44444],
"uInt32List": [3333333333],
"uInt64List": ["11111111111111111111"],
"float32List": [5555.5, "Infinity", "-Infinity", "NaN"],
"float64List": [7777.75, "Infinity", "-Infinity", "NaN"],
"textList": ["plugh", "xyzzy", "thud"],
"dataList": [[111, 111, 112, 115], [101, 120, 104, 97, 117, 115, 116, 101, 100], [114, 102, 99, 51, 48, 57, 50]],
"structList": [
{"voidField": null, "boolField": false, "int8Field": 0, "int16Field": 0, "int32Field": 0, "int64Field": "0", "uInt8Field": 0, "uInt16Field": 0, "uInt32Field": 0, "uInt64Field": "0", "float32Field": 0, "float64Field": 0, "textField": "structlist 1", "enumField": "foo", "interfaceField": null},
{"voidField": null, "boolField": false, "int8Field": 0, "int16Field": 0, "int32Field": 0, "int64Field": "0", "uInt8Field": 0, "uInt16Field": 0, "uInt32Field": 0, "uInt64Field": "0", "float32Field": 0, "float64Field": 0, "textField": "structlist 2", "enumField": "foo", "interfaceField": null},
{"voidField": null, "boolField": false, "int8Field": 0, "int16Field": 0, "int32Field": 0, "int64Field": "0", "uInt8Field": 0, "uInt16Field": 0, "uInt32Field": 0, "uInt64Field": "0", "float32Field": 0, "float64Field": 0, "textField": "structlist 3", "enumField": "foo", "interfaceField": null} ],
"enumList": ["foo", "garply"] }
{"voidField":null,"boolField":true,"int8Field":-123,"int16Field":-12345,"int32Field":-12345678,"int64Field":"-123456789012345","uInt8Field":234,"uInt16Field":45678,"uInt32Field":3456789012,"uInt64Field":"12345678901234567890","float32Field":1234.5,"float64Field":-1.23e47,"textField":"foo","dataField":[98,97,114],"structField":{"voidField":null,"boolField":true,"int8Field":-12,"int16Field":3456,"int32Field":-78901234,"int64Field":"56789012345678","uInt8Field":90,"uInt16Field":1234,"uInt32Field":56789012,"uInt64Field":"345678901234567890","float32Field":-1.2499999646475857e-10,"float64Field":345,"textField":"baz","dataField":[113,117,120],"structField":{"voidField":null,"boolField":false,"int8Field":0,"int16Field":0,"int32Field":0,"int64Field":"0","uInt8Field":0,"uInt16Field":0,"uInt32Field":0,"uInt64Field":"0","float32Field":0,"float64Field":0,"textField":"nested","structField":{"voidField":null,"boolField":false,"int8Field":0,"int16Field":0,"int32Field":0,"int64Field":"0","uInt8Field":0,"uInt16Field":0,"uInt32Field":0,"uInt64Field":"0","float32Field":0,"float64Field":0,"textField":"really nested","enumField":"foo","interfaceField":null},"enumField":"foo","interfaceField":null},"enumField":"baz","interfaceField":null,"voidList":[null,null,null],"boolList":[false,true,false,true,true],"int8List":[12,-34,-128,127],"int16List":[1234,-5678,-32768,32767],"int32List":[12345678,-90123456,-2147483648,2147483647],"int64List":["123456789012345","-678901234567890","-9223372036854775808","9223372036854775807"],"uInt8List":[12,34,0,255],"uInt16List":[1234,5678,0,65535],"uInt32List":[12345678,90123456,0,4294967295],"uInt64List":["123456789012345","678901234567890","0","18446744073709551615"],"float32List":[0,1234567,9.9999999338158125e36,-9.9999999338158125e36,9.99999991097579e-38,-9.99999991097579e-38],"float64List":[0,123456789012345,1e306,-1e306,1e-306,-1e-306],"textList":["quux","corge","grault"],"dataList":[[103,97,114,112,108,121],[119,97,108,100,111],[102,114,101,100]],"structList":[{"voidField":null,"boolField":false,"int8Field":0,"int16Field":0,"int32Field":0,"int64Field":"0","uInt8Field":0,"uInt16Field":0,"uInt32Field":0,"uInt64Field":"0","float32Field":0,"float64Field":0,"textField":"x structlist 1","enumField":"foo","interfaceField":null},{"voidField":null,"boolField":false,"int8Field":0,"int16Field":0,"int32Field":0,"int64Field":"0","uInt8Field":0,"uInt16Field":0,"uInt32Field":0,"uInt64Field":"0","float32Field":0,"float64Field":0,"textField":"x structlist 2","enumField":"foo","interfaceField":null},{"voidField":null,"boolField":false,"int8Field":0,"int16Field":0,"int32Field":0,"int64Field":"0","uInt8Field":0,"uInt16Field":0,"uInt32Field":0,"uInt64Field":"0","float32Field":0,"float64Field":0,"textField":"x structlist 3","enumField":"foo","interfaceField":null}],"enumList":["qux","bar","grault"]},"enumField":"corge","interfaceField":null,"voidList":[null,null,null,null,null,null],"boolList":[true,false,false,true],"int8List":[111,-111],"int16List":[11111,-11111],"int32List":[111111111,-111111111],"int64List":["1111111111111111111","-1111111111111111111"],"uInt8List":[111,222],"uInt16List":[33333,44444],"uInt32List":[3333333333],"uInt64List":["11111111111111111111"],"float32List":[5555.5,"Infinity","-Infinity","NaN"],"float64List":[7777.75,"Infinity","-Infinity","NaN"],"textList":["plugh","xyzzy","thud"],"dataList":[[111,111,112,115],[101,120,104,97,117,115,116,101,100],[114,102,99,51,48,57,50]],"structList":[{"voidField":null,"boolField":false,"int8Field":0,"int16Field":0,"int32Field":0,"int64Field":"0","uInt8Field":0,"uInt16Field":0,"uInt32Field":0,"uInt64Field":"0","float32Field":0,"float64Field":0,"textField":"structlist 1","enumField":"foo","interfaceField":null},{"voidField":null,"boolField":false,"int8Field":0,"int16Field":0,"int32Field":0,"int64Field":"0","uInt8Field":0,"uInt16Field":0,"uInt32Field":0,"uInt64Field":"0","float32Field":0,"float64Field":0,"textField":"structlist 2","enumField":"foo","interfaceField":null},{"voidField":null,"boolField":false,"int8Field":0,"int16Field":0,"int32Field":0,"int64Field":"0","uInt8Field":0,"uInt16Field":0,"uInt32Field":0,"uInt64Field":"0","float32Field":0,"float64Field":0,"textField":"structlist 3","enumField":"foo","interfaceField":null}],"enumList":["foo","garply"]}
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