stringify.c++ 9.38 KB
Newer Older
Kenton Varda's avatar
Kenton Varda committed
1 2
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
3
//
Kenton Varda's avatar
Kenton Varda committed
4 5 6 7 8 9
// 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:
10
//
Kenton Varda's avatar
Kenton Varda committed
11 12
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
13
//
Kenton Varda's avatar
Kenton Varda committed
14 15 16 17 18 19 20
// 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.
21

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

27
namespace capnp {
28 29 30

namespace {

31 32 33 34 35 36 37 38 39 40 41 42
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).
};

43 44 45 46 47
enum class PrintKind {
  LIST,
  RECORD
};

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

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

56 57
  kj::StringTree delimit(kj::Array<kj::StringTree> items, PrintMode mode, PrintKind kind) {
    if (amount == 0 || canPrintAllInline(items, kind)) {
58 59
      return kj::StringTree(kj::mv(items), ", ");
    } else {
60 61
      KJ_STACK_ARRAY(char, delimArrayPtr, amount * 2 + 3, 32, 256);
      auto delim = delimArrayPtr.begin();
62 63 64 65 66 67 68 69 70 71 72
      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)), ' ');
    }
73 74 75 76
  }

private:
  uint amount;
77 78 79 80

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

  static constexpr size_t maxInlineValueSize = 24;
81
  static constexpr size_t maxInlineRecordSize = 64;
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

  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;
  }

98 99
  static bool canPrintAllInline(const kj::Array<kj::StringTree>& items, PrintKind kind) {
    size_t totalSize = 0;
100 101
    for (auto& item: items) {
      if (!canPrintInline(item)) return false;
102 103 104 105
      if (kind == PrintKind::RECORD) {
        totalSize += item.size();
        if (totalSize > maxInlineRecordSize) return false;
      }
106 107 108
    }
    return true;
  }
109 110
};

Kenton Varda's avatar
Kenton Varda committed
111
static schema::Type::Which whichFieldType(const StructSchema::Field& field) {
Kenton Varda's avatar
Kenton Varda committed
112 113
  auto proto = field.getProto();
  switch (proto.which()) {
114 115
    case schema::Field::SLOT:
      return proto.getSlot().getType().which();
Kenton Varda's avatar
Kenton Varda committed
116 117
    case schema::Field::GROUP:
      return schema::Type::STRUCT;
118 119 120
  }
  KJ_UNREACHABLE;
}
121

122
static kj::StringTree print(const DynamicValue::Reader& value,
Kenton Varda's avatar
Kenton Varda committed
123
                            schema::Type::Which which, Indent indent,
124
                            PrintMode mode) {
125 126
  switch (value.getType()) {
    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 136
      return kj::strTree(value.as<uint64_t>());
    case DynamicValue::FLOAT:
Kenton Varda's avatar
Kenton Varda committed
137
      if (which == schema::Type::FLOAT32) {
138
        return kj::strTree(value.as<float>());
139
      } else {
140
        return kj::strTree(value.as<double>());
141 142 143
      }
    case DynamicValue::TEXT:
    case DynamicValue::DATA: {
144
      // TODO(someday): Maybe data should be printed as binary literal.
145 146
      kj::ArrayPtr<const char> chars;
      if (value.getType() == DynamicValue::DATA) {
147
        chars = value.as<Data>().asChars();
148 149 150
      } else {
        chars = value.as<Text>();
      }
151

152
      return kj::strTree('"', kj::encodeCEscape(chars), '"');
153 154 155
    }
    case DynamicValue::LIST: {
      auto listValue = value.as<DynamicList>();
156
      auto which = listValue.getSchema().whichElementType();
157
      kj::Array<kj::StringTree> elements = KJ_MAP(element, listValue) {
158 159
        return print(element, which, indent.next(), BARE);
      };
160
      return kj::strTree('[', indent.delimit(kj::mv(elements), mode, PrintKind::LIST), ']');
161 162 163
    }
    case DynamicValue::ENUM: {
      auto enumValue = value.as<DynamicEnum>();
164
      KJ_IF_MAYBE(enumerant, enumValue.getEnumerant()) {
165
        return kj::strTree(enumerant->getProto().getName());
166
      } else {
167
        // Unknown enum value; output raw number.
Kenton Varda's avatar
Kenton Varda committed
168
        return kj::strTree('(', enumValue.getRaw(), ')');
169 170 171 172 173
      }
      break;
    }
    case DynamicValue::STRUCT: {
      auto structValue = value.as<DynamicStruct>();
Kenton Varda's avatar
Kenton Varda committed
174 175 176 177 178
      auto unionFields = structValue.getSchema().getUnionFields();
      auto nonUnionFields = structValue.getSchema().getNonUnionFields();

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

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

182 183 184 185 186 187 188 189 190
      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));
191 192
        } else {
          which = nullptr;
193 194 195
        }
      }

Kenton Varda's avatar
Kenton Varda committed
196
      for (auto field: nonUnionFields) {
197 198
        KJ_IF_MAYBE(unionField, which) {
          if (unionField->getIndex() < field.getIndex()) {
199
            printedFields.add(kj::mv(unionValue));
200 201 202
            which = nullptr;
          }
        }
Kenton Varda's avatar
Kenton Varda committed
203 204 205 206 207
        if (structValue.has(field)) {
          printedFields.add(kj::strTree(
              field.getProto().getName(), " = ",
              print(structValue.get(field), whichFieldType(field), indent.next(), PREFIXED)));
        }
208
      }
209 210 211
      if (which != nullptr) {
        // Union value is last.
        printedFields.add(kj::mv(unionValue));
212
      }
Kenton Varda's avatar
Kenton Varda committed
213 214

      if (mode == PARENTHESIZED) {
215
        return indent.delimit(printedFields.releaseAsArray(), mode, PrintKind::RECORD);
216
      } else {
217 218
        return kj::strTree(
            '(', indent.delimit(printedFields.releaseAsArray(), mode, PrintKind::RECORD), ')');
219 220
      }
    }
221 222
    case DynamicValue::CAPABILITY:
      return kj::strTree("<external capability>");
223 224
    case DynamicValue::ANY_POINTER:
      return kj::strTree("<opaque pointer>");
225
  }
226 227

  KJ_UNREACHABLE;
228 229
}

230
kj::StringTree stringify(DynamicValue::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
231
  return print(value, schema::Type::STRUCT, Indent(false), BARE);
232 233
}

Kenton Varda's avatar
Kenton Varda committed
234 235
}  // namespace

236
kj::StringTree prettyPrint(DynamicStruct::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
237
  return print(value, schema::Type::STRUCT, Indent(true), BARE);
238 239
}

240
kj::StringTree prettyPrint(DynamicList::Reader value) {
Kenton Varda's avatar
Kenton Varda committed
241
  return print(value, schema::Type::LIST, Indent(true), BARE);
242 243
}

244 245
kj::StringTree prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
kj::StringTree prettyPrint(DynamicList::Builder value) { return prettyPrint(value.asReader()); }
246

247 248 249 250 251 252 253
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()); }
Kenton Varda's avatar
Kenton Varda committed
254

255
namespace _ {  // private
256

257 258
kj::StringTree structString(StructReader reader, const RawBrandedSchema& schema) {
  return stringify(DynamicStruct::Reader(Schema(&schema).asStruct(), reader));
259 260
}

261 262 263 264 265 266 267 268 269
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);
  }
}

270
}  // namespace _ (private)
271

272
}  // namespace capnp