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