capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

stringify.c++ (9610B)


      1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
      2 // Licensed under the MIT License:
      3 //
      4 // Permission is hereby granted, free of charge, to any person obtaining a copy
      5 // of this software and associated documentation files (the "Software"), to deal
      6 // in the Software without restriction, including without limitation the rights
      7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      8 // copies of the Software, and to permit persons to whom the Software is
      9 // furnished to do so, subject to the following conditions:
     10 //
     11 // The above copyright notice and this permission notice shall be included in
     12 // all copies or substantial portions of the Software.
     13 //
     14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     20 // THE SOFTWARE.
     21 
     22 #include "dynamic.h"
     23 #include <kj/debug.h>
     24 #include <kj/vector.h>
     25 #include <kj/encoding.h>
     26 
     27 namespace capnp {
     28 
     29 namespace {
     30 
     31 enum PrintMode {
     32   BARE,
     33   // The value is planned to be printed on its own line, unless it is very short and contains
     34   // no inner newlines.
     35 
     36   PREFIXED,
     37   // The value is planned to be printed with a prefix, like "memberName = " (a struct field).
     38 
     39   PARENTHESIZED
     40   // The value is printed in parenthesized (a union value).
     41 };
     42 
     43 enum class PrintKind {
     44   LIST,
     45   RECORD
     46 };
     47 
     48 class Indent {
     49 public:
     50   explicit Indent(bool enable): amount(enable ? 1 : 0) {}
     51 
     52   Indent next() {
     53     return Indent(amount == 0 ? 0 : amount + 1);
     54   }
     55 
     56   kj::StringTree delimit(kj::Array<kj::StringTree> items, PrintMode mode, PrintKind kind) {
     57     if (amount == 0 || canPrintAllInline(items, kind)) {
     58       return kj::StringTree(kj::mv(items), ", ");
     59     } else {
     60       KJ_STACK_ARRAY(char, delimArrayPtr, amount * 2 + 3, 32, 256);
     61       auto delim = delimArrayPtr.begin();
     62       delim[0] = ',';
     63       delim[1] = '\n';
     64       memset(delim + 2, ' ', amount * 2);
     65       delim[amount * 2 + 2] = '\0';
     66 
     67       // If the outer value isn't being printed on its own line, we need to add a newline/indent
     68       // before the first item, otherwise we only add a space on the assumption that it is preceded
     69       // by an open bracket or parenthesis.
     70       return kj::strTree(mode == BARE ? " " : delim + 1,
     71           kj::StringTree(kj::mv(items), kj::StringPtr(delim, amount * 2 + 2)), ' ');
     72     }
     73   }
     74 
     75 private:
     76   uint amount;
     77 
     78   explicit Indent(uint amount): amount(amount) {}
     79 
     80   static constexpr size_t maxInlineValueSize = 24;
     81   static constexpr size_t maxInlineRecordSize = 64;
     82 
     83   static bool canPrintInline(const kj::StringTree& text) {
     84     if (text.size() > maxInlineValueSize) {
     85       return false;
     86     }
     87 
     88     char flat[maxInlineValueSize + 1];
     89     text.flattenTo(flat);
     90     flat[text.size()] = '\0';
     91     if (strchr(flat, '\n') != nullptr) {
     92       return false;
     93     }
     94 
     95     return true;
     96   }
     97 
     98   static bool canPrintAllInline(const kj::Array<kj::StringTree>& items, PrintKind kind) {
     99     size_t totalSize = 0;
    100     for (auto& item: items) {
    101       if (!canPrintInline(item)) return false;
    102       if (kind == PrintKind::RECORD) {
    103         totalSize += item.size();
    104         if (totalSize > maxInlineRecordSize) return false;
    105       }
    106     }
    107     return true;
    108   }
    109 };
    110 
    111 static schema::Type::Which whichFieldType(const StructSchema::Field& field) {
    112   auto proto = field.getProto();
    113   switch (proto.which()) {
    114     case schema::Field::SLOT:
    115       return proto.getSlot().getType().which();
    116     case schema::Field::GROUP:
    117       return schema::Type::STRUCT;
    118   }
    119   KJ_UNREACHABLE;
    120 }
    121 
    122 static kj::StringTree print(const DynamicValue::Reader& value,
    123                             schema::Type::Which which, Indent indent,
    124                             PrintMode mode) {
    125   switch (value.getType()) {
    126     case DynamicValue::UNKNOWN:
    127       return kj::strTree("?");
    128     case DynamicValue::VOID:
    129       return kj::strTree("void");
    130     case DynamicValue::BOOL:
    131       return kj::strTree(value.as<bool>() ? "true" : "false");
    132     case DynamicValue::INT:
    133       return kj::strTree(value.as<int64_t>());
    134     case DynamicValue::UINT:
    135       return kj::strTree(value.as<uint64_t>());
    136     case DynamicValue::FLOAT:
    137       if (which == schema::Type::FLOAT32) {
    138         return kj::strTree(value.as<float>());
    139       } else {
    140         return kj::strTree(value.as<double>());
    141       }
    142     case DynamicValue::TEXT: {
    143       kj::ArrayPtr<const char> chars = value.as<Text>();
    144       return kj::strTree('"', kj::encodeCEscape(chars), '"');
    145     }
    146     case DynamicValue::DATA: {
    147       // TODO(someday): Maybe data should be printed as binary literal.
    148       kj::ArrayPtr<const byte> bytes = value.as<Data>().asBytes();
    149       return kj::strTree('"', kj::encodeCEscape(bytes), '"');
    150     }
    151     case DynamicValue::LIST: {
    152       auto listValue = value.as<DynamicList>();
    153       auto which = listValue.getSchema().whichElementType();
    154       kj::Array<kj::StringTree> elements = KJ_MAP(element, listValue) {
    155         return print(element, which, indent.next(), BARE);
    156       };
    157       return kj::strTree('[', indent.delimit(kj::mv(elements), mode, PrintKind::LIST), ']');
    158     }
    159     case DynamicValue::ENUM: {
    160       auto enumValue = value.as<DynamicEnum>();
    161       KJ_IF_MAYBE(enumerant, enumValue.getEnumerant()) {
    162         return kj::strTree(enumerant->getProto().getName());
    163       } else {
    164         // Unknown enum value; output raw number.
    165         return kj::strTree('(', enumValue.getRaw(), ')');
    166       }
    167       break;
    168     }
    169     case DynamicValue::STRUCT: {
    170       auto structValue = value.as<DynamicStruct>();
    171       auto unionFields = structValue.getSchema().getUnionFields();
    172       auto nonUnionFields = structValue.getSchema().getNonUnionFields();
    173 
    174       kj::Vector<kj::StringTree> printedFields(nonUnionFields.size() + (unionFields.size() != 0));
    175 
    176       // We try to write the union field, if any, in proper order with the rest.
    177       auto which = structValue.which();
    178 
    179       kj::StringTree unionValue;
    180       KJ_IF_MAYBE(field, which) {
    181         // Even if the union field has its default value, if it is not the default field of the
    182         // union then we have to print it anyway.
    183         auto fieldProto = field->getProto();
    184         if (fieldProto.getDiscriminantValue() != 0 || structValue.has(*field)) {
    185           unionValue = kj::strTree(
    186               fieldProto.getName(), " = ",
    187               print(structValue.get(*field), whichFieldType(*field), indent.next(), PREFIXED));
    188         } else {
    189           which = nullptr;
    190         }
    191       }
    192 
    193       for (auto field: nonUnionFields) {
    194         KJ_IF_MAYBE(unionField, which) {
    195           if (unionField->getIndex() < field.getIndex()) {
    196             printedFields.add(kj::mv(unionValue));
    197             which = nullptr;
    198           }
    199         }
    200         if (structValue.has(field)) {
    201           printedFields.add(kj::strTree(
    202               field.getProto().getName(), " = ",
    203               print(structValue.get(field), whichFieldType(field), indent.next(), PREFIXED)));
    204         }
    205       }
    206       if (which != nullptr) {
    207         // Union value is last.
    208         printedFields.add(kj::mv(unionValue));
    209       }
    210 
    211       if (mode == PARENTHESIZED) {
    212         return indent.delimit(printedFields.releaseAsArray(), mode, PrintKind::RECORD);
    213       } else {
    214         return kj::strTree(
    215             '(', indent.delimit(printedFields.releaseAsArray(), mode, PrintKind::RECORD), ')');
    216       }
    217     }
    218     case DynamicValue::CAPABILITY:
    219       return kj::strTree("<external capability>");
    220     case DynamicValue::ANY_POINTER:
    221       return kj::strTree("<opaque pointer>");
    222   }
    223 
    224   KJ_UNREACHABLE;
    225 }
    226 
    227 kj::StringTree stringify(DynamicValue::Reader value) {
    228   return print(value, schema::Type::STRUCT, Indent(false), BARE);
    229 }
    230 
    231 }  // namespace
    232 
    233 kj::StringTree prettyPrint(DynamicStruct::Reader value) {
    234   return print(value, schema::Type::STRUCT, Indent(true), BARE);
    235 }
    236 
    237 kj::StringTree prettyPrint(DynamicList::Reader value) {
    238   return print(value, schema::Type::LIST, Indent(true), BARE);
    239 }
    240 
    241 kj::StringTree prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
    242 kj::StringTree prettyPrint(DynamicList::Builder value) { return prettyPrint(value.asReader()); }
    243 
    244 kj::StringTree KJ_STRINGIFY(const DynamicValue::Reader& value) { return stringify(value); }
    245 kj::StringTree KJ_STRINGIFY(const DynamicValue::Builder& value) { return stringify(value.asReader()); }
    246 kj::StringTree KJ_STRINGIFY(DynamicEnum value) { return stringify(value); }
    247 kj::StringTree KJ_STRINGIFY(const DynamicStruct::Reader& value) { return stringify(value); }
    248 kj::StringTree KJ_STRINGIFY(const DynamicStruct::Builder& value) { return stringify(value.asReader()); }
    249 kj::StringTree KJ_STRINGIFY(const DynamicList::Reader& value) { return stringify(value); }
    250 kj::StringTree KJ_STRINGIFY(const DynamicList::Builder& value) { return stringify(value.asReader()); }
    251 
    252 namespace _ {  // private
    253 
    254 kj::StringTree structString(StructReader reader, const RawBrandedSchema& schema) {
    255   return stringify(DynamicStruct::Reader(Schema(&schema).asStruct(), reader));
    256 }
    257 
    258 kj::String enumString(uint16_t value, const RawBrandedSchema& schema) {
    259   auto enumerants = Schema(&schema).asEnum().getEnumerants();
    260   if (value < enumerants.size()) {
    261     return kj::heapString(enumerants[value].getProto().getName());
    262   } else {
    263     return kj::str(value);
    264   }
    265 }
    266 
    267 }  // namespace _ (private)
    268 
    269 }  // namespace capnp