yeptris 0.6.59.1-arm-linux-eabihf → 0.6.61.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 (50) 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 +71 -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 +73 -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/dom/dom.c +12 -1
  22. data/vendor/libyeptris/src/yeptris/dom/ytape.c +56 -7
  23. data/vendor/libyeptris/src/yeptris/emit/emit.c +93 -13
  24. data/vendor/libyeptris/src/yeptris/emit/writer.c +243 -652
  25. data/vendor/libyeptris/src/yeptris/emit/writer.h +25 -8
  26. data/vendor/libyeptris/src/yeptris/events/push.c +28 -3
  27. data/vendor/libyeptris/src/yeptris/events/values.c +46 -0
  28. data/vendor/libyeptris/src/yeptris/marshal/to_json.c +650 -0
  29. data/vendor/libyeptris/src/yeptris/parse/engine.c +611 -130
  30. data/vendor/libyeptris/src/yeptris/parse/engine.h +5 -0
  31. data/vendor/libyeptris/src/yeptris/parse/stream_iter.c +358 -0
  32. data/vendor/libyeptris/src/yeptris/parse.c +216 -44
  33. data/vendor/libyeptris/src/yeptris/resolve/compat11.c +3 -6
  34. data/vendor/libyeptris/src/yeptris/resolve/core12.c +11 -3
  35. data/vendor/libyeptris/src/yeptris/resolve/full12.c +201 -0
  36. data/vendor/libyeptris/src/yeptris/resolve/json12.c +12 -6
  37. data/vendor/libyeptris/src/yeptris/resolve/resolver.h +38 -0
  38. data/vendor/libyeptris/src/yeptris/resolve/tags.c +192 -0
  39. data/vendor/libyeptris/src/yeptris/scan/scan.c +22 -7
  40. data/vendor/libyeptris/src/yeptris/tape.c +24 -5
  41. data/vendor/libyeptris/src/yeptris/validate/compile.c +39 -3
  42. data/vendor/libyeptris/src/yeptris/validate/internal.h +223 -7
  43. data/vendor/libyeptris/src/yeptris/validate/jsonschema.c +645 -115
  44. data/vendor/libyeptris/src/yeptris/validate/output.c +246 -0
  45. data/vendor/libyeptris/src/yeptris/validate/regexp.c +623 -157
  46. data/vendor/libyeptris/src/yeptris/validate/registry.c +118 -0
  47. data/vendor/libyeptris/src/yeptris/validate/resources.c +601 -0
  48. data/vendor/libyeptris/src/yeptris/validate/stream.c +1155 -0
  49. data/vendor/libyeptris/src/yeptris/validate/validate.c +288 -145
  50. 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]);
