yeptris 0.6.59.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 (44) 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/ffi.rb +19 -0
  5. data/lib/yeptris/yaml.rb +61 -0
  6. data/lib/yeptris.rb +1 -1
  7. data/libyeptris.so +0 -0
  8. data/vendor/libyeptris/CMakeLists.txt +1 -1
  9. data/vendor/libyeptris/src/CMakeLists.txt +7 -0
  10. data/vendor/libyeptris/src/include/yeptris/emit.h +30 -2
  11. data/vendor/libyeptris/src/include/yeptris/parse.h +52 -0
  12. data/vendor/libyeptris/src/include/yeptris/resolve.h +8 -0
  13. data/vendor/libyeptris/src/include/yeptris/validate.h +222 -21
  14. data/vendor/libyeptris/src/include/yeptris/values.h +8 -2
  15. data/vendor/libyeptris/src/yeptris/cbor/cbor.h +10 -0
  16. data/vendor/libyeptris/src/yeptris/cbor/decode.c +5 -7
  17. data/vendor/libyeptris/src/yeptris/cbor/encode.c +9 -9
  18. data/vendor/libyeptris/src/yeptris/cbor/from_marshal.c +1 -1
  19. data/vendor/libyeptris/src/yeptris/common/error.h +2 -0
  20. data/vendor/libyeptris/src/yeptris/doc.h +20 -0
  21. data/vendor/libyeptris/src/yeptris/emit/emit.c +85 -13
  22. data/vendor/libyeptris/src/yeptris/emit/writer.c +158 -642
  23. data/vendor/libyeptris/src/yeptris/emit/writer.h +15 -1
  24. data/vendor/libyeptris/src/yeptris/events/push.c +28 -3
  25. data/vendor/libyeptris/src/yeptris/events/values.c +24 -0
  26. data/vendor/libyeptris/src/yeptris/marshal/to_json.c +650 -0
  27. data/vendor/libyeptris/src/yeptris/parse/engine.c +121 -40
  28. data/vendor/libyeptris/src/yeptris/parse/stream_iter.c +326 -0
  29. data/vendor/libyeptris/src/yeptris/parse.c +173 -43
  30. data/vendor/libyeptris/src/yeptris/resolve/compat11.c +1 -1
  31. data/vendor/libyeptris/src/yeptris/resolve/core12.c +11 -3
  32. data/vendor/libyeptris/src/yeptris/resolve/full12.c +201 -0
  33. data/vendor/libyeptris/src/yeptris/resolve/json12.c +12 -6
  34. data/vendor/libyeptris/src/yeptris/resolve/resolver.h +20 -0
  35. data/vendor/libyeptris/src/yeptris/validate/compile.c +39 -3
  36. data/vendor/libyeptris/src/yeptris/validate/internal.h +223 -7
  37. data/vendor/libyeptris/src/yeptris/validate/jsonschema.c +645 -115
  38. data/vendor/libyeptris/src/yeptris/validate/output.c +246 -0
  39. data/vendor/libyeptris/src/yeptris/validate/regexp.c +623 -157
  40. data/vendor/libyeptris/src/yeptris/validate/registry.c +118 -0
  41. data/vendor/libyeptris/src/yeptris/validate/resources.c +601 -0
  42. data/vendor/libyeptris/src/yeptris/validate/stream.c +1155 -0
  43. data/vendor/libyeptris/src/yeptris/validate/validate.c +288 -145
  44. metadata +8 -1
@@ -24,7 +24,6 @@
24
24
 
25
25
  #include "style.h"
26
26
 
27
- #include <math.h>
28
27
  #include <stdio.h>
29
28
  #include <stdlib.h>
30
29
 
@@ -57,7 +56,10 @@ static void wr_maybe_flush(yep_writer* w) {
57
56
  w->len = 0;
58
57
  }
59
58
 
