1#include "include/pwlib/json.h"
2
3#include <libpussy/alignment.h>
4
5
6// forward declaration
7[[nodiscard]] static bool value_to_json(PwValuePtr value, unsigned indent, unsigned depth,
8 PwValuePtr result, PwMethod_Append_append_string_data* meth_append);
9
10static uint8_t _S_NULL[4] = "null";
11static uint8_t _S_TRUE[4] = "true";
12static uint8_t _S_FALSE[5] = "false";
13static uint8_t _S_QUOTE[1] = "\"";
14static uint8_t _S_COMMA[1] = ",";
15static uint8_t _S_COLON[1] = ":";
16static uint8_t _S_SPACE[1] = " ";
17
18static uint8_t _S_B[2] = "\\b";
19static uint8_t _S_F[2] = "\\f";
20static uint8_t _S_N[2] = "\\n";
21static uint8_t _S_R[2] = "\\r";
22static uint8_t _S_T[2] = "\\t";
23
24static uint8_t _S_OPEN_SQUARE[1] = "[";
25static uint8_t _S_CLOSE_SQUARE[1] = "]";
26static uint8_t _S_OPEN_CURLY[1] = "{";
27static uint8_t _S_CLOSE_CURLY[1] = "}";
28
29#define END_PTR(s) (((uint8_t*) (s)) + sizeof(s))
30
31#define APPEND(result, start_ptr, end_ptr, char_size) \
32 ((meth_append)->func)((meth_append), (result), (start_ptr), (end_ptr), (char_size))
33
34#define APPEND_CHARS(s) \
35 do { \
36 if (!APPEND(result, (uint8_t*) (s), END_PTR(s), 1)) { \
37 return false; \
38 } \
39 } while (false)
40
41[[nodiscard]] static bool append_unescaped(PwValuePtr result, PwStringIter* iter,
42 uint8_t** scan_start, PwMethod_Append_append_string_data* meth_append)
43/*
44 * append characters to the result from scan_start up to prev iterator position as is, without escapes
45 */
46{
47 uint8_t* scan_end = iter->current_ptr - iter->char_size;
48 if (scan_end > *scan_start) {
49 if (!APPEND(result, *scan_start, scan_end, iter->char_size)) {
50 return false;
51 }
52 }
53 *scan_start = iter->current_ptr;
54 return true;
55}
56
57[[nodiscard]] static bool escape_string(PwValuePtr str, PwValuePtr result, PwMethod_Append_append_string_data* meth_append)
58/*
59 * Escape only double quotes and characters with codes < 32
60 */
61{
62 APPEND_CHARS(_S_QUOTE);
63
64 PwStringIter iter;
65 _pw_string_iter(str, &iter);
66
67 // avoid calling append method for each character, append a whole unescaped block at once;
68 // here's the start of the block
69 uint8_t* scan_start = iter.start_ptr;
70 char32_t c;
71 while (_pw_string_iter_next(&iter, &c)) {
72 if (c == '"' || c == '\\') {
73 if (!append_unescaped(result, &iter, &scan_start, meth_append)) {
74 return false;
75 }
76 // escape char
77 uint8_t s[2] = {'\\'};
78 s[1] = (uint8_t) c;
79 APPEND_CHARS(s);
80
81 } else if (c < 32) {
82 if (!append_unescaped(result, &iter, &scan_start, meth_append)) {
83 return false;
84 }
85 // escape char
86 switch (c) {
87 case '\b': APPEND_CHARS(_S_B); break;
88 case '\f': APPEND_CHARS(_S_F); break;
89 case '\n': APPEND_CHARS(_S_N); break;
90 case '\r': APPEND_CHARS(_S_R); break;
91 case '\t': APPEND_CHARS(_S_T); break;
92 default: {
93 uint8_t s[5] = "\\0000";
94 s[3] += (c >> 4);
95 s[4] += (c & 15);
96 APPEND_CHARS(s);
97 }
98 }
99 }
100 }
101 iter.current_ptr += iter.char_size; // append_unescaped expects this
102 if (!append_unescaped(result, &iter, &scan_start, meth_append)) {
103 return false;
104 }
105 APPEND_CHARS(_S_QUOTE);
106 return true;
107}
108
109[[nodiscard]] static bool array_to_json(PwValuePtr value, unsigned indent, unsigned depth,
110 PwValuePtr result, PwMethod_Append_append_string_data* meth_append)
111{
112 unsigned num_items = pw_array_length(value);
113
114 APPEND_CHARS(_S_OPEN_SQUARE);
115 if (num_items == 0) {
116 APPEND_CHARS(_S_CLOSE_SQUARE);
117 return true;
118 }
119 unsigned indent_width = indent * depth;
120 uint8_t indent_str[indent_width + 1];
121 indent_str[0] = '\n';
122 memset(&indent_str[1], ' ', indent_width);
123
124 bool multiline = indent && num_items > 1;
125 for (unsigned i = 0; i < num_items; i++) {
126 if (i) {
127 APPEND_CHARS(_S_COMMA);
128 }
129 if (multiline) {
130 APPEND_CHARS(indent_str);
131 }
132 PwValue item = PW_NULL;
133 if (!pw_array_item(value, i, &item)) {
134 return false;
135 }
136 if (!value_to_json(&item, indent, depth + multiline, result, meth_append)) {
137 return false;
138 }
139 }
140 if (multiline) {
141 // dedent closing brace
142 if (!APPEND(result, indent_str, indent_str + indent * (depth - 1) + 1, 1)) {
143 return false;
144 }
145 }
146 APPEND_CHARS(_S_CLOSE_SQUARE);
147 return true;
148}
149
150[[nodiscard]] static bool map_to_json(PwValuePtr value, unsigned indent, unsigned depth,
151 PwValuePtr result, PwMethod_Append_append_string_data* meth_append)
152{
153 unsigned num_items = pw_map_length(value);
154
155 APPEND_CHARS(_S_OPEN_CURLY);
156 if (num_items == 0) {
157 APPEND_CHARS(_S_CLOSE_CURLY);
158 return true;
159 }
160 unsigned indent_width = indent * depth;
161 uint8_t indent_str[indent_width + 1];
162 indent_str[0] = '\n';
163 memset(&indent_str[1], ' ', indent_width);
164
165 bool multiline = indent && num_items > 1;
166 for (unsigned i = 0; i < num_items; i++) {
167 PwValue k = PW_NULL;
168 PwValue v = PW_NULL;
169 if (!pw_map_item(value, i, &k, &v)) {
170 return false;
171 }
172 if (i) {
173 APPEND_CHARS(_S_COMMA);
174 }
175 if (multiline) {
176 APPEND_CHARS(indent_str);
177 }
178 if (!escape_string(&k, result, meth_append)) {
179 return false;
180 }
181 APPEND_CHARS(_S_COLON);
182 if (indent) {
183 APPEND_CHARS(_S_SPACE);
184 }
185 if (!value_to_json(&v, indent, depth + multiline, result, meth_append)) {
186 return false;
187 }
188 }
189 if (multiline) {
190 // dedent closing brace
191 if (!APPEND(result, indent_str, indent_str + indent * (depth - 1) + 1, 1)) {
192 return false;
193 }
194 }
195 APPEND_CHARS(_S_CLOSE_CURLY);
196 return true;
197}
198
199[[nodiscard]] static bool value_to_json(PwValuePtr value, unsigned indent, unsigned depth,
200 PwValuePtr result, PwMethod_Append_append_string_data* meth_append)
201/*
202 * Append serialized value to `result`.
203 *
204 * Return status.
205 */
206{
207 if (pw_is_null(value)) {
208 APPEND_CHARS(_S_NULL);
209 return true;\
210 }
211 if (pw_is_bool(value)) {
212 if (value->bool_value) {
213 APPEND_CHARS(_S_TRUE);
214 } else {
215 APPEND_CHARS(_S_FALSE);
216 }
217 return true;
218 }
219 if (pw_is_signed(value)) {
220 PwValue s = PW_NULL;
221 return pw_aprintf(result, meth_append, "%zd", value->signed_value);
222 }
223 if (pw_is_unsigned(value)) {
224 return pw_aprintf(result, meth_append, "%zu", value->unsigned_value);
225 }
226 if (pw_is_float(value)) {
227 return pw_aprintf(result, meth_append, "%f", value->float_value);
228 }
229 if (pw_is_string(value)) {
230 return escape_string(value, result, meth_append);
231 }
232 if (pw_is_array(value)) {
233 return array_to_json(value, indent, depth, result, meth_append);
234 }
235 if (pw_is_map(value)) {
236 return map_to_json(value, indent, depth, result, meth_append);
237 }
238 pw_set_status(PwStatus(PweIncompatibleType));
239 return false;
240}
241
242[[nodiscard]] bool pw_to_json(PwValuePtr value, unsigned indent, PwValuePtr result)
243{
244 PwValue string = PW_NULL;
245 PwValuePtr output;
246 if (pw_is_null(result)) {
247 // force allocator to allocate one page for string to have plenty of space for appending
248 if (!pw_create_empty_string(sys_page_size() - 2 * sizeof(_PwStringData), 1, &string)) {
249 return false;
250 }
251 output = &string;
252 } else {
253 output = result;
254 }
255
256 PwMethod_Append_append_string_data* meth_append;
257 if (!pw_method(output->type_id, Append, append_string_data, &meth_append)) {
258 return false;
259 }
260 if (!value_to_json(value, indent, 1, output, meth_append)) {
261 return false;
262 }
263 if (pw_is_null(result)) {
264 // if resulting string is smaller than half of page size, reallocate
265 if (string.length * string.str_params.char_size < (sys_page_size() >> 1)) {
266 return pw_deepcopy(result, &string);
267 }
268 pw_move(result, &string);
269 }
270 return true;
271}