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 escape_string(PwValuePtr str, PwValuePtr result, PwMethod_Append_append_string_data* meth_append)
42/*
43 * Escape only double quotes and characters with codes < 32
44 */
45{
46 APPEND_CHARS(_S_QUOTE);
47
48 uint8_t* end_ptr;
49 uint8_t* start_ptr = _pw_string_start_end(str, &end_ptr);
50 uint8_t* ptr = start_ptr;
51 uint8_t char_size = str->char_size;
52
53 while (ptr < end_ptr) {
54 char32_t c = _pw_get_char(ptr, char_size);
55 if (c == '"' || c == '\\') {
56 if (ptr > start_ptr) {
57 if (!APPEND(result, start_ptr, ptr, char_size)) { return false; }
58 }
59 start_ptr = ptr + char_size;
60
61 uint8_t s[2] = {'\\'};
62 s[1] = (uint8_t) c;
63 APPEND_CHARS(s);
64
65 } else if (c < 32) {
66 if (ptr > start_ptr) {
67 if (!APPEND(result, start_ptr, ptr, char_size)) { return false; }
68 }
69 start_ptr = ptr + char_size;
70
71 switch (c) {
72 case '\b': APPEND_CHARS(_S_B); break;
73 case '\f': APPEND_CHARS(_S_F); break;
74 case '\n': APPEND_CHARS(_S_N); break;
75 case '\r': APPEND_CHARS(_S_R); break;
76 case '\t': APPEND_CHARS(_S_T); break;
77 default: {
78 uint8_t s[5] = "\\0000";
79 s[3] += (c >> 4);
80 s[4] += (c & 15);
81 APPEND_CHARS(s);
82 }
83 }
84 }
85 ptr += char_size;
86 }
87 if (ptr > start_ptr) {
88 if (!APPEND(result, start_ptr, ptr, char_size)) { return false; }
89 }
90 APPEND_CHARS(_S_QUOTE);
91 return true;
92}
93
94[[nodiscard]] static bool array_to_json(PwValuePtr value, unsigned indent, unsigned depth,
95 PwValuePtr result, PwMethod_Append_append_string_data* meth_append)
96{
97 unsigned num_items = pw_array_length(value);
98
99 APPEND_CHARS(_S_OPEN_SQUARE);
100 if (num_items == 0) {
101 APPEND_CHARS(_S_CLOSE_SQUARE);
102 return true;
103 }
104 unsigned indent_width = indent * depth;
105 uint8_t indent_str[indent_width + 1];
106 indent_str[0] = '\n';
107 memset(&indent_str[1], ' ', indent_width);
108
109 bool multiline = indent && num_items > 1;
110 for (unsigned i = 0; i < num_items; i++) {{
111 if (i) {
112 APPEND_CHARS(_S_COMMA);
113 }
114 if (multiline) {
115 APPEND_CHARS(indent_str);
116 }
117 PwValue item = PW_NULL;
118 if (!pw_array_item(value, i, &item)) {
119 return false;
120 }
121 if (!value_to_json(&item, indent, depth + multiline, result, meth_append)) {
122 return false;
123 }
124 }}
125 if (multiline) {
126 // dedent closing brace
127 if (!APPEND(result, indent_str, indent_str + indent * (depth - 1) + 1, 1)) {
128 return false;
129 }
130 }
131 APPEND_CHARS(_S_CLOSE_SQUARE);
132 return true;
133}
134
135[[nodiscard]] static bool map_to_json(PwValuePtr value, unsigned indent, unsigned depth,
136 PwValuePtr result, PwMethod_Append_append_string_data* meth_append)
137{
138 unsigned num_items = pw_map_length(value);
139
140 APPEND_CHARS(_S_OPEN_CURLY);
141 if (num_items == 0) {
142 APPEND_CHARS(_S_CLOSE_CURLY);
143 return true;
144 }
145 unsigned indent_width = indent * depth;
146 uint8_t indent_str[indent_width + 1];
147 indent_str[0] = '\n';
148 memset(&indent_str[1], ' ', indent_width);
149
150 bool multiline = indent && num_items > 1;
151 for (unsigned i = 0; i < num_items; i++) {{
152 PwValue k = PW_NULL;
153 PwValue v = PW_NULL;
154 if (!pw_map_item(value, i, &k, &v)) {
155 return false;
156 }
157 if (i) {
158 APPEND_CHARS(_S_COMMA);
159 }
160 if (multiline) {
161 APPEND_CHARS(indent_str);
162 }
163 if (!escape_string(&k, result, meth_append)) {
164 return false;
165 }
166 APPEND_CHARS(_S_COLON);
167 if (indent) {
168 APPEND_CHARS(_S_SPACE);
169 }
170 if (!value_to_json(&v, indent, depth + multiline, result, meth_append)) {
171 return false;
172 }
173 }}
174 if (multiline) {
175 // dedent closing brace
176 if (!APPEND(result, indent_str, indent_str + indent * (depth - 1) + 1, 1)) {
177 return false;
178 }
179 }
180 APPEND_CHARS(_S_CLOSE_CURLY);
181 return true;
182}
183
184[[nodiscard]] static bool append_printf(PwValuePtr result, PwMethod_Append_append_string_data* meth_append, char* format, ...)
185{
186 uint8_t buffer[512];
187 va_list ap;
188 va_start(ap);
189 int n = vsnprintf((char*) buffer, sizeof(buffer), format, ap);
190 va_end(ap);
191 if (n < 0 || n >= (int) sizeof(buffer)) {
192 pw_set_status(PwStatus(PW_ERROR));
193 return false;
194 }
195 return APPEND(result, buffer, buffer + n, 1);
196}
197
198[[nodiscard]] static bool value_to_json(PwValuePtr value, unsigned indent, unsigned depth,
199 PwValuePtr result, PwMethod_Append_append_string_data* meth_append)
200/*
201 * Append serialized value to `result`.
202 *
203 * Return status.
204 */
205{
206 if (pw_is_null(value)) {
207 APPEND_CHARS(_S_NULL);
208 return true;\
209 }
210 if (pw_is_bool(value)) {
211 if (value->bool_value) {
212 APPEND_CHARS(_S_TRUE);
213 } else {
214 APPEND_CHARS(_S_FALSE);
215 }
216 return true;
217 }
218 if (pw_is_signed(value)) {
219 PwValue s = PW_NULL;
220 return append_printf(result, meth_append, "%zd", value->signed_value);
221 }
222 if (pw_is_unsigned(value)) {
223 return append_printf(result, meth_append, "%zu", value->unsigned_value);
224 }
225 if (pw_is_float(value)) {
226 return append_printf(result, meth_append, "%f", value->float_value);
227 }
228 if (pw_is_string(value)) {
229 return escape_string(value, result, meth_append);
230 }
231 if (pw_is_array(value)) {
232 return array_to_json(value, indent, depth, result, meth_append);
233 }
234 if (pw_is_map(value)) {
235 return map_to_json(value, indent, depth, result, meth_append);
236 }
237 pw_set_status(PwStatus(PW_ERROR_INCOMPATIBLE_TYPE));
238 return false;
239}
240
241[[nodiscard]] bool pw_to_json(PwValuePtr value, unsigned indent, PwValuePtr result)
242{
243 PwValue string = PW_NULL;
244 PwValuePtr output;
245 if (pw_is_null(result)) {
246 // force allocator to allocate one page for string to have plenty of space for appending
247 if (!pw_create_empty_string(sys_page_size() - 2 * sizeof(_PwStringData), 1, &string)) {
248 return false;
249 }
250 output = &string;
251 } else {
252 output = result;
253 }
254
255 PwMethod_Append_append_string_data* meth_append;
256 if (!pw_method(output->type_id, Append, append_string_data, &meth_append)) {
257 return false;
258 }
259 if (!value_to_json(value, indent, 1, output, meth_append)) {
260 return false;
261 }
262 if (pw_is_null(result)) {
263 // if resulting string is smaller than half of page size, reallocate
264 if (string.length * string.char_size < (sys_page_size() >> 1)) {
265 return pw_deepcopy(&string, result);
266 }
267 pw_move(&string, result);
268 }
269 return true;
270}