MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <stdint.h>
3#include <string.h>
4#include <math.h>
5#include <yyjson.h>
6#include <uthash.h>
7
8#include "gc/roots.h"
9#include "utf8.h"
10#include "errors.h"
11#include "runtime.h"
12#include "internal.h"
13
14#include "silver/engine.h"
15#include "modules/json.h"
16#include "modules/symbol.h"
17
18typedef struct {
19 const char *key;
20 size_t key_len;
21 ant_offset_t prop_off;
22 UT_hash_handle hh;
23} json_key_entry_t;
24
25static inline bool json_value_needs_temp_root(ant_value_t value) {
26 if (value <= NANBOX_PREFIX) return false;
27
28 static const uint32_t mask =
29 (1u << T_STR) | (1u << T_OBJ) | (1u << T_ARR) | (1u << T_FUNC) |
30 (1u << T_PROMISE) | (1u << T_GENERATOR) | (1u << T_SYMBOL) | (1u << T_BIGINT);
31
32 uint8_t t = vtype(value);
33 return t < 32 && (mask >> t) & 1;
34}
35
36static inline bool json_temp_pin(gc_temp_root_scope_t *roots, ant_value_t value) {
37 if (!json_value_needs_temp_root(value)) return true;
38 return gc_temp_root_handle_valid(gc_temp_root_add(roots, value));
39}
40
41static inline ant_value_t json_parse_oom(ant_t *js) {
42 return js_mkerr(js, "JSON.parse() failed: out of memory");
43}
44
45static inline ant_value_t json_stringify_oom(ant_t *js) {
46 return js_mkerr(js, "JSON.stringify() failed: out of memory");
47}
48
49static ant_value_t yyjson_to_jsval(ant_t *js, yyjson_val *val, gc_temp_root_scope_t *roots) {
50 if (!val) return js_mkundef();
51
52 switch (yyjson_get_type(val)) {
53 case YYJSON_TYPE_NULL: return js_mknull();
54 case YYJSON_TYPE_BOOL: return js_bool(yyjson_get_bool(val));
55
56 case YYJSON_TYPE_STR: {
57 ant_value_t str = js_mkstr(js, yyjson_get_str(val), yyjson_get_len(val));
58 if (is_err(str)) return str;
59 if (!json_temp_pin(roots, str)) return json_parse_oom(js);
60 return str;
61 }
62
63 case YYJSON_TYPE_NUM: {
64 if (yyjson_is_sint(val)) return js_mknum((double)yyjson_get_sint(val));
65 if (yyjson_is_uint(val)) return js_mknum((double)yyjson_get_uint(val));
66 return js_mknum(yyjson_get_real(val));
67 }
68
69 case YYJSON_TYPE_ARR: {
70 ant_value_t arr = js_mkarr(js);
71 if (is_err(arr)) return arr;
72 if (!json_temp_pin(roots, arr)) return json_parse_oom(js);
73 size_t idx, max;
74 yyjson_val *item;
75
76 yyjson_arr_foreach(val, idx, max, item) {
77 ant_value_t elem = yyjson_to_jsval(js, item, roots);
78 if (is_err(elem)) return elem;
79 js_arr_push(js, arr, elem);
80 }
81
82 return arr;
83 }
84
85 case YYJSON_TYPE_OBJ: {
86 ant_value_t obj = js_newobj(js);
87 if (is_err(obj)) return obj;
88 if (!json_temp_pin(roots, obj)) return json_parse_oom(js);
89
90 size_t idx, max; yyjson_val *key, *item;
91 json_key_entry_t *hash = NULL, *entry, *tmp;
92
93 yyjson_obj_foreach(val, idx, max, key, item) {
94 const char *k = yyjson_get_str(key);
95
96 size_t klen = yyjson_get_len(key);
97 ant_value_t v = yyjson_to_jsval(js, item, roots);
98 if (is_err(v)) {
99 HASH_ITER(hh, hash, entry, tmp)
100 HASH_DEL(hash, entry); free(entry);
101 return v;
102 }
103
104 HASH_FIND(hh, hash, k, klen, entry);
105 if (entry) js_saveval(js, entry->prop_off, v); else {
106 ant_offset_t off = js_mkprop_fast_off(js, obj, k, klen, v);
107 if (off == 0) {
108 HASH_ITER(hh, hash, entry, tmp)
109 HASH_DEL(hash, entry); free(entry);
110 return json_parse_oom(js);
111 }
112 entry = malloc(sizeof(json_key_entry_t));
113 if (!entry) {
114 HASH_ITER(hh, hash, entry, tmp)
115 HASH_DEL(hash, entry); free(entry);
116 return json_parse_oom(js);
117 }
118 entry->key = k; entry->key_len = klen; entry->prop_off = off;
119 HASH_ADD_KEYPTR(hh, hash, entry->key, entry->key_len, entry);
120 }}
121
122 HASH_ITER(hh, hash, entry, tmp)
123 HASH_DEL(hash, entry); free(entry);
124
125 return obj;
126 }
127
128 default: return js_mkundef(); }
129}
130
131typedef struct {
132 ant_t *js;
133 ant_value_t *stack;
134 ant_value_t replacer_func;
135 ant_value_t replacer_arr;
136 ant_value_t error;
137 ant_value_t holder;
138
139 gc_temp_root_scope_t temp_roots;
140 gc_temp_root_handle_t error_handle;
141 gc_temp_root_handle_t holder_handle;
142
143 int stack_size;
144 int stack_cap;
145 int replacer_arr_len;
146 int has_cycle;
147} json_cycle_ctx;
148
149static inline bool json_has_abort(json_cycle_ctx *ctx) {
150 return ctx->has_cycle || vtype(ctx->error) != T_UNDEF;
151}
152
153static inline ant_value_t json_normalize_error(ant_value_t value) {
154 if (is_err(value) && vdata(value) != 0) return js_as_obj(value);
155 return value;
156}
157
158static void json_set_error(json_cycle_ctx *ctx, ant_value_t value) {
159 ctx->error = value;
160 gc_temp_root_set(ctx->error_handle, value);
161}
162
163static inline bool json_ctx_pin_value(json_cycle_ctx *ctx, ant_value_t value) {
164 if (json_temp_pin(&ctx->temp_roots, value)) return true;
165 json_set_error(ctx, json_stringify_oom(ctx->js));
166 return false;
167}
168
169static inline void json_set_holder(json_cycle_ctx *ctx, ant_value_t value) {
170 ctx->holder = value;
171 gc_temp_root_set(ctx->holder_handle, value);
172}
173
174static void json_capture_error(json_cycle_ctx *ctx, ant_value_t value) {
175 if (vtype(ctx->error) != T_UNDEF) return;
176 if (ctx->js->thrown_exists) {
177 json_set_error(ctx, ctx->js->thrown_value);
178 ctx->js->thrown_exists = false;
179 ctx->js->thrown_value = js_mkundef();
180 return;
181 }
182 json_set_error(ctx, json_normalize_error(value));
183}
184
185static yyjson_mut_val *json_string_to_yyjson(ant_t *js, yyjson_mut_doc *doc, ant_value_t value) {
186 size_t byte_len = 0;
187 char *str = js_getstr(js, value, &byte_len);
188 size_t raw_len = 0;
189 char *raw = utf8_json_quote(str, byte_len, &raw_len);
190 if (!raw) goto oom;
191 yyjson_mut_val *out = yyjson_mut_rawncpy(doc, raw, raw_len);
192 free(raw);
193 return out;
194
195oom:
196 free(raw);
197 return NULL;
198}
199
200static int json_cycle_check(json_cycle_ctx *ctx, ant_value_t val) {
201 for (int i = 0; i < ctx->stack_size; i++)
202 if (ctx->stack[i] == val) { ctx->has_cycle = 1; return 1; }
203 return 0;
204}
205
206static void json_cycle_push(json_cycle_ctx *ctx, ant_value_t val) {
207 if (ctx->stack_size >= ctx->stack_cap) {
208 ctx->stack_cap = ctx->stack_cap ? ctx->stack_cap * 2 : 16;
209 ctx->stack = realloc(ctx->stack, ctx->stack_cap * sizeof(ant_value_t));
210 }
211 ctx->stack[ctx->stack_size++] = val;
212}
213
214static inline void json_cycle_pop(json_cycle_ctx *ctx) {
215 if (ctx->stack_size > 0) ctx->stack_size--;
216}
217
218static inline int key_matches(const char *a, size_t a_len, const char *b, size_t b_len) {
219 return a_len == b_len && memcmp(a, b, a_len) == 0;
220}
221
222static inline bool json_is_array(ant_value_t value) {
223 return vtype(value) == T_ARR;
224}
225
226static inline ant_value_t json_snapshot_keys(ant_t *js, ant_value_t value) {
227 if (!is_special_object(value)) return js_mkarr(js);
228 return js_for_in_keys(js, value);
229}
230
231static int is_key_in_replacer_arr(ant_t *js, json_cycle_ctx *ctx, const char *key, size_t key_len) {
232 if (!is_special_object(ctx->replacer_arr)) return 1;
233
234 for (int i = 0; i < ctx->replacer_arr_len; i++) {
235 char idxstr[32];
236 snprintf(idxstr, sizeof(idxstr), "%d", i);
237
238 ant_value_t item = js_get(js, ctx->replacer_arr, idxstr);
239 int type = vtype(item);
240
241 if (type == T_STR) {
242 size_t item_len;
243 char *item_str = js_getstr(js, item, &item_len);
244 if (key_matches(item_str, item_len, key, key_len)) return 1;
245 } else if (type == T_NUM) {
246 char numstr[32];
247 snprintf(numstr, sizeof(numstr), "%.0f", js_getnum(item));
248 if (key_matches(numstr, strlen(numstr), key, key_len)) return 1;
249 }}
250
251 return 0;
252}
253
254static yyjson_mut_val *ant_value_to_yyjson_with_key(
255 ant_t *js, yyjson_mut_doc *doc, const char *key,
256 ant_value_t val, json_cycle_ctx *ctx, int in_array
257);
258
259static ant_value_t apply_reviver(
260 ant_t *js, ant_value_t holder,
261 const char *key, ant_value_t reviver,
262 gc_temp_root_scope_t *roots
263);
264
265static ant_value_t json_apply_tojson(
266 ant_t *js,
267 const char *key,
268 ant_value_t val,
269 json_cycle_ctx *ctx
270) {
271 if (!is_special_object(val)) return val;
272 ant_value_t toJSON = js_get(js, val, "toJSON");
273
274 if (is_err(toJSON)) {
275 json_capture_error(ctx, toJSON);
276 return js_mkundef();
277 }
278
279 if (!is_callable(toJSON)) return val;
280 ant_value_t key_arg = js_mkstr(js, key, strlen(key));
281 if (is_err(key_arg)) {
282 json_capture_error(ctx, key_arg);
283 return js_mkundef();
284 }
285
286 if (!json_ctx_pin_value(ctx, key_arg)) return js_mkundef();
287 ant_value_t args[1] = { key_arg };
288
289 ant_value_t transformed = sv_vm_call(
290 js->vm, js,
291 toJSON, val,
292 args, 1, NULL, false
293 );
294
295 if (is_err(transformed)) {
296 json_capture_error(ctx, transformed);
297 return js_mkundef();
298 }
299 if (!json_ctx_pin_value(ctx, transformed)) return js_mkundef();
300
301 return transformed;
302}
303
304static ant_value_t json_apply_replacer(
305 ant_t *js,
306 const char *key,
307 ant_value_t val,
308 json_cycle_ctx *ctx
309) {
310 if (!is_callable(ctx->replacer_func)) return val;
311 ant_value_t key_arg = js_mkstr(js, key, strlen(key));
312 if (is_err(key_arg)) {
313 json_capture_error(ctx, key_arg);
314 return js_mkundef();
315 }
316 if (!json_ctx_pin_value(ctx, key_arg)) return js_mkundef();
317 ant_value_t args[2] = { key_arg, val };
318
319 ant_value_t transformed = sv_vm_call(
320 js->vm, js,
321 ctx->replacer_func, ctx->holder,
322 args, 2, NULL, false
323 );
324
325 if (is_err(transformed)) {
326 json_capture_error(ctx, transformed);
327 return js_mkundef();
328 }
329 if (!json_ctx_pin_value(ctx, transformed)) return js_mkundef();
330
331 return transformed;
332}
333
334static inline ant_value_t json_create_root_holder(ant_t *js, ant_value_t value, json_cycle_ctx *ctx) {
335 ant_value_t holder = js_mkobj(js);
336 if (is_err(holder)) return holder;
337 if (!json_ctx_pin_value(ctx, holder)) return js_mkundef();
338 js_set(js, holder, "", value);
339 return holder;
340}
341
342static yyjson_mut_val *json_array_to_yyjson(
343 ant_t *js, yyjson_mut_doc *doc, ant_value_t val, json_cycle_ctx *ctx
344) {
345 yyjson_mut_val *arr = yyjson_mut_arr(doc);
346 ant_offset_t length = js_arr_len(js, val);
347 ant_value_t saved_holder = ctx->holder;
348
349 json_set_holder(ctx, val);
350 for (ant_offset_t i = 0; i < length; i++) {
351 char idxstr[32];
352 uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i);
353 ant_value_t elem = js_arr_get(js, val, i);
354 yyjson_mut_val *item = ant_value_to_yyjson_with_key(js, doc, idxstr, elem, ctx, 1);
355 if (json_has_abort(ctx)) {
356 json_set_holder(ctx, saved_holder);
357 return NULL;
358 }
359 yyjson_mut_arr_add_val(arr, item);
360 }
361
362 json_set_holder(ctx, saved_holder);
363 return arr;
364}
365
366static yyjson_mut_val *json_object_to_yyjson(
367 ant_t *js, yyjson_mut_doc *doc, ant_value_t val, json_cycle_ctx *ctx
368) {
369 yyjson_mut_val *obj = yyjson_mut_obj(doc);
370 ant_value_t keys = json_snapshot_keys(js, val);
371 ant_value_t saved_holder = ctx->holder;
372
373 if (is_err(keys)) {
374 json_capture_error(ctx, keys);
375 return NULL;
376 }
377 if (!json_ctx_pin_value(ctx, keys)) return NULL;
378
379 json_set_holder(ctx, val);
380 ant_offset_t key_count = js_arr_len(js, keys);
381
382 for (ant_offset_t i = 0; i < key_count; i++) {
383 ant_value_t key_val = js_arr_get(js, keys, i);
384 size_t key_len = 0;
385 char *key = js_getstr(js, key_val, &key_len);
386
387 if (!key) continue;
388 if (!is_key_in_replacer_arr(js, ctx, key, key_len)) continue;
389
390 ant_value_t prop = js_get(js, val, key);
391 if (is_err(prop)) {
392 json_capture_error(ctx, prop);
393 json_set_holder(ctx, saved_holder);
394 return NULL;
395 }
396
397 yyjson_mut_val *jval = ant_value_to_yyjson_with_key(js, doc, key, prop, ctx, 0);
398 if (json_has_abort(ctx)) {
399 json_set_holder(ctx, saved_holder);
400 return NULL;
401 }
402
403 if (jval == YYJSON_SKIP_VALUE) continue;
404 yyjson_mut_obj_add(obj, yyjson_mut_strncpy(doc, key, key_len), jval);
405 }
406
407 json_set_holder(ctx, saved_holder);
408 return obj;
409}
410
411static yyjson_mut_val *ant_value_to_yyjson_impl(ant_t *js, yyjson_mut_doc *doc, ant_value_t val, json_cycle_ctx *ctx, int in_array) {
412 int type = vtype(val);
413 yyjson_mut_val *result = NULL;
414
415 switch (type) {
416 case T_NULL: return yyjson_mut_null(doc);
417 case T_BOOL: return yyjson_mut_bool(doc, val == js_true);
418
419 case T_UNDEF: return in_array ? yyjson_mut_null(doc) : YYJSON_SKIP_VALUE;
420 case T_FUNC: return in_array ? yyjson_mut_null(doc) : YYJSON_SKIP_VALUE;
421 case T_SYMBOL: return in_array ? yyjson_mut_null(doc) : YYJSON_SKIP_VALUE;
422
423 case T_NUM: {
424 double num = js_getnum(val);
425 if (isnan(num) || isinf(num)) return yyjson_mut_null(doc);
426 if (
427 num >= (double)INT64_MIN &&
428 num < (double)INT64_MAX &&
429 num == (double)(int64_t)num
430 ) return yyjson_mut_sint(doc, (int64_t)num);
431 return yyjson_mut_real(doc, num);
432 }
433
434 case T_STR: {
435 return json_string_to_yyjson(js, doc, val);
436 }
437
438 case T_OBJ:
439 case T_ARR: break;
440 default: return yyjson_mut_null(doc);
441 }
442
443 if (json_cycle_check(ctx, val)) return NULL;
444 json_cycle_push(ctx, val);
445
446 result = json_is_array(val)
447 ? json_array_to_yyjson(js, doc, val, ctx)
448 : json_object_to_yyjson(js, doc, val, ctx);
449
450 json_cycle_pop(ctx);
451 return result;
452}
453
454static yyjson_mut_val *ant_value_to_yyjson_with_key(
455 ant_t *js, yyjson_mut_doc *doc, const char *key,
456 ant_value_t val, json_cycle_ctx *ctx, int in_array
457) {
458 val = json_apply_tojson(js, key, val, ctx);
459 if (json_has_abort(ctx)) return NULL;
460
461 val = json_apply_replacer(js, key, val, ctx);
462 if (json_has_abort(ctx)) return NULL;
463
464 return ant_value_to_yyjson_impl(js, doc, val, ctx, in_array);
465}
466
467static yyjson_mut_val *ant_value_to_yyjson(ant_t *js, yyjson_mut_doc *doc, ant_value_t val, json_cycle_ctx *ctx) {
468 return ant_value_to_yyjson_with_key(js, doc, "", val, ctx, 0);
469}
470
471static ant_value_t apply_reviver_call(
472 ant_t *js,
473 ant_value_t holder,
474 const char *key,
475 ant_value_t reviver,
476 gc_temp_root_scope_t *roots
477) {
478 ant_value_t key_str = js_mkstr(js, key, strlen(key));
479 if (is_err(key_str)) return key_str;
480 if (!json_temp_pin(roots, key_str)) return json_parse_oom(js);
481 ant_value_t current_value = js_get(js, holder, key);
482 ant_value_t call_args[2] = { key_str, current_value };
483
484 ant_value_t result = sv_vm_call(
485 js->vm, js, reviver, holder,
486 call_args, 2, NULL, false
487 );
488 if (!is_err(result) && !json_temp_pin(roots, result)) return json_parse_oom(js);
489
490 return result;
491}
492
493static void apply_reviver_to_array(
494 ant_t *js,
495 ant_value_t value,
496 ant_value_t reviver,
497 gc_temp_root_scope_t *roots
498) {
499 ant_offset_t length = js_arr_len(js, value);
500
501 for (ant_offset_t i = 0; i < length; i++) {
502 char idxstr[32];
503 size_t idx_len = uint_to_str(idxstr, sizeof(idxstr), (uint64_t)i);
504 ant_value_t new_elem = apply_reviver(js, value, idxstr, reviver, roots);
505 if (vtype(new_elem) == T_UNDEF) js_delete_prop(js, value, idxstr, idx_len);
506 else {
507 ant_value_t key_val = js_mkstr(js, idxstr, idx_len);
508 if (is_err(key_val)) return;
509 if (!json_temp_pin(roots, key_val)) return;
510 js_setprop(js, value, key_val, new_elem);
511 }}
512}
513
514static void apply_reviver_to_object(
515 ant_t *js,
516 ant_value_t value,
517 ant_value_t reviver,
518 gc_temp_root_scope_t *roots
519) {
520 ant_value_t keys = json_snapshot_keys(js, value);
521 if (is_err(keys) || vtype(keys) != T_ARR) return;
522 if (!json_temp_pin(roots, keys)) return;
523
524 ant_offset_t key_count = js_arr_len(js, keys);
525 for (ant_offset_t i = 0; i < key_count; i++) {
526 ant_value_t key_val = js_arr_get(js, keys, i);
527 size_t key_len = 0;
528 char *key = js_getstr(js, key_val, &key_len);
529 if (!key) continue;
530 ant_value_t new_val = apply_reviver(js, value, key, reviver, roots);
531 if (vtype(new_val) == T_UNDEF) js_delete_prop(js, value, key, key_len);
532 else js_set(js, value, key, new_val);
533 }
534}
535
536static ant_value_t apply_reviver(
537 ant_t *js,
538 ant_value_t holder,
539 const char *key,
540 ant_value_t reviver,
541 gc_temp_root_scope_t *roots
542) {
543 ant_value_t val = js_get(js, holder, key);
544
545 if (json_is_array(val)) apply_reviver_to_array(js, val, reviver, roots);
546 else if (vtype(val) == T_OBJ) apply_reviver_to_object(js, val, reviver, roots);
547
548 return apply_reviver_call(js, holder, key, reviver, roots);
549}
550
551ant_value_t js_json_parse(ant_t *js, ant_value_t *args, int nargs) {
552 if (nargs < 1) return js_mkerr(js, "JSON.parse() requires at least 1 argument");
553 if (vtype(args[0]) != T_STR) return js_mkerr(js, "JSON.parse() argument must be a string");
554 gc_temp_root_scope_t temp_roots;
555 gc_temp_root_scope_begin(js, &temp_roots);
556
557 size_t len;
558 char *json_str = js_getstr(js, args[0], &len);
559
560 yyjson_doc *doc = yyjson_read(json_str, len, 0);
561
562 if (!doc) {
563 gc_temp_root_scope_end(&temp_roots);
564 return js_mkerr_typed(js, JS_ERR_SYNTAX, "JSON.parse: unexpected character");
565 }
566
567 ant_value_t result = yyjson_to_jsval(js, yyjson_doc_get_root(doc), &temp_roots);
568 yyjson_doc_free(doc);
569 if (is_err(result)) {
570 gc_temp_root_scope_end(&temp_roots);
571 return result;
572 }
573
574 if (nargs >= 2 && is_callable(args[1])) {
575 ant_value_t reviver = args[1];
576 if (!json_temp_pin(&temp_roots, reviver)) {
577 gc_temp_root_scope_end(&temp_roots);
578 return json_parse_oom(js);
579 }
580 ant_value_t root = js_mkobj(js);
581 if (is_err(root)) {
582 gc_temp_root_scope_end(&temp_roots);
583 return root;
584 }
585 if (!json_temp_pin(&temp_roots, root)) {
586 gc_temp_root_scope_end(&temp_roots);
587 return json_parse_oom(js);
588 }
589 js_set(js, root, "", result);
590 result = apply_reviver(js, root, "", reviver, &temp_roots);
591 }
592
593 gc_temp_root_scope_end(&temp_roots);
594 return result;
595}
596
597ant_value_t json_parse_value(ant_t *js, ant_value_t value) {
598 ant_value_t args[1] = { value };
599 return js_json_parse(js, args, 1);
600}
601
602static yyjson_write_flag get_write_flags(ant_value_t *args, int nargs) {
603 if (nargs < 3) return 0;
604
605 int type = vtype(args[2]);
606 if (type == T_UNDEF || type == T_NULL) return 0;
607 if (type != T_NUM) return YYJSON_WRITE_PRETTY;
608
609 int indent = (int)js_getnum(args[2]);
610 if (indent <= 0) return 0;
611 if (indent == 2) return YYJSON_WRITE_PRETTY_TWO_SPACES;
612
613 return YYJSON_WRITE_PRETTY;
614}
615
616ant_value_t js_json_stringify(ant_t *js, ant_value_t *args, int nargs) {
617 ant_value_t result;
618 yyjson_mut_doc *doc = NULL;
619
620 json_cycle_ctx ctx = {
621 .js = js,
622 .replacer_func = js_mkundef(),
623 .replacer_arr = js_mkundef(),
624 .error = js_mkundef(),
625 .holder = js_mkundef(),
626 };
627
628 char *json_str = NULL;
629 size_t len;
630 ant_value_t root_holder = js_mkundef();
631
632 if (nargs < 1) return js_mkerr(js, "JSON.stringify() requires at least 1 argument");
633 gc_temp_root_scope_begin(js, &ctx.temp_roots);
634 ctx.error_handle = gc_temp_root_add(&ctx.temp_roots, ctx.error);
635 ctx.holder_handle = gc_temp_root_add(&ctx.temp_roots, ctx.holder);
636
637 if (!gc_temp_root_handle_valid(ctx.error_handle) || !gc_temp_root_handle_valid(ctx.holder_handle)) {
638 gc_temp_root_scope_end(&ctx.temp_roots);
639 return json_stringify_oom(js);
640 }
641
642 if (!json_ctx_pin_value(&ctx, args[0])) {
643 result = ctx.error;
644 goto cleanup;
645 }
646
647 int top_type = vtype(args[0]);
648
649 if (nargs < 2 && top_type == T_STR) {
650 size_t byte_len = 0;
651 size_t raw_len = 0;
652
653 char *str = js_getstr(js, args[0], &byte_len);
654 char *raw = utf8_json_quote(str, byte_len, &raw_len);
655
656 if (!raw) {
657 result = js_mkerr(js, "JSON.stringify() failed: out of memory");
658 goto cleanup;
659 }
660 result = js_mkstr(js, raw, raw_len);
661 free(raw);
662 goto cleanup;
663 }
664
665 if (nargs >= 2) {
666 ant_value_t replacer = args[1];
667 if (is_callable(replacer)) {
668 ctx.replacer_func = replacer;
669 if (!json_ctx_pin_value(&ctx, replacer)) {
670 result = ctx.error;
671 goto cleanup;
672 }}
673
674 else if (is_special_object(replacer)) {
675 ant_value_t len_val = js_get(js, replacer, "length");
676
677 if (vtype(len_val) == T_NUM) {
678 ctx.replacer_arr = replacer;
679 ctx.replacer_arr_len = (int)js_getnum(len_val);
680 if (!json_ctx_pin_value(&ctx, replacer)) {
681 result = ctx.error;
682 goto cleanup;
683 }
684 }}}
685
686 doc = yyjson_mut_doc_new(NULL);
687 if (!doc) {
688 result = js_mkerr(js, "JSON.stringify() failed: out of memory");
689 goto cleanup;
690 }
691
692 root_holder = json_create_root_holder(js, args[0], &ctx);
693 if (is_err(root_holder)) {
694 result = root_holder;
695 goto cleanup;
696 }
697
698 if (vtype(root_holder) == T_UNDEF && vtype(ctx.error) != T_UNDEF) {
699 result = ctx.error;
700 goto cleanup;
701 }
702
703 json_set_holder(&ctx, root_holder);
704 yyjson_mut_val *root = ant_value_to_yyjson(js, doc, args[0], &ctx);
705
706 if (vtype(ctx.error) != T_UNDEF) {
707 ant_value_t error = json_normalize_error(ctx.error);
708 result = is_err(error) ? error : js_throw(js, error);
709 goto cleanup;
710 }
711
712 if (ctx.has_cycle) {
713 result = js_mkerr_typed(js, JS_ERR_TYPE, "Converting circular structure to JSON");
714 goto cleanup;
715 }
716
717 if (root == YYJSON_SKIP_VALUE) {
718 result = js_mkundef();
719 goto cleanup;
720 }
721
722 yyjson_mut_doc_set_root(doc, root);
723 json_str = yyjson_mut_write(doc, get_write_flags(args, nargs), &len);
724
725 if (!json_str) {
726 result = js_mkerr(js, "JSON.stringify() failed: write error");
727 goto cleanup;
728 }
729
730 result = js_mkstr(js, json_str, len);
731
732cleanup:
733 free(json_str);
734 free(ctx.stack);
735 yyjson_mut_doc_free(doc);
736 gc_temp_root_scope_end(&ctx.temp_roots);
737 return result;
738}
739
740ant_value_t json_stringify_value(ant_t *js, ant_value_t value) {
741 ant_value_t args[1] = { value };
742 return js_json_stringify(js, args, 1);
743}
744
745void init_json_module() {
746 ant_t *js = rt->js;
747 ant_value_t json_obj = js_mkobj(js);
748
749 js_set(js, json_obj, "parse", js_mkfun(js_json_parse));
750 js_set(js, json_obj, "stringify", js_mkfun(js_json_stringify));
751
752 js_set_sym(js, json_obj, get_toStringTag_sym(), js_mkstr(js, "JSON", 4));
753 js_set(js, js_glob(js), "JSON", json_obj);
754}