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}