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
27namespace capnp {
28
29namespace {
30
31enum 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
43enum class PrintKind {
44 LIST,
45 RECORD
46};
47
48class Indent {
49public:
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
75private:
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
111static 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
122static 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 case DynamicValue::DATA: {
144 // TODO(someday): Maybe data should be printed as binary literal.
145 kj::ArrayPtr<const char> chars;
146 if (value.getType() == DynamicValue::DATA) {
147 chars = value.as<Data>().asChars();
148 } else {
149 chars = value.as<Text>();
150 }
151
152 return kj::strTree('"', kj::encodeCEscape(chars), '"');
153 }
154 case DynamicValue::LIST: {
155 auto listValue = value.as<DynamicList>();
156 auto which = listValue.getSchema().whichElementType();
157 kj::Array<kj::StringTree> elements = KJ_MAP(element, listValue) {
158 return print(element, which, indent.next(), BARE);
159 };
160 return kj::strTree('[', indent.delimit(kj::mv(elements), mode, PrintKind::LIST), ']');
161 }
162 case DynamicValue::ENUM: {
163 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.
168 return kj::strTree('(', enumValue.getRaw(), ')');
169 }
170 break;
171 }
172 case DynamicValue::STRUCT: {
173 auto structValue = value.as<DynamicStruct>();
174 auto unionFields = structValue.getSchema().getUnionFields();
175 auto nonUnionFields = structValue.getSchema().getNonUnionFields();
176
177 kj::Vector<kj::StringTree> printedFields(nonUnionFields.size() + (unionFields.size() != 0));
178
179 // We try to write the union field, if any, in proper order with the rest.
180 auto which = structValue.which();
181
182 kj::StringTree unionValue;
183 KJ_IF_MAYBE(field, which) {
184 // Even if the union field has its default value, if it is not the default field of the
185 // union then we have to print it anyway.
186 auto fieldProto = field->getProto();
187 if (fieldProto.getDiscriminantValue() != 0 || structValue.has(*field)) {
188 unionValue = kj::strTree(
189 fieldProto.getName(), " = ",
190 print(structValue.get(*field), whichFieldType(*field), indent.next(), PREFIXED));
191 } else {
192 which = nullptr;
193 }
194 }
195
196 for (auto field: nonUnionFields) {
197 KJ_IF_MAYBE(unionField, which) {
198 if (unionField->getIndex() < field.getIndex()) {
199 printedFields.add(kj::mv(unionValue));
200 which = nullptr;
201 }
202 }
203 if (structValue.has(field)) {
204 printedFields.add(kj::strTree(
205 field.getProto().getName(), " = ",
206 print(structValue.get(field), whichFieldType(field), indent.next(), PREFIXED)));
207 }
208 }
209 if (which != nullptr) {
210 // Union value is last.
211 printedFields.add(kj::mv(unionValue));
212 }
213
214 if (mode == PARENTHESIZED) {
215 return indent.delimit(printedFields.releaseAsArray(), mode, PrintKind::RECORD);
216 } else {
217 return kj::strTree(
218 '(', indent.delimit(printedFields.releaseAsArray(), mode, PrintKind::RECORD), ')');
219 }
220 }
221 case DynamicValue::CAPABILITY:
222 return kj::strTree("<external capability>");
223 case DynamicValue::ANY_POINTER:
224 return kj::strTree("<opaque pointer>");
225 }
226
227 KJ_UNREACHABLE;
228}
229
230kj::StringTree stringify(DynamicValue::Reader value) {
231 return print(value, schema::Type::STRUCT, Indent(false), BARE);
232}
233
234} // namespace
235
236kj::StringTree prettyPrint(DynamicStruct::Reader value) {
237 return print(value, schema::Type::STRUCT, Indent(true), BARE);
238}
239
240kj::StringTree prettyPrint(DynamicList::Reader value) {
241 return print(value, schema::Type::LIST, Indent(true), BARE);
242}
243
244kj::StringTree prettyPrint(DynamicStruct::Builder value) { return prettyPrint(value.asReader()); }
245kj::StringTree prettyPrint(DynamicList::Builder value) { return prettyPrint(value.asReader()); }
246
247kj::StringTree KJ_STRINGIFY(const DynamicValue::Reader& value) { return stringify(value); }
248kj::StringTree KJ_STRINGIFY(const DynamicValue::Builder& value) { return stringify(value.asReader()); }
249kj::StringTree KJ_STRINGIFY(DynamicEnum value) { return stringify(value); }
250kj::StringTree KJ_STRINGIFY(const DynamicStruct::Reader& value) { return stringify(value); }
251kj::StringTree KJ_STRINGIFY(const DynamicStruct::Builder& value) { return stringify(value.asReader()); }
252kj::StringTree KJ_STRINGIFY(const DynamicList::Reader& value) { return stringify(value); }
253kj::StringTree KJ_STRINGIFY(const DynamicList::Builder& value) { return stringify(value.asReader()); }
254
255namespace _ { // private
256
257kj::StringTree structString(StructReader reader, const RawBrandedSchema& schema) {
258 return stringify(DynamicStruct::Reader(Schema(&schema).asStruct(), reader));
259}
260
261kj::String enumString(uint16_t value, const RawBrandedSchema& schema) {
262 auto enumerants = Schema(&schema).asEnum().getEnumerants();
263 if (value < enumerants.size()) {
264 return kj::heapString(enumerants[value].getProto().getName());
265 } else {
266 return kj::str(value);
267 }
268}
269
270} // namespace _ (private)
271
272} // namespace capnp
273