stringify.c++ 10.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// 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.

Kenton Varda's avatar
Kenton Varda committed
24
#include "dynamic.h"
Kenton Varda's avatar
Kenton Varda committed
25
#include <kj/debug.h>
26
#include <kj/vector.h>
27

28
namespace capnp {
29 30 31 32 33

namespace {

static const char HEXDIGITS[] = "0123456789abcdef";

34 35 36 37 38 39 40 41 42 43 44 45
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).
};

46 47
class Indent {
public:
48
  explicit Indent(bool enable): amount(enable ? 1 : 0) {}
49

50 51
  Indent next() {
    return Indent(amount == 0 ? 0 : amount + 1);
52 53
  }

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
  kj::StringTree delimit(kj::Array<kj::StringTree> items, PrintMode mode) {
    if (amount == 0 || canPrintAllInline(items)) {
      return kj::StringTree(kj::mv(items), ", ");
    } else {
      char delim[amount * 2 + 3];
      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)), ' ');
    }
70 71 72 73
  }

private:
  uint amount;
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

  explicit Indent(uint amount): amount(amount) {}

  static constexpr size_t maxInlineValueSize = 24;

  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) {
    for (auto& item: items) {
      if (!canPrintInline(item)) return false;
    }
    return true;
  }
100 101
};

Kenton Varda's avatar
Kenton Varda committed
102
static schema::Type::Which whichFieldType(const StructSchema::Field& field) {
Kenton Varda's avatar
Kenton Varda committed
103 104
  auto proto = field.getProto();
  switch (proto.which()) {
105 106
    case schema::Field::SLOT:
      return proto.getSlot().getType().which();
Kenton Varda's avatar
Kenton Varda committed
107 108
    case schema::Field::GROUP:
      return schema::Type::STRUCT;
109 110 111
  }
  KJ_UNREACHABLE;
}
112

113
static kj::StringTree print(const DynamicValue::Reader& value,
Kenton Varda's avatar
Kenton Varda committed
114
                            schema::Type::Which which, Indent indent,
115
                            PrintMode mode) {
116 117
  switch (value.getType()) {
    case DynamicValue::UNKNOWN:
118
      return kj::strTree("?");
119
    case DynamicValue::VOID:
120
      return kj::strTree("void");
121
    case DynamicValue::BOOL:
122
      return kj::strTree(value.as<bool>() ? "true" : "false");
123
    case DynamicValue::INT:
124
      return kj::strTree(value.as<int64_t>());
125
    case DynamicValue::UINT:
126 127
      return kj::strTree(value.as<uint64_t>());
    case DynamicValue::FLOAT:
Kenton Varda's avatar
Kenton Varda committed
128
      if (which == schema::Type::FLOAT32) {
129
        return kj::strTree(value.as<float>());
130
      } else {
131
        return kj::strTree(value.as<double>());
132 133 134
      }
    case DynamicValue::TEXT:
    case DynamicValue::DATA: {
135 136 137 138 139 140 141 142
      // TODO(someday):  Data probably shouldn't be printed as a string.
      kj::ArrayPtr<const char> chars;
      if (value.getType() == DynamicValue::DATA) {
        auto reader = value.as<Data>();
        chars = kj::arrayPtr(reinterpret_cast<const char*>(reader.begin()), reader.size());
      } else {
        chars = value.as<Text>();
      }
143 144 145

      kj::Vector<char> escaped(chars.size());

146
      for (char c: chars) {
147
        switch (c) {
148 149 150 151 152 153 154 155 156 157
          case '\a': escaped.addAll(kj::StringPtr("\\a")); break;
          case '\b': escaped.addAll(kj::StringPtr("\\b")); break;
          case '\f': escaped.addAll(kj::StringPtr("\\f")); break;
          case '\n': escaped.addAll(kj::StringPtr("\\n")); break;
          case '\r': escaped.addAll(kj::StringPtr("\\r")); break;
          case '\t': escaped.addAll(kj::StringPtr("\\t")); break;
          case '\v': escaped.addAll(kj::StringPtr("\\v")); break;
          case '\'': escaped.addAll(kj::StringPtr("\\\'")); break;
          case '\"': escaped.addAll(kj::StringPtr("\\\"")); break;
          case '\\': escaped.addAll(kj::StringPtr("\\\\")); break;
158 159
          default:
            if (c < 0x20) {
160 161
              escaped.add('\\');
              escaped.add('x');
162
              uint8_t c2 = c;
163 164
              escaped.add(HEXDIGITS[c2 / 16]);
              escaped.add(HEXDIGITS[c2 % 16]);
165
            } else {
166
              escaped.add(c);
167 168 169 170
            }
            break;
        }
      }
171
      return kj::strTree('"', escaped, '"');
172 173 174
    }
    case DynamicValue::LIST: {
      auto listValue = value.as<DynamicList>();
175
      auto which = listValue.getSchema().whichElementType();
176
      kj::Array<kj::StringTree> elements = KJ_MAP(element, listValue) {
177 178 179
        return print(element, which, indent.next(), BARE);
      };
      return kj::strTree('[', indent.delimit(kj::mv(elements), mode), ']');
180 181 182
    }
    case DynamicValue::ENUM: {
      auto enumValue = value.as<DynamicEnum>();
183
      KJ_IF_MAYBE(enumerant, enumValue.getEnumerant()) {
184
        return kj::strTree(enumerant->getProto().getName());
185
      } else {
186
        // Unknown enum value; output raw number.
Kenton Varda's avatar
Kenton Varda committed
187
        return kj::strTree('(', enumValue.getRaw(), ')');
188 189 190 191 192
      }
      break;
    }
    case DynamicValue::STRUCT: {
      auto structValue = value.as<DynamicStruct>();
Kenton Varda's avatar
Kenton Varda committed
193 194 195 196 197
      auto unionFields = structValue.getSchema().getUnionFields();
      auto nonUnionFields = structValue.getSchema().getNonUnionFields();

      kj::Vector<kj::StringTree> printedFields(nonUnionFields.size() + (unionFields.size() != 0));

198 199
      // We try to write the union field, if any, in proper order with the rest.
      auto which = structValue.which();
200

201 202 203 204 205 206 207 208 209
      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));
210 211
        } else {
          which = nullptr;
212 213 214
        }
      }

