yeptris 0.6.58.1-arm-linux-eabihf → 0.6.60.1-arm-linux-eabihf

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/ext/yeptris_native/version.h +2 -2
  3. data/lib/yeptris/3.4/native.so +0 -0
  4. data/lib/yeptris/document.rb +15 -0
  5. data/lib/yeptris/ffi.rb +112 -0
  6. data/lib/yeptris/psych/visitors.rb +16 -6
  7. data/lib/yeptris/psych.rb +11 -5
  8. data/lib/yeptris/validation.rb +320 -0
  9. data/lib/yeptris/yaml.rb +128 -6
  10. data/lib/yeptris.rb +2 -1
  11. data/libyeptris.so +0 -0
  12. data/vendor/libyeptris/CMakeLists.txt +1 -1
  13. data/vendor/libyeptris/src/CMakeLists.txt +8 -0
  14. data/vendor/libyeptris/src/include/yeptris/emit.h +30 -2
  15. data/vendor/libyeptris/src/include/yeptris/parse.h +52 -0
  16. data/vendor/libyeptris/src/include/yeptris/resolve.h +8 -0
  17. data/vendor/libyeptris/src/include/yeptris/validate.h +259 -15
  18. data/vendor/libyeptris/src/include/yeptris/values.h +8 -2
  19. data/vendor/libyeptris/src/yeptris/cbor/cbor.h +10 -0
  20. data/vendor/libyeptris/src/yeptris/cbor/decode.c +5 -7
  21. data/vendor/libyeptris/src/yeptris/cbor/encode.c +9 -9
  22. data/vendor/libyeptris/src/yeptris/cbor/from_marshal.c +1 -1
  23. data/vendor/libyeptris/src/yeptris/common/error.h +2 -0
  24. data/vendor/libyeptris/src/yeptris/doc.h +20 -0
  25. data/vendor/libyeptris/src/yeptris/emit/emit.c +85 -13
  26. data/vendor/libyeptris/src/yeptris/emit/writer.c +158 -642
  27. data/vendor/libyeptris/src/yeptris/emit/writer.h +15 -1
  28. data/vendor/libyeptris/src/yeptris/events/push.c +28 -3
  29. data/vendor/libyeptris/src/yeptris/events/values.c +24 -0
  30. data/vendor/libyeptris/src/yeptris/marshal/to_json.c +650 -0
  31. data/vendor/libyeptris/src/yeptris/parse/engine.c +121 -40
  32. data/vendor/libyeptris/src/yeptris/parse/stream_iter.c +326 -0
  33. data/vendor/libyeptris/src/yeptris/parse.c +181 -46
  34. data/vendor/libyeptris/src/yeptris/resolve/compat11.c +1 -1
  35. data/vendor/libyeptris/src/yeptris/resolve/core12.c +11 -3
  36. data/vendor/libyeptris/src/yeptris/resolve/full12.c +201 -0
  37. data/vendor/libyeptris/src/yeptris/resolve/json12.c +12 -6
  38. data/vendor/libyeptris/src/yeptris/resolve/resolver.h +20 -0
  39. data/vendor/libyeptris/src/yeptris/validate/compile.c +306 -0
  40. data/vendor/libyeptris/src/yeptris/validate/internal.h +255 -10
  41. data/vendor/libyeptris/src/yeptris/validate/jsonschema.c +1025 -125
  42. data/vendor/libyeptris/src/yeptris/validate/output.c +246 -0
  43. data/vendor/libyeptris/src/yeptris/validate/regexp.c +791 -163
  44. data/vendor/libyeptris/src/yeptris/validate/registry.c +118 -0
  45. data/vendor/libyeptris/src/yeptris/validate/resources.c +601 -0
  46. data/vendor/libyeptris/src/yeptris/validate/stream.c +1155 -0
  47. data/vendor/libyeptris/src/yeptris/validate/validate.c +330 -226
  48. metadata +10 -1