@@ -448,7 +539,33 @@ static void emit_literal(yep_writer* w, const yep_dnode* n, int parent_col) {
448
539
  /* Canonical scalar (13B): typed words and numbers plain, everything
449
540
  * else double-quoted; floats re-print through the shortest printer so
450
541
  * canonical output is a fixed point (parse of canonical re-prints
451
- * identically — the byte-stability gate). */
542
+ * identically — the byte-stability gate). The DOM stores only the tag
543
+ * + source text, so the typed arms re-derive the VALUE from the
544
+ * resolver's own tables (#937): the bool table decides true/false
545
+ * (the old length guess spelled every non-4-char true-word false), the
546
+ * int decode spells JSON-unspellable forms (0x1F/0o7) in decimal, and
547
+ * the inf/nan words ride quoted in JSON mode (no JSON infinity). */
548
+ static int json_int_token(const char* p, uint32_t n) {
549
+ /* the strict JSON int token: -?(0|[1-9][0-9]*) — leading zeros
550
+ * and a '+' are YAML-spellable but not JSON (those decode) */
551
+ uint32_t i = 0;
552
+ if (i < n && p[0] == '-') {
553
+ i = 1;
554
+ }
555
+ if (i >= n) {
556
+ return 0;
557
+ }
558
+ if (p[i] == '0') {
559
+ return i + 1 == n;
560
+ }
561
+ for (uint32_t k = i; k < n; k++) {
562
+ if (p[k] < '0' || p[k] > '9') {
563
+ return 0;
564
+ }
565
+ }
566
+ return 1;
567
+ }
568
+
452
569
  static void emit_canonical_scalar(yep_writer* w, const yep_dnode* n) {
453
570
  yep_view vdec = wv(w, n->value);
454
571
  const char* p = (const char*)vdec.p;
@@ -462,17 +579,24 @@ static void emit_canonical_scalar(yep_writer* w, const yep_dnode* n) {
462
579
  wr_put(w, "~", 1);
463
580
  }
464
581
  return;
465
- case YEPTRIS_TAG_BOOL:
466
- if (len == 4) { /* true/TRUE/True/null-ish variants */
467
- wr_put(w, (p[0] == 't' || p[0] == 'T') ? "true" : "null", 4);
468
- } else { /* false family (5 chars) */
469
- wr_put(w, "false", 5);
582
+ case YEPTRIS_TAG_BOOL: {
583
+ int bv = yep_tag_bool_value(p, len);
584
+ if (bv < 0) {
585
+ break; /* not a bool word: quoted (the resolver only
586
+ tags the table's words; defense) */
470
587
  }
588
+ wr_put(w, bv ? "true" : "false", bv ? 4u : 5u);
471
589
  return;
590
+ }
472
591
  case YEPTRIS_TAG_FLOAT: {
473
592
  const char* word = yep_tag_float_word(p, len);
474
593
  if (word != NULL) {
475
- wr_put(w, word, (uint32_t)strlen(word));
594
+ uint32_t wl = (uint32_t)strlen(word);
595
+ if (w->json) {
596
+ emit_dq_json(w, word, wl);
597
+ } else {
598
+ wr_put(w, word, wl);
599
+ }
476
600
  return;
477
601
  }
478
602
  char buf[40];
@@ -493,8 +617,23 @@ static void emit_canonical_scalar(yep_writer* w, const yep_dnode* n) {
493
617
  break; /* unparseable view: fall through to quoting */
494
618
  }
495
619
  case YEPTRIS_TAG_INT:
496
- wr_put(w, p, len);
497
- return;
620
+ if (!w->json || json_int_token(p, len)) {
621
+ /* YAML keeps the source text (stable, lossless); JSON
622
+ * keeps JSON-number-shaped text (big ints stay exact) */
623
+ wr_put(w, p, len);
624
+ return;
625
+ }
626
+ {
627
+ int compat = w->resolver == yep_resolver_compat11();
628
+ int64_t v;
629
+ if (yep_tag_int64(p, len, compat, &v) == 0) {
630
+ char buf[24];
631
+ int nl = snprintf(buf, sizeof buf, "%lld", (long long)v);
632
+ wr_put(w, buf, (uint32_t)nl);
633
+ return;
634
+ }
635
+ }
636
+ break; /* undecodable int text: quoted keeps JSON valid */
498
637
  default:
499
638
  break;
500
639
  }
@@ -521,6 +660,13 @@ enum { YEP_SC_NOTHING = 0, YEP_SC_PLAIN, YEP_SC_SQ, YEP_SC_DQ, YEP_SC_LITERAL, Y
521
660
  * The block map's explicit-key promotion consults the same predicate:
522
661
  * a key whose content demotes to DQ re-parses as a QUOTED key and must
523
662
  * ride simple, or round two's render differs (the nightly's round 118). */
663
+
664
+ /* The flow arm below is a per-byte switch ON PURPOSE (#904 item 4's
665
+ * measured skip): the stopset SIMD kernel variant was measured at
666
+ * ~+3% median on flow-json emit (the only shape that enters the arm;
667
+ * 4-sample A/B, build-bench harness) — under the ledger's ~5% bar for
668
+ * touching the hot derive path, and within run noise under a loaded
669
+ * machine. PERF-LEDGER carries the numbers. */
524
670
  static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
525
671
  int has_tag, int no_exotic, int plain_fact) {
526
672
  /* the parse-time fact bits (#352 lanes 2-3): a dnode verified clean
@@ -533,7 +679,9 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
533
679
  if (w->in_flow && len > 0) {
534
680
  /* flow context: the indicators TERMINATE a plain scalar — a
535
681
  * raw ']' inside "{}" re-parses as a stray close (the
536
- * nightly's "555555ta]" explicit-key crasher) */
682
+ * nightly's "555555ta]" explicit-key crasher). Per-byte switch
683
+ * ON PURPOSE: the stopset kernel variant measured under the
684
+ * skip bar (the comment above; PERF-LEDGER has the numbers). */
537
685
  for (uint32_t k = 0; k < len; k++) {
538
686
  switch (p[k]) {
539
687
  case ',':
@@ -699,10 +847,21 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
699
847
  break; /* an empty plain value emits as nothing (libyaml's null
700
848
  rendering, issue #290) */
701
849
  case YEP_SC_PLAIN:
702
- wr_put(w, p, len);
850
+ if (w->block_fold && !as_key && !w->in_flow && len > 0) {
851
+ /* #903: block-context width folding; keys never fold
852
+ * (libyaml: allow_breaks = !simple_key_context) and flow
853
+ * members keep the flow walk */
854
+ emit_plain_folded(w, p, len, parent_col + w->indent);
855
+ } else {
856
+ wr_put(w, p, len);
857
+ }
703
858
  break;
704
859
  case YEP_SC_SQ:
705
- emit_sq(w, p, len);
860
+ if (w->block_fold && !as_key && !w->in_flow && len > 0) {
861
+ emit_sq_folded(w, p, len, parent_col + w->indent);
862
+ } else {
863
+ emit_sq(w, p, len);
864
+ }
706
865
  break;
707
866
  case YEP_SC_LITERAL:
708
867
  emit_literal(w, n, parent_col);
@@ -1172,13 +1331,16 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1172
1331
  * key, plain unpropertied scalar value, both safety-proven by
1173
1332
  * the exact predicates sc_route consults (no memo touch, so
1174
1333
  * the dry/wet sc_i streams stay aligned). Anything else falls
1175
- * through untouched — parity by construction. */
1334
+ * through untouched — parity by construction. block_fold
1335
+ * (#903) declines the arm wholesale: the fused write's
1336
+ * arithmetic column model has no fold walk, and the general
1337
+ * body emits the identical bytes with folding. */
1176
1338
  const yep_dnode* kn0 = yep_dom_node(d, child);
1177
1339
  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) {
1340
+ if (!w->canonical && !w->json && !w->force_flow && !w->block_fold && vn0 != NULL &&
1341
+ kn0->kind == 0 && kn0->style == 1 && kn0->anchor.len == 0 && kn0->tag.len == 0 &&
1342
+ kn0->count == 0 && vn0->kind == 0 && vn0->style == 1 && vn0->anchor.len == 0 &&
1343
+ vn0->tag.len == 0 && vn0->count == 0) {
1182
1344
  yep_view kv = wv(w, kn0->value);
1183
1345
  yep_view vv = wv(w, vn0->value);
1184
1346
  uint32_t f_ind = 0, f_nl = 0, f_total = 0;
@@ -1337,11 +1499,11 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1337
1499
  * with a complex key forcing the explicit form) */
1338
1500
  if (!(vn->kind == 0 || vn->kind == 3 || vn->flow || vn->count == 0)) {
1339
1501
  int seq_val = (vn->kind == 1 && !vn->flow);
1340
- int val_col = seq_val ? content_col : content_col + 2;
1502
+ int val_col = seq_val ? content_col : content_col + w->indent;
1341
1503
  emit_props_tail(w, vn);
1342
1504
  wr_byte(w, '\n');
1343
1505
  wr_indent(w, val_col);
1344
- emit_node(em, kn->next_sibling, val_col - 2, 0);
1506
+ emit_node(em, kn->next_sibling, val_col - w->indent, 0);
1345
1507
  } else {
1346
1508
  emit_node(em, kn->next_sibling, content_col, 0);
1347
1509
  }
@@ -1368,15 +1530,16 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1368
1530
  } else {
1369
1531
  /* nested block collection: next line. libyaml parity:
1370
1532
  * a SEQUENCE value sits at the key's column (zero
1371
- * extra indent); mappings indent +2 (issue #95) */
1533
+ * extra indent); mappings indent one level (issue #95;
1534
+ * the level width is the #903 indent option) */
1372
1535
  int seq_val = (vn->kind == 1 && !vn->flow);
1373
- int val_col = seq_val ? content_col : content_col + 2;
1536
+ int val_col = seq_val ? content_col : content_col + w->indent;
1374
1537
  emit_props_tail(w, vn);
1375
1538
  wr_byte(w, '\n');
1376
1539
  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);
1540
+ /* emit_block_seq dashes at parent_col+indent: pass
1541
+ * val_col-indent so the dashes land at val_col exactly */
1542
+ emit_node(em, kn->next_sibling, val_col - w->indent, 0);
1380
1543
  }
1381
1544
  }
1382
1545
  pair++;
@@ -1419,11 +1582,19 @@ static void emit_block_seq(yep_emitter* em, uint32_t id, int content_col) {
1419
1582
  wr_byte(w, '-');
1420
1583
  emit_props_tail(w, cn);
1421
1584
  wr_byte(w, '\n');
1422
- wr_indent(w, content_col + 2);
1585
+ wr_indent(w, content_col + w->indent);
1423
1586
  emit_node(em, child, content_col, 0);
1424
1587
  } else {
1425
1588
  /* compact: a block collection starts right after "- " */
1426
1589
  wr_put(w, "- ", 2);
1590
+ if (w->indent > 2 && cn->kind != 0 && cn->kind != 3 && !cn->flow && cn->count > 0) {
1591
+ /* libyaml/PyYAML's indent shape (#903): a nested
1592
+ * block collection's first line rides at the LEVEL
1593
+ * column (dash + indent), not the dash+2 token width
1594
+ * — indent=4 spells "- a: 1" / "- - x"; scalars,
1595
+ * aliases and empty/flow collections keep "- x" */
1596
+ wr_indent(w, w->indent - 2);
1597
+ }
1427
1598
  emit_node(em, child, content_col, 0);
1428
1599
  }
1429
1600
  idx++;
@@ -1459,7 +1630,10 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key)
1459
1630
  emit_anchor_ex(em, n);
1460
1631
  wr_put(w, "{}", 2);
1461
1632
  } else {
1462
- emit_block_map(em, id, parent_col + 2);
1633
+ /* the root (parent_col < 0, the caller's -2) rides column
1634
+ * 0; every nesting level adds the indent option (#903) */
1635
+ int base = (parent_col < 0) ? parent_col + 2 : parent_col + w->indent;
1636
+ emit_block_map(em, id, base);
1463
1637
  }