60
- static void wr_col_update(yep_writer* w, const char* p, uint32_t n) {
59
+ /* the write primitives below are writer.h-visible: the marshal reader
60
+ * (marshal/to_json.c) writes through the same engine (#904 item 5's
61
+ * move kept ONE writer). */
62
+ void wr_col_update(yep_writer* w, const char* p, uint32_t n) {
61
63
  for (uint32_t i = n; i > 0; i--) { /* last newline wins */
62
64
  if (p[i - 1] == '\n') {
63
65
  w->col = (int)(n - i);
@@ -67,7 +69,7 @@ static void wr_col_update(yep_writer* w, const char* p, uint32_t n) {
67
69
  w->col += (int)n;
68
70
  }
69
71
 
70
- static void wr_reserve(yep_writer* w, size_t need);
72
+ void wr_reserve(yep_writer* w, size_t need);
71
73
 
72
74
  static void wr_grow(yep_writer* w, size_t need) {
73
75
  if (w->oom || w->p == NULL) {
@@ -78,6 +80,10 @@ static void wr_grow(yep_writer* w, size_t need) {
78
80
  if (want < w->cap * 2) {
79
81
  want = w->cap * 2;
80
82
  }
83
+ /* raw realloc ON PURPOSE (#904 item 3): w->p is either the buffer
84
+ * the serializers RETURN (release contract: the host's free(),
85
+ * emit.h) or the caller's own bytes — no document allocator may
86
+ * own either, or the host's free() breaks */
81
87
  char* np = (char*)realloc(w->p, want);
82
88
  if (np == NULL) {
83
89
  w->oom = 1;
@@ -87,7 +93,7 @@ static void wr_grow(yep_writer* w, size_t need) {
87
93
  w->cap = want;
88
94
  }
89
95
 
90
- static void wr_put(yep_writer* w, const char* p, uint32_t n) {
96
+ void wr_put(yep_writer* w, const char* p, uint32_t n) {
91
97
  if (n > 0) {
92
98
  w->last = p[n - 1];
93
99
  }
@@ -160,7 +166,7 @@ static void wr_put(yep_writer* w, const char* p, uint32_t n) {
160
166
  * trusted-count routes keep cap bookkeeping that is not the writer's
161
167
  * to reason about (several set cap=0), so their writes stay exactly
162
168
  * the old per-call trust. */
163
- static void wr_reserve(yep_writer* w, size_t need) {
169
+ void wr_reserve(yep_writer* w, size_t need) {
164
170
  /* the FLUSH runs for every route: the fixed-window streaming route
165
171
  * (grow=0) has no reservation, so this refill path is its ONLY
166
172
  * flush point -- skipping it let len ride to cap and the next
@@ -184,7 +190,7 @@ static void wr_reserve(yep_writer* w, size_t need) {
184
190
  w->reserve = w->cap - w->len - 1;
185
191
  }
186
192
 
187
- static void wr_byte(yep_writer* w, char c) {
193
+ void wr_byte(yep_writer* w, char c) {
188
194
  w->last = c;
189
195
  if (c == '\n') {
190
196
  w->col = 0;
@@ -357,9 +363,91 @@ static void emit_sq(yep_writer* w, const char* p, uint32_t n) {
357
363
  wr_byte(w, '\'');
358
364
  }
359
365
 
366
+ /* Block-side width folding (#903): libyaml's plain/single-quoted fold
367
+ * walk. At an INTERIOR single space (not the value's first or last
368
+ * byte, no adjacent spaces) with the column past best_width, the break
369
+ * REPLACES the space and the continuation re-indents to the fold
370
+ * column — parent_col + indent (libyaml's emit_scalar bumps the write
371
+ * indent to parent_indent + best_indent for the scalar's own lines).
372
+ * The folded bodies carry no breaks by construction: sc_route sends
373
+ * multiline content to DQ/literal, so the walk only decides on ' '
374
+ * (IS_SPACE — a tab is content). Columns are BYTES, matching the
375
+ * engine's existing 13B flow fold; libyaml counts characters, so
376
+ * multi-byte content folds at slightly different points (documented
377
+ * divergence, ASCII input folds byte-identically). */
378
+
379
+ /* The fold break: libyaml's write_indent mid-scalar — indention is
380
+ * always clear there, so it is exactly a break plus the pad. */
381
+ static void wr_fold_break(yep_writer* w, int fold_col) {
382
+ wr_byte(w, '\n');
383
+ wr_indent(w, fold_col);
384
+ }
385
+
386
+ /* The shared walk for a plain body (no doubling to apply). The fold
387
+ * decision prices the PENDING run in (it flushes only at a fold): the
388
+ * live column at the candidate space is w->col + run — exactly
389
+ * libyaml's emitter->column at the same byte. */
390
+ static void emit_plain_folded(yep_writer* w, const char* p, uint32_t n, int fold_col) {
391
+ uint32_t run = 0; /* pending flush span length */
392
+ uint32_t i = 0;
393
+ while (i < n) {
394
+ if (p[i] == ' ' && i > 0 && i + 1 < n && w->col + (int)run > w->best_width &&
395
+ p[i - 1] != ' ' && p[i + 1] != ' ') {
396
+ if (run) {
397
+ wr_put(w, p + i - run, run);
398
+ run = 0;
399
+ }
400
+ wr_fold_break(w, fold_col);
401
+ i++; /* the fold consumes the space (libyaml: MOVE without WRITE) */
402
+ continue;
403
+ }
404
+ i++;
405
+ run++;
406
+ }
407
+ if (run) {
408
+ wr_put(w, p + n - run, run);
409
+ }
410
+ }
411
+
412
+ /* The single-quoted twin: '' doubling rides the same walk (a doubled
413
+ * apostrophe is two written bytes; the column tracking through wr_put
414
+ * prices it in automatically). */
415
+ static void emit_sq_folded(yep_writer* w, const char* p, uint32_t n, int fold_col) {
416
+ wr_byte(w, '\'');
417
+ uint32_t run = 0;
418
+ uint32_t i = 0;
419
+ while (i < n) {
420
+ if (p[i] == '\'') {
421
+ if (run) {
422
+ wr_put(w, p + i - run, run);
423
+ run = 0;
424
+ }
425
+ wr_put(w, "''", 2);
426
+ i++;
427
+ continue;
428
+ }
429
+ if (p[i] == ' ' && i > 0 && i + 1 < n && w->col + (int)run > w->best_width &&
430
+ p[i - 1] != ' ' && p[i + 1] != ' ') {
431
+ if (run) {
432
+ wr_put(w, p + i - run, run);
433
+ run = 0;
434
+ }
435
+ wr_fold_break(w, fold_col);
436
+ i++;
437
+ continue;
438
+ }
439
+ i++;
440
+ run++;
441
+ }
442
+ if (run) {
443
+ wr_put(w, p + n - run, run);
444
+ }
445
+ wr_byte(w, '\'');
446
+ }
447
+
360
448
  /* JSON string escapes: \" \\ \b \f \n \r \t and \uXXXX for other
361
449
  * controls; raw UTF-8 passes through (RFC 8259). */
362
- static void emit_dq_json(yep_writer* w, const char* p, uint32_t n) {
450
+ void emit_dq_json(yep_writer* w, const char* p, uint32_t n) {
363
451
  wr_byte(w, '"');
364
452
  static const char* named[32] = {
365
453
  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\b", "\\t", "\\n",
@@ -397,9 +485,10 @@ static void emit_dq_json(yep_writer* w, const char* p, uint32_t n) {
397
485
  wr_byte(w, '"');
398
486
  }
399
487
 
400
- /* Literal block: header at the cursor, body lines at parent_col+2.
401
- * The explicit indicator is always '2' because the pad is defined as
402
- * parent_col+2 everywhere (the contract that keeps it valid). */
488
+ /* Literal block: header at the cursor, body lines at parent_col+indent.
489
+ * The explicit indicator spells the pad when it is ambiguous because
490
+ * the pad is defined as parent_col+indent everywhere (the contract
491
+ * that keeps it valid; indent=2 gives the historical '2'). */
403
492
  static void emit_literal(yep_writer* w, const yep_dnode* n, int parent_col) {
404
493
  yep_view vdec = wv(w, n->value);
405
494
  const char* p = (const char*)vdec.p;
@@ -418,12 +507,14 @@ static void emit_literal(yep_writer* w, const yep_dnode* n, int parent_col) {
418
507
  /* libyaml's indicator rules (#290 family 4): the explicit indent
419
508
  * appears when the first body line starts with a space or is
420
509
  * blank — the base indent is unambiguous otherwise (the pad is
421
- * parent_col+2 by contract; F6MC's leading-blank folded bodies) */
510
+ * parent_col+indent by contract; F6MC's leading-blank folded
511
+ * bodies). The digit spells the pad (#903: '2' at the default
512
+ * indent, the caller's level otherwise). */
422
513
  if (body > 0 && (p[0] == ' ' || p[0] == '\n')) {
423
- wr_byte(w, '2');
514
+ wr_byte(w, (char)('0' + w->indent));
424
515
  }
425
516
  wr_byte(w, '\n');
426
- int pad = parent_col + 2;
517
+ int pad = parent_col + w->indent;
427
518
  wr_indent(w, pad);
428
519
  for (uint32_t i = 0; i < body; i++) {
429
520
  wr_byte(w, p[i]);
@@ -521,6 +612,13 @@ enum { YEP_SC_NOTHING = 0, YEP_SC_PLAIN, YEP_SC_SQ, YEP_SC_DQ, YEP_SC_LITERAL, Y
521
612
  * The block map's explicit-key promotion consults the same predicate:
522
613
  * a key whose content demotes to DQ re-parses as a QUOTED key and must
523
614
  * ride simple, or round two's render differs (the nightly's round 118). */
615
+
616
+ /* The flow arm below is a per-byte switch ON PURPOSE (#904 item 4's
617
+ * measured skip): the stopset SIMD kernel variant was measured at
618
+ * ~+3% median on flow-json emit (the only shape that enters the arm;
619
+ * 4-sample A/B, build-bench harness) — under the ledger's ~5% bar for
620
+ * touching the hot derive path, and within run noise under a loaded
621
+ * machine. PERF-LEDGER carries the numbers. */
524
622
  static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
525
623
  int has_tag, int no_exotic, int plain_fact) {
526
624
  /* the parse-time fact bits (#352 lanes 2-3): a dnode verified clean
@@ -533,7 +631,9 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
533
631
  if (w->in_flow && len > 0) {
534
632
  /* flow context: the indicators TERMINATE a plain scalar — a
535
633
  * raw ']' inside "{}" re-parses as a stray close (the
536
- * nightly's "555555ta]" explicit-key crasher) */
634
+ * nightly's "555555ta]" explicit-key crasher). Per-byte switch
635
+ * ON PURPOSE: the stopset kernel variant measured under the
636
+ * skip bar (the comment above; PERF-LEDGER has the numbers). */
537
637
  for (uint32_t k = 0; k < len; k++) {
538
638
  switch (p[k]) {
539
639
  case ',':
@@ -699,10 +799,21 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
699
799
  break; /* an empty plain value emits as nothing (libyaml's null
700
800
  rendering, issue #290) */
701
801
  case YEP_SC_PLAIN:
702
- wr_put(w, p, len);
802
+ if (w->block_fold && !as_key && !w->in_flow && len > 0) {
803
+ /* #903: block-context width folding; keys never fold
804
+ * (libyaml: allow_breaks = !simple_key_context) and flow
805
+ * members keep the flow walk */
806
+ emit_plain_folded(w, p, len, parent_col + w->indent);
807
+ } else {
808
+ wr_put(w, p, len);
809
+ }
703
810
  break;
704
811
  case YEP_SC_SQ:
705
- emit_sq(w, p, len);
812
+ if (w->block_fold && !as_key && !w->in_flow && len > 0) {
813
+ emit_sq_folded(w, p, len, parent_col + w->indent);
814
+ } else {
815
+ emit_sq(w, p, len);
816
+ }
706
817
  break;
707
818
  case YEP_SC_LITERAL:
708
819
  emit_literal(w, n, parent_col);
@@ -1172,13 +1283,16 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1172
1283
  * key, plain unpropertied scalar value, both safety-proven by
1173
1284
  * the exact predicates sc_route consults (no memo touch, so
1174
1285
  * the dry/wet sc_i streams stay aligned). Anything else falls
1175
- * through untouched — parity by construction. */
1286
+ * through untouched — parity by construction. block_fold
1287
+ * (#903) declines the arm wholesale: the fused write's
1288
+ * arithmetic column model has no fold walk, and the general
1289
+ * body emits the identical bytes with folding. */
1176
1290
  const yep_dnode* kn0 = yep_dom_node(d, child);
1177
1291
  const yep_dnode* vn0 = yep_dom_node(d, kn0->next_sibling);
1178
- if (!w->canonical && !w->json && !w->force_flow && vn0 != NULL && kn0->kind == 0 &&
1179
- kn0->style == 1 && kn0->anchor.len == 0 && kn0->tag.len == 0 && kn0->count == 0 &&
1180
- vn0->kind == 0 && vn0->style == 1 && vn0->anchor.len == 0 && vn0->tag.len == 0 &&
1181
- vn0->count == 0) {
1292
+ if (!w->canonical && !w->json && !w->force_flow && !w->block_fold && vn0 != NULL &&
1293
+ kn0->kind == 0 && kn0->style == 1 && kn0->anchor.len == 0 && kn0->tag.len == 0 &&
1294
+ kn0->count == 0 && vn0->kind == 0 && vn0->style == 1 && vn0->anchor.len == 0 &&
1295
+ vn0->tag.len == 0 && vn0->count == 0) {
1182
1296
  yep_view kv = wv(w, kn0->value);
1183
1297
  yep_view vv = wv(w, vn0->value);
1184
1298
  uint32_t f_ind = 0, f_nl = 0, f_total = 0;
@@ -1337,11 +1451,11 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1337
1451
  * with a complex key forcing the explicit form) */
1338
1452
  if (!(vn->kind == 0 || vn->kind == 3 || vn->flow || vn->count == 0)) {
1339
1453
  int seq_val = (vn->kind == 1 && !vn->flow);
1340
- int val_col = seq_val ? content_col : content_col + 2;
1454
+ int val_col = seq_val ? content_col : content_col + w->indent;
1341
1455
  emit_props_tail(w, vn);
1342
1456
  wr_byte(w, '\n');
1343
1457
  wr_indent(w, val_col);
1344
- emit_node(em, kn->next_sibling, val_col - 2, 0);
1458
+ emit_node(em, kn->next_sibling, val_col - w->indent, 0);
1345
1459
  } else {
1346
1460
  emit_node(em, kn->next_sibling, content_col, 0);
1347
1461
  }
@@ -1368,15 +1482,16 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1368
1482
  } else {
1369
1483
  /* nested block collection: next line. libyaml parity:
1370
1484
  * a SEQUENCE value sits at the key's column (zero
1371
- * extra indent); mappings indent +2 (issue #95) */
1485
+ * extra indent); mappings indent one level (issue #95;
1486
+ * the level width is the #903 indent option) */
1372
1487
  int seq_val = (vn->kind == 1 && !vn->flow);
1373
- int val_col = seq_val ? content_col : content_col + 2;
1488
+ int val_col = seq_val ? content_col : content_col + w->indent;
1374
1489
  emit_props_tail(w, vn);
1375
1490
  wr_byte(w, '\n');
1376
1491
  wr_indent(w, val_col);
1377
- /* emit_block_seq dashes at parent_col+2: pass val_col-2
1378
- * so the dashes land at val_col exactly */
1379
- emit_node(em, kn->next_sibling, val_col - 2, 0);
1492
+ /* emit_block_seq dashes at parent_col+indent: pass
1493
+ * val_col-indent so the dashes land at val_col exactly */
1494
+ emit_node(em, kn->next_sibling, val_col - w->indent, 0);
1380
1495
  }
1381
1496
  }
1382
1497
  pair++;
@@ -1419,11 +1534,19 @@ static void emit_block_seq(yep_emitter* em, uint32_t id, int content_col) {
1419
1534
  wr_byte(w, '-');
1420
1535
  emit_props_tail(w, cn);
1421
1536
  wr_byte(w, '\n');
1422
- wr_indent(w, content_col + 2);
1537
+ wr_indent(w, content_col + w->indent);
1423
1538
  emit_node(em, child, content_col, 0);
1424
1539
  } else {
1425
1540
  /* compact: a block collection starts right after "- " */
1426
1541
  wr_put(w, "- ", 2);
1542
+ if (w->indent > 2 && cn->kind != 0 && cn->kind != 3 && !cn->flow && cn->count > 0) {
1543
+ /* libyaml/PyYAML's indent shape (#903): a nested
1544
+ * block collection's first line rides at the LEVEL
1545
+ * column (dash + indent), not the dash+2 token width
1546
+ * — indent=4 spells "- a: 1" / "- - x"; scalars,
1547
+ * aliases and empty/flow collections keep "- x" */
1548
+ wr_indent(w, w->indent - 2);
1549
+ }
1427
1550
  emit_node(em, child, content_col, 0);
1428
1551
  }
1429
1552
  idx++;
@@ -1459,7 +1582,10 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key)
1459
1582
  emit_anchor_ex(em, n);
1460
1583
  wr_put(w, "{}", 2);
1461
1584
  } else {
1462
- emit_block_map(em, id, parent_col + 2);
1585
+ /* the root (parent_col < 0, the caller's -2) rides column
1586
+ * 0; every nesting level adds the indent option (#903) */
1587
+ int base = (parent_col < 0) ? parent_col + 2 : parent_col + w->indent;
1588
+ emit_block_map(em, id, base);
1463
1589
  }
1464
1590
  return;
1465
1591
  }
@@ -1469,7 +1595,8 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key)
1469
1595
  emit_anchor_ex(em, n);
1470
1596
  wr_put(w, "[]", 2);
1471
1597
  } else {
1472
- emit_block_seq(em, id, parent_col + 2);
1598
+ int base = (parent_col < 0) ? parent_col + 2 : parent_col + w->indent;
1599
+ emit_block_seq(em, id, base);
1473
1600
  }
1474
1601
  return;
1475
1602
  }
@@ -1574,617 +1701,6 @@ static void emit_json_node(yep_emitter* em, uint32_t id) {
1574
1701
  emit_canonical_scalar(w, n);
1575
1702
  }
1576
1703
 
1577
- /* ============================================================
1578
- * yeptris-ruby#322: the marshal READER — Ruby Marshal.dump bytes
1579
- * to JSON text in one C call (no per-scalar crossings, no object
1580
- * reconstruction). The format facts mirror marshal.c's emission
1581
- * contract (empirically pinned against Ruby 3.4): w_long small form
1582
- * x==0 -> 0x00, |x| <= 122 -> sign*(|x|+5), size form tag +-k with
1583
- * LE payload (2^(8k) - m for negatives); strings carry 'I' ivar
1584
- * wrappers; arrays/hashes/strings/floats register for '@' links.
1585
- *
1586
- * '@' links MEMMOVE the registered object's emitted span — the first
1587
- * walk's bytes are the cache; a lookup miss is a cycle (JSON cannot
1588
- * express it, the binding falls back). No re-parse, no registration
1589
- * on links: the object table stays exactly Ruby's stream order.
1590
- *
1591
- * The JSON-irrelevant constructs ('o' objects, 'S' structs, 'u'
1592
- * user marshal, 'l' bignums beyond i64, non-finite floats) return
1593
- * UNSUPPORTED — the binding falls back to its per-node walk.
1594
- * ============================================================ */
1595
-
1596
- typedef struct {
1597
- const char* p;
1598
- size_t len;
1599
- size_t at;
1600
- } mj_stream;
1601
-
1602
- typedef struct {
1603
- const char* p;
1604
- uint32_t len;
1605
- } mj_sym;
1606
-
1607
- typedef struct {
1608
- size_t pos; /* the stream position of the type byte */
1609
- size_t out_off; /* where the emitted span starts in w's buffer */
1610
- size_t out_len; /* the span's length */
1611
- } mj_obj;
1612
-
1613
- typedef struct {
1614
- yep_writer* w;
1615
- mj_stream s;
1616
- mj_sym* syms;
1617
- size_t syms_n, syms_cap;
1618
- mj_obj* objs;
1619
- size_t objs_n, objs_cap;
1620
- int failed; /* 0 ok, YEPTRIS_ERROR_UNSUPPORTED */
1621
- int compact;
1622
- int no_reg; /* the next mj_value entry skips registration — an
1623
- * 'I'-wrapped construct registers ONCE (at the 'I');
1624
- * its immediate inner must not add a second entry or
1625
- * every later '@' link shifts */
1626
- int depth; /* mj_value recursion guard — the reader walks hostile
1627
- * bytes, not Ruby's heap: nesting is unbounded in the
1628
- * input, the stack is not (the parser's own
1629
- * max_depth=1000 is the same law) */
1630
- } mj_ctx;
1631
-
1632
- static int mj_fail(mj_ctx* c) {
1633
- c->failed = YEPTRIS_ERROR_UNSUPPORTED;
1634
- return -1;
1635
- }
1636
-
1637
- static int mj_byte(mj_ctx* c, unsigned char* out) {
1638
- if (c->s.at >= c->s.len)
1639
- return mj_fail(c);
1640
- *out = (unsigned char)c->s.p[c->s.at++];
1641
- return 0;
1642
- }
1643
-
1644
- /* the w_long inverse — mirrors m_w_long's encode exactly:
1645
- * 0x00 -> 0; 1..4 -> positive size form k=b; 5..127 -> small +(
1646
- * b-5); 129..250 -> small -(251-b); 251..255 -> negative size form
1647
- * k=256-b (payload = LE(2^(8k) - m)); 128 unreachable. */
1648
- static int mj_long(mj_ctx* c, int64_t* out) {
1649
- unsigned char b;
1650
- if (mj_byte(c, &b) != 0)
1651
- return -1;
1652
- if (b == 0) {
1653
- *out = 0;
1654
- return 0;
1655
- }
1656
- if (b <= 4) { /* positive size form, k = b */
1657
- int64_t v = 0;
1658
- for (int i = 0; i < (int)b; i++) {
1659
- unsigned char d;
1660
- if (mj_byte(c, &d) != 0)
1661
- return -1;
1662
- v |= (int64_t)d << (8 * i);
1663
- }
1664
- *out = v;
1665
- return 0;
1666
- }
1667
- if (b <= 127) {
1668
- *out = (int64_t)b - 5;
1669
- return 0;
1670
- }
1671
- if (b >= 129 && b <= 250) {
1672
- *out = (int64_t)b - 251;
1673
- return 0;
1674
- }
1675
- { /* 251..255: negative size form */
1676
- int bytes = 256 - (int)b;
1677
- int64_t v = 0;
1678
- for (int i = 0; i < bytes; i++) {
1679
- unsigned char d;
1680
- if (mj_byte(c, &d) != 0)
1681
- return -1;
1682
- v |= (int64_t)d << (8 * i);
1683
- }
1684
- *out = v - ((int64_t)1 << (8 * bytes));
1685
- return 0;
1686
- }
1687
- }
1688
-
1689
- static int mj_ivar_pairs(mj_ctx* c);
1690
- static int mj_value(mj_ctx* c);
1691
- static int mj_discard_value(mj_ctx* c);
1692
-
1693
- static int mj_string_body(mj_ctx* c, int64_t len) {
1694
- if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
1695
- return mj_fail(c);
1696
- emit_dq_json(c->w, c->s.p + c->s.at, (uint32_t)len);
1697
- c->s.at += (size_t)len;
1698
- return 0;
1699
- }
1700
-
1701
- /* a symbol: ':' + long(len) + bytes — registers in the table; the
1702
- * name is emitted as a JSON string when emit is set */
1703
- static int mj_symbol(mj_ctx* c, int emit) {
1704
- int64_t len;
1705
- if (mj_long(c, &len) != 0)
1706
- return -1;
1707
- if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
1708
- return mj_fail(c);
1709
- const char* name = c->s.p + c->s.at;
1710
- uint32_t nlen = (uint32_t)len;
1711
- c->s.at += (size_t)len;
1712
- if (c->syms_n == c->syms_cap) {
1713
- c->syms_cap = c->syms_cap ? c->syms_cap * 2 : 16;
1714
- c->syms = (mj_sym*)realloc(c->syms, c->syms_cap * sizeof(*c->syms));
1715
- if (!c->syms)
1716
- return mj_fail(c);
1717
- }
1718
- c->syms[c->syms_n].p = name;
1719
- c->syms[c->syms_n].len = nlen;
1720
- c->syms_n++;
1721
- if (emit)
1722
- emit_dq_json(c->w, name, nlen);
1723
- return 0;
1724
- }
1725
-
1726
- /* the count + (symbol, value) pairs that follow an 'I' wrapper's
1727
- * wrapped value. Parsed and DISCARDED — encoding/freeze ivars have
1728
- * no JSON spelling; the symbol still enters the ';' table. */
1729
- static int mj_ivar_pairs(mj_ctx* c) {
1730
- int64_t count;
1731
- if (mj_long(c, &count) != 0)
1732
- return -1;
1733
- for (int64_t i = 0; i < count; i++) {
1734
- unsigned char nt;
1735
- if (mj_byte(c, &nt) != 0)
1736
- return -1;
1737
- if (nt == ':') {
1738
- if (mj_symbol(c, 0) != 0)
1739
- return -1; /* the name: tabled, not emitted */
1740
- } else if (nt == ';') {
1741
- int64_t sidx;
1742
- if (mj_long(c, &sidx) != 0)
1743
- return -1;
1744
- if (sidx < 0 || (size_t)sidx >= c->syms_n)
1745
- return mj_fail(c);
1746
- } else {
1747
- return mj_fail(c); /* ivar names are symbols only */
1748
- }
1749
- if (mj_discard_value(c) != 0)
1750
- return -1; /* the ivar value */
1751
- }
1752
- return 0;
1753
- }
1754
-
1755
- /* stream-position-only walk: consumes one value, emits nothing */
1756
-
1757
- #define MJ_DEPTH_MAX 1000
1758
-
1759
- /* the discard twin rides the same budget (its 'I'/'['/'{' recursion is
1760
- * just as hostile-byte reachable) */
1761
- static int mj_discard_value_inner(mj_ctx* c);
1762
-
1763
- static int mj_discard_value(mj_ctx* c) {
1764
- if (c->depth >= MJ_DEPTH_MAX)
1765
- return mj_fail(c);
1766
- c->depth++;
1767
- int rc = mj_discard_value_inner(c);
1768
- c->depth--;
1769
- return rc;
1770
- }
1771
-
1772
- static int mj_discard_value_inner(mj_ctx* c) {
1773
- unsigned char t;
1774
- int rc = mj_byte(c, &t);
1775
- if (rc != 0)
1776
- return -1;
1777
- switch (t) {
1778
- case '0':
1779
- case 'T':
1780
- case 'F':
1781
- return 0;
1782
- case 'i':
1783
- case ';':
1784
- case '@': {
1785
- int64_t v;
1786
- return mj_long(c, &v);
1787
- }
1788
- case 'l': {
1789
- unsigned char sign;
1790
- int64_t nlimbs;
1791
- if (mj_byte(c, &sign) != 0)
1792
- return -1;
1793
- if (sign != 0x2b && sign != 0x2d)
1794
- return mj_fail(c);
1795
- if (mj_long(c, &nlimbs) != 0)
1796
- return -1;
1797
- if (nlimbs < 0 || (uint64_t)(nlimbs * 2) > (uint64_t)(c->s.len - c->s.at))
1798
- return mj_fail(c);
1799
- c->s.at += (size_t)(nlimbs * 2);
1800
- return 0;
1801
- }
1802
- case 'f':
1803
- case '"': {
1804
- int64_t len;
1805
- if (mj_long(c, &len) != 0)
1806
- return -1;
1807
- if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
1808
- return mj_fail(c);
1809
- c->s.at += (size_t)len;
1810
- return 0;
1811
- }
1812
- case 'I':
1813
- if (mj_discard_value(c) != 0)
1814
- return -1;
1815
- return mj_ivar_pairs(c);
1816
- case ':':
1817
- return mj_symbol(c, 0);
1818
- case '[': {
1819
- int64_t count;
1820
- if (mj_long(c, &count) != 0)
1821
- return -1;
1822
- for (int64_t i = 0; i < count; i++) {
1823
- if (mj_discard_value(c) != 0)
1824
- return -1;
1825
- }
1826
- return 0;
1827
- }
1828
- case '{': {
1829
- int64_t count;
1830
- if (mj_long(c, &count) != 0)
1831
- return -1;
1832
- for (int64_t i = 0; i < count; i++) {
1833
- if (mj_discard_value(c) != 0)
1834
- return -1; /* key */
1835
- if (mj_discard_value(c) != 0)
1836
- return -1; /* value */
1837
- }
1838
- return 0;
1839
- }
1840
- default:
1841
- return mj_fail(c);
1842
- }
1843
- }
1844
-
1845
- /* the memo'd span of a registered object, copied to the cursor. The
1846
- * span lives earlier in the same buffer (append-only), so the copy
1847
- * is memmove-safe. */
1848
- static int mj_emit_memo(mj_ctx* c, const mj_obj* o) {
1849
- if (getenv("MJ_DEBUG")) {
1850
- char prev[13];
1851
- uint32_t k = o->out_len < 12 ? (uint32_t)o->out_len : 12;
1852
- memcpy(prev, c->w->p + o->out_off, k);
1853
- prev[k] = '\0';
1854
- fprintf(stderr, "memo off=%zu len=%zu: %s\n", o->out_off, o->out_len, prev);
1855
- }
1856
- if (o->out_len == 0)
1857
- return mj_fail(c); /* not memoized yet: a cycle */
1858
- wr_reserve(c->w, o->out_len);
1859
- if (c->w->oom)
1860
- return -1;
1861
- memmove(c->w->p + c->w->len, c->w->p + o->out_off, o->out_len);
1862
- wr_col_update(c->w, c->w->p + c->w->len, (uint32_t)o->out_len);
1863
- c->w->len += o->out_len;
1864
- /* the raw advance bypasses wr_put: hand back the reservation it
1865
- * would have consumed, or the next write rides past cap */
1866
- c->w->reserve -= o->out_len;
1867
- c->w->last = c->w->p[c->w->len - 1];
1868
- return 0;
1869
- }
1870
-
1871
- static int mj_value_inner(mj_ctx* c);
1872
-
1873
- /* the wrapper owns the recursion budget: hostile bytes can nest arrays
1874
- * without bound, the stack cannot (the parser's max_depth=1000 law) */
1875
- static int mj_value(mj_ctx* c) {
1876
- if (c->depth >= MJ_DEPTH_MAX)
1877
- return mj_fail(c);
1878
- c->depth++;
1879
- int rc = mj_value_inner(c);
1880
- c->depth--;
1881
- return rc;
1882
- }
1883
-
1884
- static int mj_value_inner(mj_ctx* c) {
1885
- unsigned char t;
1886
- size_t tpos = c->s.at;
1887
- int reg_idx = -1;
1888
- size_t span_start = 0;
1889
- int register_this = !c->no_reg;
1890
- c->no_reg = 0;
1891
- if (mj_byte(c, &t) != 0)
1892
- return -1;
1893
- /* arrays, hashes, strings, floats, bignums and anything
1894
- * 'I'-wrapped register — ONCE per construct, at the outer byte
1895
- * (the wrapper's no_reg suppresses the inner) (0-based stream
1896
- * order). Bignums hold a slot but Ruby never emits a link to one
1897
- * (the second occurrence re-emits in full); the entry only keeps
1898
- * the slot count aligned for later links. Integers, symbols and
1899
- * the other immediates never register. */
1900
- if (register_this && (t == '[' || t == '{' || t == '"' || t == 'f' || t == 'I' || t == 'l')) {
1901
- if (c->objs_n == c->objs_cap) {
1902
- c->objs_cap = c->objs_cap ? c->objs_cap * 2 : 16;
1903
- c->objs = (mj_obj*)realloc(c->objs, c->objs_cap * sizeof(*c->objs));
1904
- if (!c->objs)
1905
- return mj_fail(c);
1906
- }
1907
- c->objs[c->objs_n].pos = tpos;
1908
- c->objs[c->objs_n].out_off = c->w->len;
1909
- c->objs[c->objs_n].out_len = 0;
1910
- reg_idx = (int)c->objs_n;
1911
- c->objs_n++;
1912
- span_start = c->w->len;
1913
- }
1914
- switch (t) {
1915
- case '0':
1916
- wr_put(c->w, "null", 4);
1917
- break;
1918
- case 'T':
1919
- wr_put(c->w, "true", 4);
1920
- break;
1921
- case 'F':
1922
- wr_put(c->w, "false", 5);
1923
- break;
1924
- case 'i': {
1925
- int64_t v;
1926
- if (mj_long(c, &v) != 0)
1927
- return -1;
1928
- char buf[24];
1929
- int n = snprintf(buf, sizeof buf, "%lld", (long long)v);
1930
- wr_put(c->w, buf, (uint32_t)n);
1931
- break;
1932
- }
1933
- case 'l': {
1934
- /* the bignum: sign byte '+'/'-' + w_long(byte count) + LE
1935
- * bytes; the i64-representable range emits, wider falls back */
1936
- unsigned char sign;
1937
- if (mj_byte(c, &sign) != 0)
1938
- return -1;
1939
- if (sign != 0x2b && sign != 0x2d)
1940
- return mj_fail(c);
1941
- int64_t nlimbs;
1942
- if (mj_long(c, &nlimbs) != 0)
1943
- return -1;
1944
- if (nlimbs < 1 || nlimbs > 4)
1945
- return mj_fail(c); /* beyond 64 bits: fall back */
1946
- int64_t v = 0;
1947
- for (int64_t i = 0; i < nlimbs; i++) {
1948
- unsigned char lo, hi;
1949
- if (mj_byte(c, &lo) != 0 || mj_byte(c, &hi) != 0)
1950
- return -1;
1951
- /* shift in the unsigned domain: the signed form put
1952
- * 0xFFFF into the sign bit at limb 3 (UB, UBSan-flagged) */
1953
- v |= (int64_t)((uint64_t)(lo | ((uint64_t)hi << 8)) << (16 * i));
1954
- }
1955
- if (sign == 0x2d)
1956
- v = -v;
1957
- char buf[24];
1958
- int n = snprintf(buf, sizeof buf, "%lld", (long long)v);
1959
- wr_put(c->w, buf, (uint32_t)n);
1960
- break;
1961
- }
1962
- case 'f': {
1963
- int64_t len;
1964
- if (mj_long(c, &len) != 0)
1965
- return -1;
1966
- if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
1967
- return mj_fail(c);
1968
- char tmp[64];
1969
- uint32_t tl = (uint32_t)len < sizeof tmp - 1 ? (uint32_t)len : (uint32_t)sizeof tmp - 1;
1970
- memcpy(tmp, c->s.p + c->s.at, tl);
1971
- tmp[tl] = '\0';
1972
- c->s.at += (size_t)len;
1973
- double d = strtod(tmp, NULL);
1974
- if (!isfinite(d))
1975
- return mj_fail(c); /* JSON has no inf/nan spelling — and a
1976
- * strtod OVERFLOW ("1e999") produces
1977
- * one from text that never spells it */
1978
- char buf[40];
1979
- int wl = yep_d2s_json(d, buf);
1980
- wr_put(c->w, buf, (uint32_t)wl);
1981
- break;
1982
- }
1983
- case '"': {
1984
- int64_t len;
1985
- if (mj_long(c, &len) != 0)
1986
- return -1;
1987
- int rc = mj_string_body(c, len);
1988
- if (rc != 0)
1989
- return -1;
1990
- break;
1991
- }
1992
- case 'I': {
1993
- /* the wrapped value IS the payload: re-dispatch into it, then
1994
- * the ivar pairs ride. The inner dispatch must not register —
1995
- * the construct registered at the 'I' (the double entry
1996
- * shifted every later '@' link) */
1997
- c->no_reg = 1;
1998
- if (mj_value(c) != 0) {
1999
- c->no_reg = 0;
2000
- return -1;
2001
- }
2002
- c->no_reg = 0;
2003
- if (mj_ivar_pairs(c) != 0)
2004
- return -1;
2005
- break;
2006
- }
2007
- case ':': {
2008
- /* a bare symbol VALUE: emitted as its name string */
2009
- if (mj_symbol(c, 1) != 0)
2010
- return -1;
2011
- break;
2012
- }
2013
- case ';': {
2014
- int64_t idx;
2015
- if (mj_long(c, &idx) != 0)
2016
- return -1;
2017
- if (idx < 0 || (size_t)idx >= c->syms_n)
2018
- return mj_fail(c);
2019
- emit_dq_json(c->w, c->syms[idx].p, c->syms[idx].len);
2020
- break;
2021
- }
2022
- case '@': {
2023
- /* an object link: the registered span memmoves to the cursor */
2024
- int64_t idx;
2025
- if (mj_long(c, &idx) != 0)
2026
- return -1;
2027
- if (idx < 0 || (size_t)idx >= c->objs_n)
2028
- return mj_fail(c);
2029
- if (mj_emit_memo(c, &c->objs[idx]) != 0)
2030
- return -1;
2031
- break;
2032
- }
2033
- case '[':
2034
- case '{': {
2035
- int64_t count;
2036
- if (mj_long(c, &count) != 0)
2037
- return -1;
2038
- if (count < 0)
2039
- return mj_fail(c);
2040
- wr_byte(c->w, t == '[' ? '[' : '{');
2041
- const char* sep = c->compact ? "," : ", ";
2042
- uint32_t sep_len = c->compact ? 1u : 2u;
2043
- for (int64_t i = 0; i < count; i++) {
2044
- if (i > 0)
2045
- wr_put(c->w, sep, sep_len);
2046
- if (t == '[') {
2047
- if (mj_value(c) != 0)
2048
- return -1;
2049
- } else {
2050
- /* the key: a string (with its 'I' wrapper) or a
2051
- * symbol — JSON keys are strings */
2052
- unsigned char kt;
2053
- size_t ktpos = c->s.at;
2054
- if (mj_byte(c, &kt) != 0)
2055
- return -1;
2056
- if (kt == 'I') {
2057
- if (mj_value(c) != 0)
2058
- return -1;
2059
- if (mj_ivar_pairs(c) != 0)
2060
- return -1;
2061
- } else if (kt == '"') {
2062
- /* the key registers like any string object: its
2063
- * span is memo'd for later '@' key links */
2064
- int kreg = (int)c->objs_n;
2065
- size_t kspan = c->w->len;
2066
- if (kreg >= 0) {
2067
- if (c->objs_n == c->objs_cap) {
2068
- c->objs_cap = c->objs_cap ? c->objs_cap * 2 : 16;
2069
- c->objs = (mj_obj*)realloc(c->objs, c->objs_cap * sizeof(*c->objs));
2070
- if (!c->objs)
2071
- return mj_fail(c);
2072
- }
2073
- c->objs[c->objs_n].pos = ktpos;
2074
- c->objs[c->objs_n].out_off = c->w->len;
2075
- c->objs[c->objs_n].out_len = 0;
2076
- c->objs_n++;
2077
- }
2078
- int64_t len;
2079
- if (mj_long(c, &len) != 0)
2080
- return -1;
2081
- int rc = mj_string_body(c, len);
2082
- if (rc != 0)
2083
- return -1;
2084
- if (kreg >= 0) {
2085
- c->objs[kreg].out_off = kspan;
2086
- c->objs[kreg].out_len = c->w->len - kspan;
2087
- }
2088
- } else if (kt == ':') {
2089
- if (mj_symbol(c, 1) != 0)
2090
- return -1;
2091
- } else if (kt == ';') {
2092
- int64_t idx;
2093
- if (mj_long(c, &idx) != 0)
2094
- return -1;
2095
- if (idx < 0 || (size_t)idx >= c->syms_n)
2096
- return mj_fail(c);
2097
- emit_dq_json(c->w, c->syms[idx].p, c->syms[idx].len);
2098
- } else if (kt == '@') {
2099
- int64_t idx;
2100
- if (mj_long(c, &idx) != 0)
2101
- return -1;
2102
- if (idx < 0 || (size_t)idx >= c->objs_n)
2103
- return mj_fail(c);
2104
- if (mj_emit_memo(c, &c->objs[idx]) != 0)
2105
- return -1;
2106
- } else {
2107
- return mj_fail(c);
2108
- }
2109
- wr_put(c->w, c->compact ? ":" : ": ", c->compact ? 1u : 2u);
2110
- if (mj_value(c) != 0)
2111
- return -1;
2112
- }
2113
- }
2114
- wr_byte(c->w, t == '[' ? ']' : '}');
2115
- break;
2116
- }
2117
- default:
2118
- return mj_fail(c);
2119
- }
2120
- if (reg_idx >= 0) {
2121
- c->objs[reg_idx].out_off = span_start;
2122
- c->objs[reg_idx].out_len = c->w->len - span_start;
2123
- }
2124
- return 0;
2125
- }
2126
-
2127
- YEPTRIS_API YeptrisStatus yeptris_json_from_marshal(const char* m, size_t m_len, int compact,
2128
- char** out, size_t* out_len) {
2129
- if (out == NULL || out_len == NULL)
2130
- return YEPTRIS_ERROR_ARG;
2131
- *out = NULL;
2132
- *out_len = 0;
2133
- if (m == NULL || m_len < 2 || (unsigned char)m[0] != 0x04 || (unsigned char)m[1] != 0x08)
2134
- return YEPTRIS_ERROR_UNSUPPORTED;
2135
- yep_emitter em;
2136
- memset(&em, 0, sizeof(em));
2137
- yep_writer* w = &em.w;
2138
- w->sc_dec = NULL;
2139
- w->sc_i = 0;
2140
- w->json = 1;
2141
- w->json_compact = compact ? 1 : 0;
2142
- w->json_pretty = 0;
2143
- w->canonical = 0;
2144
- w->explicit_doc_start = 0;
2145
- w->best_width = 0;
2146
- w->sv_input = NULL;
2147
- w->sv_arena = NULL;
2148
- w->sink = NULL;
2149
- w->force_flow = 0;
2150
- w->in_flow = 0;
2151
- w->flow_item = 0;
2152
- w->unserializable = 0;
2153
-
2154
- mj_ctx c;
2155
- memset(&c, 0, sizeof(c));
2156
- c.w = w;
2157
- c.s.p = m;
2158
- c.s.len = m_len;
2159
- c.s.at = 2; /* the 4.8 header */
2160
- c.compact = compact ? 1 : 0;
2161
-
2162
- /* one wet pass into a growable buffer (the #352 slice-2 shape) */
2163
- size_t est = m_len * 2 + 64;
2164
- char* buf = (char*)malloc(est);
2165
- if (!buf)
2166
- return YEPTRIS_ERROR_MEMORY;
2167
- w->p = buf;
2168
- w->cap = est;
2169
- w->grow = 1;
2170
-
2171
- mj_value(&c);
2172
-
2173
- int failed = c.failed;
2174
- size_t wrote = w->len;
2175
- buf = w->p;
2176
- free(c.syms);
2177
- free(c.objs);
2178
- if (failed || w->oom) {
2179
- free(buf);
2180
- return failed ? (YeptrisStatus)failed : YEPTRIS_ERROR_MEMORY;
2181
- }
2182
- buf[wrote] = '\0';
2183
- *out = buf;
2184
- *out_len = wrote;
2185
- return YEPTRIS_OK;
2186
- }
2187
-
2188
1704
  size_t yep_emit_run(yep_emitter* em, int dry) {
2189
1705
  yep_writer* w = &em->w;
2190
1706
  const yep_dom* d = em->doc->dom;