blob: e17d47faf5315b22bdf007e47fd8f8c684791eb8 [file] [log] [blame]
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "dynamic.h"
#include <kj/debug.h>
#include <kj/vector.h>
#include <kj/encoding.h>
namespace capnp {
namespace {
enum PrintMode {
BARE,
// The value is planned to be printed on its own line, unless it is very short and contains
// no inner newlines.
PREFIXED,
// The value is planned to be printed with a prefix, like "memberName = " (a struct field).
PARENTHESIZED
// The value is printed in parenthesized (a union value).
};
enum class PrintKind {
LIST,
RECORD
};
class Indent {
public:
explicit Indent(bool enable): amount(enable ? 1 : 0) {}
Indent next() {
return Indent(amount == 0 ? 0 : amount + 1);
}
kj::StringTree delimit(kj::Array<kj::StringTree> items, PrintMode mode, PrintKind kind) {
if (amount == 0 || canPrintAllInline(items, kind)) {
return kj::StringTree(kj::mv(items), ", ");
} else {
KJ_STACK_ARRAY(char, delimArrayPtr, amount * 2 + 3, 32, 256);
auto delim = delimArrayPtr.begin();
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)), ' ');
}
}
private:
uint amount;
explicit Indent(uint amount): amount(amount) {}
static constexpr size_t maxInlineValueSize = 24;
static constexpr size_t maxInlineRecordSize = 64;
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;
}
return true;
}
static bool canPrintAllInline(const kj::Array<kj::StringTree>& items, PrintKind kind) {
size_t totalSize = 0;
for (auto& item: items) {
if (!canPrintInline(item)) return false;
if (kind == PrintKind::RECORD) {
totalSize += item.size();
if (totalSize > maxInlineRecordSize) return false;
}
}
return true;
}
};
static schema::Type::Which whichFieldType(const StructSchema::Field& field) {
auto proto = field.getProto();
switch (proto.which()) {
case schema::Field::SLOT:
return proto.getSlot().getType().which();
case schema::Field::GROUP:
return schema::Type::STRUCT;
}
KJ_UNREACHABLE;
}
static kj::StringTree print(const DynamicValue::Reader& value,
schema::Type::Which which, Indent indent,
PrintMode mode) {
switch (value.getType()) {
case DynamicValue::UNKNOWN:
return kj::strTree("?");
case DynamicValue::VOID:
return kj::strTree("void");
case DynamicValue::BOOL:
return kj::strTree(value.as<bool>() ? "true" : "false");
case DynamicValue::INT:
return kj::strTree(value.as<int64_t>());
case DynamicValue::UINT:
return kj::strTree(value.as<uint64_t>());
case DynamicValue::FLOAT:
if (which == schema::Type::FLOAT32) {
return kj::strTree(value.as<float>());
} else {
return kj::strTree(value.as<double>());
}
case DynamicValue::TEXT: {
kj::ArrayPtr<const char> chars = value.as<Text>();
return kj::strTree('"', kj::encodeCEscape(chars), '"');
}
case DynamicValue::DATA: {
// TODO(someday): Maybe data should be printed as binary literal.
kj::ArrayPtr<const byte> bytes = value.as<Data>().asBytes();
return kj::strTree('"', kj::encodeCEscape(bytes), '"');
}
case DynamicValue::LIST: {
auto listValue = value.as<DynamicList>();
auto which = listValue.getSchema().whichElementType();
kj::Array<kj::StringTree> elements = KJ_MAP(element, listValue) {
return print(element, which, indent.next(), BARE);
};
return kj::strTree('[', indent.delimit(kj::mv(elements), mode, PrintKind::LIST), ']');
}
case DynamicValue::ENUM: {
auto enumValue = value.as<DynamicEnum>();
KJ_IF_MAYBE(enumerant, enumValue.getEnumerant()) {
return kj::strTree(enumerant->getProto().getName());
} else {
// Unknown enum value; output raw number.
return kj::strTree('(', enumValue.getRaw(), ')');
}
break;
}
case DynamicValue::STRUCT: {
auto structValue = value.as<DynamicStruct>();
auto unionFields = structValue.getSchema().getUnionFields();
auto nonUnionFields = structValue.getSchema().getNonUnionFields();
kj::Vector<kj::StringTree> printedFields(nonUnionFields.size() + (unionFields.size() != 0));
// We try to write the union field, if any, in proper order with the rest.
auto which = structValue.which();
kj::StringTree unionValue;
KJ_IF_MAYBE(field, which) {
// Even if the union field has its default value, if it is not the default field of the
// union then we have to print it anyway.
auto fieldProto = field->getProto();
if (fieldProto.getDiscriminantValue() != 0 || structValue.has(*field)) {
unionValue = kj::strTree(
fieldProto.getName(), " = ",
print(structValue.get(*field), whichFieldType(*field), indent.next(), PREFIXED));
} else {
which = nullptr;
}
}
for (auto field: nonUnionFields) {
KJ_IF_MAYBE(unionField, which) {
if (unionField->getIndex() < field.getIndex()) {
printedFields.add(kj::mv(unionValue));
which = nullptr;
}
}
if (structValue.has(field)) {
printedFields.add(kj::strTree(
field.getProto().getName(), " = ",
print(structValue.get(field), whichFieldType(field), indent.next(), PREFIXED)));
}
}
if (which != nullptr) {
// Union value is last.
printedFields.add(kj::mv(unionValue));
}
if (mode == PARENTHESIZED) {
return indent.delimit(printedFields.releaseAsArray(), mode, PrintKind::RECORD);
} else {
return kj::strTree(
'(', indent.delimit(printedFields.releaseAsArray(), mode, PrintKind::RECORD), ')');
}
}
case DynamicValue::CAPABILITY:
return kj::strTree("<external capability>");
case DynamicValue::ANY_POINTER:
return kj::strTree("<opaque pointer>");
}
KJ_UNREACHABLE;
}
kj::StringTree stringify(DynamicValue::Reader value) {
return print(value, schema::Type::STRUCT, Indent(false), BARE);
}
} // namespace
kj::StringTree prettyPrint(DynamicStruct::Reader value) {
return print(value, schema::Type::STRUCT, Indent(true), BARE);
}
kj::StringTree prettyPrint(DynamicList::Reader value) {
return print(value, schema::Type::LIST, Indent(true), BARE);
}
kj::StringTree prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
kj::StringTree prettyPrint(DynamicList::Builder value) { return prettyPrint(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 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::StringTree structString(StructReader reader, const RawBrandedSchema& schema) {
return stringify(DynamicStruct::Reader(Schema(&schema).asStruct(), reader));
}
kj::String enumString(uint16_t value, const RawBrandedSchema& schema) {
auto enumerants = Schema(&schema).asEnum().getEnumerants();
if (value < enumerants.size()) {
return kj::heapString(enumerants[value].getProto().getName());
} else {
return kj::str(value);
}
}
} // namespace _ (private)
} // namespace capnp