1464
1638
  return;
1465
1639
  }
@@ -1469,12 +1643,31 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key)
1469
1643
  emit_anchor_ex(em, n);
1470
1644
  wr_put(w, "[]", 2);
1471
1645
  } else {
1472
- emit_block_seq(em, id, parent_col + 2);
1646
+ int base = (parent_col < 0) ? parent_col + 2 : parent_col + w->indent;
1647
+ emit_block_seq(em, id, base);
1473
1648
  }
1474
1649
  return;
1475
1650
  }
1476
1651
  emit_tag(w, n);
1477
1652
  emit_anchor_ex(em, n);
1653
+ if (parent_col < 0 && !w->canonical && !w->json && n->kind == 0 && n->style <= 1) {
1654
+ /* A DOCUMENT-ROOT empty scalar rides the quoted form: libyaml
1655
+ * renders "--- ''" and never a bare marker with nothing after
1656
+ * it. A plain-empty root emitted as NOTHING (the value law of
1657
+ * issue #290, "k:" — kept below for non-root contexts) sheds
1658
+ * the root node entirely; the re-parse of that stream produced
1659
+ * an ANY-styled empty root whose render is "" — and the bytes
1660
+ * diverged on round two (the nightly's r201 fuzz_roundtrip
1661
+ * byte-instability catch, ">\n! \n !<aaa: bbb\n...\n<NEL><NEL>",
1662
+ * whose second document's break-only content parses to a
1663
+ * plain-empty root). Quoting the root's emptiness makes every
1664
+ * empty-root spelling converge on the same fixpoint bytes. */
1665
+ yep_view vdec = wv(w, n->value);
1666
+ if (vdec.len == 0) {
1667
+ emit_dq(w, (const char*)vdec.p, vdec.len);
1668
+ return;
1669
+ }
1670
+ }
1478
1671
  if (parent_col < 0) {
1479
1672
  parent_col = 0; /* root scalar: block body at column 2 */
1480
1673
  }