Kenton Varda's avatar
Kenton Varda committed
215
      for (auto field: nonUnionFields) {
216 217
        KJ_IF_MAYBE(unionField, which) {
          if (unionField->getIndex() < field.getIndex()) {
218
            printedFields.add(kj::mv(unionValue));
219 220 221
            which = nullptr;
          }
        }
Kenton Varda's avatar
Kenton Varda committed
222 223 224 225 226
        if (structValue.has(field)) {
          printedFields.add(kj::strTree(
              field.getProto().getName(), " = ",
              print(structValue.get(field), whichFieldType(field), indent.next(), PREFIXED)));
        }
227
      }
228 229 230
      if (which != nullptr) {
        // Union value is last.
        printedFields.add(kj::mv(unionValue));
231
      }
Kenton Varda's avatar
Kenton Varda committed
232 233 234

      if (mode == PARENTHESIZED) {
        return indent.delimit(printedFields.releaseAsArray(), mode);
235
      } else {
Kenton Varda's avatar
Kenton Varda committed
236
        return kj::strTree('(', indent.delimit(printedFields.releaseAsArray(), mode), ')');
237 238 239
      }
    }
    case DynamicValue::INTERFACE:
240
      KJ_FAIL_ASSERT("Don't know how to print interfaces.") {
241
        return kj::String();
242
      }
243
    case DynamicValue::OBJECT:
244
      return kj::strTree("<opaque object>");
245
  }
246 247

  KJ_UNREACHABLE;
248 249
}

250
kj::StringTree stringify(DynamicValue::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
251
  return print(value, schema::Type::STRUCT, Indent(false), BARE);
252 253
}

Kenton Varda's avatar
Kenton Varda committed
254 255
}  // namespace

256
kj::StringTree prettyPrint(DynamicStruct::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
257
  return print(value, schema::Type::STRUCT, Indent(true), BARE);
258 259
}

260
kj::StringTree prettyPrint(DynamicList::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
261
  return print(value, schema::Type::LIST, Indent(true), BARE);
262 263
}

264 265
kj::StringTree prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
kj::StringTree prettyPrint(DynamicList::Builder value) { return prettyPrint(value.asReader()); }
266

267 268 269
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); }
270 271
kj::StringTree KJ_STRINGIFY(const DynamicObject::Reader& value) { return stringify(value); }
kj::StringTree KJ_STRINGIFY(const DynamicObject::Builder& value) { return stringify(value.asReader()); }
272 273 274 275
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()); }
Kenton Varda's avatar
Kenton Varda committed
276

277
namespace _ {  // private
278

279
kj::StringTree structString(StructReader reader, const RawSchema& schema) {
280 281 282
  return stringify(DynamicStruct::Reader(StructSchema(&schema), reader));
}

283
}  // namespace _ (private)
284

285
}  // namespace capnp