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}