@@ -1574,617 +1767,6 @@ static void emit_json_node(yep_emitter* em, uint32_t id) {
1574
1767
  emit_canonical_scalar(w, n);
1575
1768
  }
1576
1769
 
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
1770
  size_t yep_emit_run(yep_emitter* em, int dry) {
2189
1771
  yep_writer* w = &em->w;
2190
1772
  const yep_dom* d = em->doc->dom;
@@ -2239,7 +1821,16 @@ size_t yep_emit_run(yep_emitter* em, int dry) {
2239
1821
  yep_view tdec3 = wv(w, root->tag);
2240
1822
  wr_tag_token(w, &tdec3);
2241
1823
  }
2242
- if (root->anchor.len > 0) {
1824
+ if (root->anchor.len > 0 && !anchor_name_unspellable(w, root)) {
1825
+ /* the r235 law: the lead line writes the name UNQUOTED
1826
+ * and anchor names have no quoting -- a name the spell
1827
+ * law rejects has no safe lead-line spelling. The r235
1828
+ * nightly's riding-colon names ("&:" and the artifact's
1829
+ * "&k:\"" — run 42739927952, crash-5de6f9ff) re-parsed
1830
+ * as an anchored empty KEY of the follow-on collection
1831
+ * (the anchor lost, s1 != s2, byte growth); the
1832
+ * colon-tail law fails the serialize like every other
1833
+ * anchor write path (the r228 "&: |" law) */
2243
1834
  if (root->tag.len > 0) {
2244
1835
  wr_byte(w, ' '); /* props need the separator */
2245
1836
  }