@@ -0,0 +1,650 @@
1
+ /* to_json.c — the marshal READER: Ruby Marshal.dump bytes to JSON text
2
+ * in one C call (yeptris-ruby#322, TODO.impl/#904 item 5: the reader's
3
+ * MECE home — marshal-domain bytes live here, the JSON writer's
4
+ * primitives stay in emit/writer.c).
5
+ *
6
+ * No per-scalar crossings, no object reconstruction. The format facts
7
+ * mirror marshal.c's emission contract (empirically pinned against
8
+ * Ruby 3.4): w_long small form
9
+ * x==0 -> 0x00, |x| <= 122 -> sign*(|x|+5), size form tag +-k with
10
+ * LE payload (2^(8k) - m for negatives); strings carry 'I' ivar
11
+ * wrappers; arrays/hashes/strings/floats register for '@' links.
12
+ *
13
+ * '@' links MEMMOVE the registered object's emitted span — the first
14
+ * walk's bytes are the cache; a lookup miss is a cycle (JSON cannot
15
+ * express it, the binding falls back). No re-parse, no registration
16
+ * on links: the object table stays exactly Ruby's stream order.
17
+ *
18
+ * The JSON-irrelevant constructs ('o' objects, 'S' structs, 'u'
19
+ * user marshal, 'l' bignums beyond i64, non-finite floats) return
20
+ * UNSUPPORTED — the binding falls back to its per-node walk.
21
+ */
22
+
23
+ #include "emit/writer.h"
24
+
25
+ #include <math.h>
26
+ #include <stdatomic.h>
27
+ #include <stdio.h>
28
+ #include <stdlib.h>
29
+ #include <string.h>
30
+
31
+ #include "emit/float/api.h"
32
+
33
+ #include <yeptris/marshal.h>
34
+
35
+ typedef struct {
36
+ const char* p;
37
+ size_t len;
38
+ size_t at;
39
+ } mj_stream;
40
+
41
+ typedef struct {
42
+ const char* p;
43
+ uint32_t len;
44
+ } mj_sym;
45
+
46
+ typedef struct {
47
+ size_t pos; /* the stream position of the type byte */
48
+ size_t out_off; /* where the emitted span starts in w's buffer */
49
+ size_t out_len; /* the span's length */
50
+ } mj_obj;
51
+
52
+ typedef struct {
53
+ yep_writer* w;
54
+ mj_stream s;
55
+ mj_sym* syms;
56
+ size_t syms_n, syms_cap;
57
+ mj_obj* objs;
58
+ size_t objs_n, objs_cap;
59
+ int failed; /* 0 ok, YEPTRIS_ERROR_UNSUPPORTED */
60
+ int compact;
61
+ int no_reg; /* the next mj_value entry skips registration — an
62
+ * 'I'-wrapped construct registers ONCE (at the 'I');
63
+ * its immediate inner must not add a second entry or
64
+ * every later '@' link shifts */
65
+ int depth; /* mj_value recursion guard — the reader walks hostile
66
+ * bytes, not Ruby's heap: nesting is unbounded in the
67
+ * input, the stack is not (the parser's own
68
+ * max_depth=1000 is the same law) */
69
+ } mj_ctx;
70
+
71
+ static int mj_fail(mj_ctx* c) {
72
+ c->failed = YEPTRIS_ERROR_UNSUPPORTED;
73
+ return -1;
74
+ }
75
+
76
+ static int mj_byte(mj_ctx* c, unsigned char* out) {
77
+ if (c->s.at >= c->s.len)
78
+ return mj_fail(c);
79
+ *out = (unsigned char)c->s.p[c->s.at++];
80
+ return 0;
81
+ }
82
+
83
+ /* the w_long inverse — mirrors m_w_long's encode exactly:
84
+ * 0x00 -> 0; 1..4 -> positive size form k=b; 5..127 -> small +(
85
+ * b-5); 129..250 -> small -(251-b); 251..255 -> negative size form
86
+ * k=256-b (payload = LE(2^(8k) - m)); 128 unreachable. */
87
+ static int mj_long(mj_ctx* c, int64_t* out) {
88
+ unsigned char b;
89
+ if (mj_byte(c, &b) != 0)
90
+ return -1;
91
+ if (b == 0) {
92
+ *out = 0;
93
+ return 0;
94
+ }
95
+ if (b <= 4) { /* positive size form, k = b */
96
+ int64_t v = 0;
97
+ for (int i = 0; i < (int)b; i++) {
98
+ unsigned char d;
99
+ if (mj_byte(c, &d) != 0)
100
+ return -1;
101
+ v |= (int64_t)d << (8 * i);
102
+ }
103
+ *out = v;
104
+ return 0;
105
+ }
106
+ if (b <= 127) {
107
+ *out = (int64_t)b - 5;
108
+ return 0;
109
+ }
110
+ if (b >= 129 && b <= 250) {
111
+ *out = (int64_t)b - 251;
112
+ return 0;
113
+ }
114
+ { /* 251..255: negative size form */
115
+ int bytes = 256 - (int)b;
116
+ int64_t v = 0;
117
+ for (int i = 0; i < bytes; i++) {
118
+ unsigned char d;
119
+ if (mj_byte(c, &d) != 0)
120
+ return -1;
121
+ v |= (int64_t)d << (8 * i);
122
+ }
123
+ *out = v - ((int64_t)1 << (8 * bytes));
124
+ return 0;
125
+ }
126
+ }
127
+
128
+ static int mj_ivar_pairs(mj_ctx* c);
129
+ static int mj_value(mj_ctx* c);
130
+ static int mj_discard_value(mj_ctx* c);
131
+
132
+ static int mj_string_body(mj_ctx* c, int64_t len) {
133
+ if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
134
+ return mj_fail(c);
135
+ emit_dq_json(c->w, c->s.p + c->s.at, (uint32_t)len);
136
+ c->s.at += (size_t)len;
137
+ return 0;
138
+ }
139
+
140
+ /* a symbol: ':' + long(len) + bytes — registers in the table; the
141
+ * name is emitted as a JSON string when emit is set */
142
+ static int mj_symbol(mj_ctx* c, int emit) {
143
+ int64_t len;
144
+ if (mj_long(c, &len) != 0)
145
+ return -1;
146
+ if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
147
+ return mj_fail(c);
148
+ const char* name = c->s.p + c->s.at;
149
+ uint32_t nlen = (uint32_t)len;
150
+ c->s.at += (size_t)len;
151
+ if (c->syms_n == c->syms_cap) {
152
+ c->syms_cap = c->syms_cap ? c->syms_cap * 2 : 16;
153
+ c->syms = (mj_sym*)realloc(c->syms, c->syms_cap * sizeof(*c->syms));
154
+ if (!c->syms)
155
+ return mj_fail(c);
156
+ }
157
+ c->syms[c->syms_n].p = name;
158
+ c->syms[c->syms_n].len = nlen;
159
+ c->syms_n++;
160
+ if (emit)
161
+ emit_dq_json(c->w, name, nlen);
162
+ return 0;
163
+ }
164
+
165
+ /* the count + (symbol, value) pairs that follow an 'I' wrapper's
166
+ * wrapped value. Parsed and DISCARDED — encoding/freeze ivars have
167
+ * no JSON spelling; the symbol still enters the ';' table. */
168
+ static int mj_ivar_pairs(mj_ctx* c) {
169
+ int64_t count;
170
+ if (mj_long(c, &count) != 0)
171
+ return -1;
172
+ for (int64_t i = 0; i < count; i++) {
173
+ unsigned char nt;
174
+ if (mj_byte(c, &nt) != 0)
175
+ return -1;
176
+ if (nt == ':') {
177
+ if (mj_symbol(c, 0) != 0)
178
+ return -1; /* the name: tabled, not emitted */
179
+ } else if (nt == ';') {
180
+ int64_t sidx;
181
+ if (mj_long(c, &sidx) != 0)
182
+ return -1;
183
+ if (sidx < 0 || (size_t)sidx >= c->syms_n)
184
+ return mj_fail(c);
185
+ } else {
186
+ return mj_fail(c); /* ivar names are symbols only */
187
+ }
188
+ if (mj_discard_value(c) != 0)
189
+ return -1; /* the ivar value */
190
+ }
191
+ return 0;
192
+ }
193
+
194
+ /* stream-position-only walk: consumes one value, emits nothing */
195
+
196
+ #define MJ_DEPTH_MAX 1000
197
+
198
+ /* the discard twin rides the same budget (its 'I'/'['/'{' recursion is
199
+ * just as hostile-byte reachable) */
200
+ static int mj_discard_value_inner(mj_ctx* c);
201
+
202
+ static int mj_discard_value(mj_ctx* c) {
203
+ if (c->depth >= MJ_DEPTH_MAX)
204
+ return mj_fail(c);
205
+ c->depth++;
206
+ int rc = mj_discard_value_inner(c);
207
+ c->depth--;
208
+ return rc;
209
+ }
210
+
211
+ static int mj_discard_value_inner(mj_ctx* c) {
212
+ unsigned char t;
213
+ int rc = mj_byte(c, &t);
214
+ if (rc != 0)
215
+ return -1;
216
+ switch (t) {
217
+ case '0':
218
+ case 'T':
219
+ case 'F':
220
+ return 0;
221
+ case 'i':
222
+ case ';':
223
+ case '@': {
224
+ int64_t v;
225
+ return mj_long(c, &v);
226
+ }
227
+ case 'l': {
228
+ unsigned char sign;
229
+ int64_t nlimbs;
230
+ if (mj_byte(c, &sign) != 0)
231
+ return -1;
232
+ if (sign != 0x2b && sign != 0x2d)
233
+ return mj_fail(c);
234
+ if (mj_long(c, &nlimbs) != 0)
235
+ return -1;
236
+ if (nlimbs < 0 || (uint64_t)(nlimbs * 2) > (uint64_t)(c->s.len - c->s.at))
237
+ return mj_fail(c);
238
+ c->s.at += (size_t)(nlimbs * 2);
239
+ return 0;
240
+ }
241
+ case 'f':
242
+ case '"': {
243
+ int64_t len;
244
+ if (mj_long(c, &len) != 0)
245
+ return -1;
246
+ if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
247
+ return mj_fail(c);
248
+ c->s.at += (size_t)len;
249
+ return 0;
250
+ }
251
+ case 'I':
252
+ if (mj_discard_value(c) != 0)
253
+ return -1;
254
+ return mj_ivar_pairs(c);
255
+ case ':':
256
+ return mj_symbol(c, 0);
257
+ case '[': {
258
+ int64_t count;
259
+ if (mj_long(c, &count) != 0)
260
+ return -1;
261
+ for (int64_t i = 0; i < count; i++) {
262
+ if (mj_discard_value(c) != 0)
263
+ return -1;
264
+ }
265
+ return 0;
266
+ }
267
+ case '{': {
268
+ int64_t count;
269
+ if (mj_long(c, &count) != 0)
270
+ return -1;
271
+ for (int64_t i = 0; i < count; i++) {
272
+ if (mj_discard_value(c) != 0)
273
+ return -1; /* key */
274
+ if (mj_discard_value(c) != 0)
275
+ return -1; /* value */
276
+ }
277
+ return 0;
278
+ }
279
+ default:
280
+ return mj_fail(c);
281
+ }
282
+ }
283
+
284
+ /* MJ_DEBUG is read ONCE per process (the audit's finding: the memo
285
+ * path ran getenv per hit — an environment table walk inside the hot
286
+ * link copy). The flag is process-fixed for the reader's lifetime:
287
+ * a benign first-use race only double-reads the same name and both
288
+ * threads store the identical verdict through the atomic (the
289
+ * cpu.c one-time-detect shape). */
290
+ static int mj_debug_env(void) {
291
+ static atomic_int cached = -1;
292
+ int v = atomic_load_explicit(&cached, memory_order_acquire);
293
+ if (v < 0) {
294
+ v = getenv("MJ_DEBUG") != NULL;
295
+ atomic_store_explicit(&cached, v, memory_order_release);
296
+ }
297
+ return v;
298
+ }
299
+
300
+ /* the memo'd span of a registered object, copied to the cursor. The
301
+ * span lives earlier in the same buffer (append-only), so the copy
302
+ * is memmove-safe. */
303
+ static int mj_emit_memo(mj_ctx* c, const mj_obj* o) {
304
+ if (mj_debug_env()) {
305
+ char prev[13];
306
+ uint32_t k = o->out_len < 12 ? (uint32_t)o->out_len : 12;
307
+ memcpy(prev, c->w->p + o->out_off, k);
308
+ prev[k] = '\0';
309
+ fprintf(stderr, "memo off=%zu len=%zu: %s\n", o->out_off, o->out_len, prev);
310
+ }
311
+ if (o->out_len == 0)
312
+ return mj_fail(c); /* not memoized yet: a cycle */
313
+ wr_reserve(c->w, o->out_len);
314
+ if (c->w->oom)
315
+ return -1;
316
+ memmove(c->w->p + c->w->len, c->w->p + o->out_off, o->out_len);
317
+ wr_col_update(c->w, c->w->p + c->w->len, (uint32_t)o->out_len);
318
+ c->w->len += o->out_len;
319
+ /* the raw advance bypasses wr_put: hand back the reservation it
320
+ * would have consumed, or the next write rides past cap */
321
+ c->w->reserve -= o->out_len;
322
+ c->w->last = c->w->p[c->w->len - 1];
323
+ return 0;
324
+ }
325
+
326
+ static int mj_value_inner(mj_ctx* c);
327
+
328
+ /* the wrapper owns the recursion budget: hostile bytes can nest arrays
329
+ * without bound, the stack cannot (the parser's max_depth=1000 law) */
330
+ static int mj_value(mj_ctx* c) {
331
+ if (c->depth >= MJ_DEPTH_MAX)
332
+ return mj_fail(c);
333
+ c->depth++;
334
+ int rc = mj_value_inner(c);
335
+ c->depth--;
336
+ return rc;
337
+ }
338
+
339
+ static int mj_value_inner(mj_ctx* c) {
340
+ unsigned char t;
341
+ size_t tpos = c->s.at;
342
+ int reg_idx = -1;
343
+ size_t span_start = 0;
344
+ int register_this = !c->no_reg;
345
+ c->no_reg = 0;
346
+ if (mj_byte(c, &t) != 0)
347
+ return -1;
348
+ /* arrays, hashes, strings, floats, bignums and anything
349
+ * 'I'-wrapped register — ONCE per construct, at the outer byte
350
+ * (the wrapper's no_reg suppresses the inner) (0-based stream
351
+ * order). Bignums hold a slot but Ruby never emits a link to one
352
+ * (the second occurrence re-emits in full); the entry only keeps
353
+ * the slot count aligned for later links. Integers, symbols and
354
+ * the other immediates never register. */
355
+ if (register_this && (t == '[' || t == '{' || t == '"' || t == 'f' || t == 'I' || t == 'l')) {
356
+ if (c->objs_n == c->objs_cap) {
357
+ c->objs_cap = c->objs_cap ? c->objs_cap * 2 : 16;
358
+ c->objs = (mj_obj*)realloc(c->objs, c->objs_cap * sizeof(*c->objs));
359
+ if (!c->objs)
360
+ return mj_fail(c);
361
+ }
362
+ c->objs[c->objs_n].pos = tpos;
363
+ c->objs[c->objs_n].out_off = c->w->len;
364
+ c->objs[c->objs_n].out_len = 0;
365
+ reg_idx = (int)c->objs_n;
366
+ c->objs_n++;
367
+ span_start = c->w->len;
368
+ }
369
+ switch (t) {
370
+ case '0':
371
+ wr_put(c->w, "null", 4);
372
+ break;
373
+ case 'T':
374
+ wr_put(c->w, "true", 4);
375
+ break;
376
+ case 'F':
377
+ wr_put(c->w, "false", 5);
378
+ break;
379
+ case 'i': {
380
+ int64_t v;
381
+ if (mj_long(c, &v) != 0)
382
+ return -1;
383
+ char buf[24];
384
+ int n = snprintf(buf, sizeof buf, "%lld", (long long)v);
385
+ wr_put(c->w, buf, (uint32_t)n);
386
+ break;
387
+ }
388
+ case 'l': {
389
+ /* the bignum: sign byte '+'/'-' + w_long(byte count) + LE
390
+ * bytes; the i64-representable range emits, wider falls back */
391
+ unsigned char sign;
392
+ if (mj_byte(c, &sign) != 0)
393
+ return -1;
394
+ if (sign != 0x2b && sign != 0x2d)
395
+ return mj_fail(c);
396
+ int64_t nlimbs;
397
+ if (mj_long(c, &nlimbs) != 0)
398
+ return -1;
399
+ if (nlimbs < 1 || nlimbs > 4)
400
+ return mj_fail(c); /* beyond 64 bits: fall back */
401
+ int64_t v = 0;
402
+ for (int64_t i = 0; i < nlimbs; i++) {
403
+ unsigned char lo, hi;
404
+ if (mj_byte(c, &lo) != 0 || mj_byte(c, &hi) != 0)
405
+ return -1;
406
+ /* shift in the unsigned domain: the signed form put
407
+ * 0xFFFF into the sign bit at limb 3 (UB, UBSan-flagged) */
408
+ v |= (int64_t)((uint64_t)(lo | ((uint64_t)hi << 8)) << (16 * i));
409
+ }
410
+ if (sign == 0x2d)
411
+ v = -v;
412
+ char buf[24];
413
+ int n = snprintf(buf, sizeof buf, "%lld", (long long)v);
414
+ wr_put(c->w, buf, (uint32_t)n);
415
+ break;
416
+ }
417
+ case 'f': {
418
+ int64_t len;
419
+ if (mj_long(c, &len) != 0)
420
+ return -1;
421
+ if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
422
+ return mj_fail(c);
423
+ char tmp[64];
424
+ uint32_t tl = (uint32_t)len < sizeof tmp - 1 ? (uint32_t)len : (uint32_t)sizeof tmp - 1;
425
+ memcpy(tmp, c->s.p + c->s.at, tl);
426
+ tmp[tl] = '\0';
427
+ c->s.at += (size_t)len;
428
+ double d = strtod(tmp, NULL);
429
+ if (!isfinite(d))
430
+ return mj_fail(c); /* JSON has no inf/nan spelling — and a
431
+ * strtod OVERFLOW ("1e999") produces
432
+ * one from text that never spells it */
433
+ char buf[40];
434
+ int wl = yep_d2s_json(d, buf);
435
+ wr_put(c->w, buf, (uint32_t)wl);
436
+ break;
437
+ }
438
+ case '"': {
439
+ int64_t len;
440
+ if (mj_long(c, &len) != 0)
441
+ return -1;
442
+ int rc = mj_string_body(c, len);
443
+ if (rc != 0)
444
+ return -1;
445
+ break;
446
+ }
447
+ case 'I': {
448
+ /* the wrapped value IS the payload: re-dispatch into it, then
449
+ * the ivar pairs ride. The inner dispatch must not register —
450
+ * the construct registered at the 'I' (the double entry
451
+ * shifted every later '@' link) */
452
+ c->no_reg = 1;
453
+ if (mj_value(c) != 0) {
454
+ c->no_reg = 0;
455
+ return -1;
456
+ }
457
+ c->no_reg = 0;
458
+ if (mj_ivar_pairs(c) != 0)
459
+ return -1;
460
+ break;
461
+ }
462
+ case ':': {
463
+ /* a bare symbol VALUE: emitted as its name string */
464
+ if (mj_symbol(c, 1) != 0)
465
+ return -1;
466
+ break;
467
+ }
468
+ case ';': {
469
+ int64_t idx;
470
+ if (mj_long(c, &idx) != 0)
471
+ return -1;
472
+ if (idx < 0 || (size_t)idx >= c->syms_n)
473
+ return mj_fail(c);
474
+ emit_dq_json(c->w, c->syms[idx].p, c->syms[idx].len);
475
+ break;
476
+ }
477
+ case '@': {
478
+ /* an object link: the registered span memmoves to the cursor */
479
+ int64_t idx;
480
+ if (mj_long(c, &idx) != 0)
481
+ return -1;
482
+ if (idx < 0 || (size_t)idx >= c->objs_n)
483
+ return mj_fail(c);
484
+ if (mj_emit_memo(c, &c->objs[idx]) != 0)
485
+ return -1;
486
+ break;
487
+ }
488
+ case '[':
489
+ case '{': {
490
+ int64_t count;
491
+ if (mj_long(c, &count) != 0)
492
+ return -1;
493
+ if (count < 0)
494
+ return mj_fail(c);
495
+ wr_byte(c->w, t == '[' ? '[' : '{');
496
+ const char* sep = c->compact ? "," : ", ";
497
+ uint32_t sep_len = c->compact ? 1u : 2u;
498
+ for (int64_t i = 0; i < count; i++) {
499
+ if (i > 0)
500
+ wr_put(c->w, sep, sep_len);
501
+ if (t == '[') {
502
+ if (mj_value(c) != 0)
503
+ return -1;
504
+ } else {
505
+ /* the key: a string (with its 'I' wrapper) or a
506
+ * symbol — JSON keys are strings */
507
+ unsigned char kt;
508
+ size_t ktpos = c->s.at;
509
+ if (mj_byte(c, &kt) != 0)
510
+ return -1;
511
+ if (kt == 'I') {
512
+ if (mj_value(c) != 0)
513
+ return -1;
514
+ if (mj_ivar_pairs(c) != 0)
515
+ return -1;
516
+ } else if (kt == '"') {
517
+ /* the key registers like any string object: its
518
+ * span is memo'd for later '@' key links */
519
+ int kreg = (int)c->objs_n;
520
+ size_t kspan = c->w->len;
521
+ if (kreg >= 0) {
522
+ if (c->objs_n == c->objs_cap) {
523
+ c->objs_cap = c->objs_cap ? c->objs_cap * 2 : 16;
524
+ c->objs = (mj_obj*)realloc(c->objs, c->objs_cap * sizeof(*c->objs));
525
+ if (!c->objs)
526
+ return mj_fail(c);
527
+ }
528
+ c->objs[c->objs_n].pos = ktpos;
529
+ c->objs[c->objs_n].out_off = c->w->len;
530
+ c->objs[c->objs_n].out_len = 0;
531
+ c->objs_n++;
532
+ }
533
+ int64_t len;
534
+ if (mj_long(c, &len) != 0)
535
+ return -1;
536
+ int rc = mj_string_body(c, len);
537
+ if (rc != 0)
538
+ return -1;
539
+ if (kreg >= 0) {
540
+ c->objs[kreg].out_off = kspan;
541
+ c->objs[kreg].out_len = c->w->len - kspan;
542
+ }
543
+ } else if (kt == ':') {
544
+ if (mj_symbol(c, 1) != 0)
545
+ return -1;
546
+ } else if (kt == ';') {
547
+ int64_t idx;
548
+ if (mj_long(c, &idx) != 0)
549
+ return -1;
550
+ if (idx < 0 || (size_t)idx >= c->syms_n)
551
+ return mj_fail(c);
552
+ emit_dq_json(c->w, c->syms[idx].p, c->syms[idx].len);
553
+ } else if (kt == '@') {
554
+ int64_t idx;
555
+ if (mj_long(c, &idx) != 0)
556
+ return -1;
557
+ if (idx < 0 || (size_t)idx >= c->objs_n)
558
+ return mj_fail(c);
559
+ if (mj_emit_memo(c, &c->objs[idx]) != 0)
560
+ return -1;
561
+ } else {
562
+ return mj_fail(c);
563
+ }
564
+ wr_put(c->w, c->compact ? ":" : ": ", c->compact ? 1u : 2u);
565
+ if (mj_value(c) != 0)
566
+ return -1;
567
+ }
568
+ }
569
+ wr_byte(c->w, t == '[' ? ']' : '}');
570
+ break;
571
+ }
572
+ default:
573
+ return mj_fail(c);
574
+ }
575
+ if (reg_idx >= 0) {
576
+ c->objs[reg_idx].out_off = span_start;
577
+ c->objs[reg_idx].out_len = c->w->len - span_start;
578
+ }
579
+ return 0;
580
+ }
581
+
582
+ YEPTRIS_API YeptrisStatus yeptris_json_from_marshal(const char* m, size_t m_len, int compact,
583
+ char** out, size_t* out_len) {
584
+ if (out == NULL || out_len == NULL)
585
+ return YEPTRIS_ERROR_ARG;
586
+ *out = NULL;
587
+ *out_len = 0;
588
+ if (m == NULL || m_len < 2 || (unsigned char)m[0] != 0x04 || (unsigned char)m[1] != 0x08)
589
+ return YEPTRIS_ERROR_UNSUPPORTED;
590
+ yep_emitter em;
591
+ memset(&em, 0, sizeof(em));
592
+ yep_writer* w = &em.w;
593
+ w->sc_dec = NULL;
594
+ w->sc_i = 0;
595
+ w->json = 1;
596
+ w->json_compact = compact ? 1 : 0;
597
+ w->json_pretty = 0;
598
+ w->canonical = 0;
599
+ w->explicit_doc_start = 0;
600
+ w->best_width = 0;
601
+ w->sv_input = NULL;
602
+ w->sv_arena = NULL;
603
+ w->sink = NULL;
604
+ w->force_flow = 0;
605
+ w->in_flow = 0;
606
+ w->flow_item = 0;
607
+ w->unserializable = 0;
608
+ w->indent = 2; /* #903 fields: the JSON lane never folds or
609
+ * block-indents, but the writer stays fully
610
+ * initialized */
611
+ w->block_fold = 0;
612
+
613
+ mj_ctx c;
614
+ memset(&c, 0, sizeof(c));
615
+ c.w = w;
616
+ c.s.p = m;
617
+ c.s.len = m_len;
618
+ c.s.at = 2; /* the 4.8 header */
619
+ c.compact = compact ? 1 : 0;
620
+
621
+ /* one wet pass into a growable buffer (the #352 slice-2 shape).
622
+ * Raw malloc is the CONTRACT here, not a bypass (#904 item 3): the
623
+ * route takes bytes, not a document — there is no document
624
+ * allocator to route through, and *out is "malloc'd, free with
625
+ * yeptris_marshal_free" (marshal.h), so the growth in wr_grow must
626
+ * stay realloc-on-system too or the caller's free() breaks. */
627
+ size_t est = m_len * 2 + 64;
628
+ char* buf = (char*)malloc(est);
629
+ if (!buf)
630
+ return YEPTRIS_ERROR_MEMORY;
631
+ w->p = buf;
632
+ w->cap = est;
633
+ w->grow = 1;
634
+
635
+ mj_value(&c);
636
+
637
+ int failed = c.failed;
638
+ size_t wrote = w->len;
639
+ buf = w->p;
640
+ free(c.syms);
641
+ free(c.objs);
642
+ if (failed || w->oom) {
643
+ free(buf);
644
+ return failed ? (YeptrisStatus)failed : YEPTRIS_ERROR_MEMORY;
645
+ }
646
+ buf[wrote] = '\0';
647
+ *out = buf;
648
+ *out_len = wrote;
649
+ return YEPTRIS_OK;
650
+ }