yeptris 0.6.31.2-arm-linux-eabihf → 0.6.33.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.
@@ -18,6 +18,8 @@
18
18
 
19
19
  #include "writer.h"
20
20
 
21
+ #include "../scan/scan.h"
22
+
21
23
  #include <string.h>
22
24
 
23
25
  #include "style.h"
@@ -168,6 +170,40 @@ static void emit_dq(yep_writer* w, const char* p, uint32_t n) {
168
170
  i += 2;
169
171
  continue;
170
172
  }
173
+ /* C1 controls (U+0080..U+009F, the UTF-8 pairs c2 80..c2 9f)
174
+ * are NON-PRINTABLE: raw, the re-parse's printability gate
175
+ * rejects the very document we just wrote (the nightly's
176
+ * "\x999" catch — \x99 decodes to U+0099 and the raw pair
177
+ * c2 99 cannot ride). Psych escapes the whole C1 range; the
178
+ * \xNN escape re-enters the doc the same way it left. */
179
+ if (c == 0xc2 && i + 1 < n && (unsigned char)p[i + 1] >= 0x80 &&
180
+ (unsigned char)p[i + 1] <= 0x9f) {
181
+ static const char hd[] = "0123456789ABCDEF";
182
+ if (run) {
183
+ wr_put(w, p + i - run, run);
184
+ run = 0;
185
+ }
186
+ wr_byte(w, '\\');
187
+ wr_byte(w, 'x');
188
+ wr_byte(w, hd[(unsigned char)p[i + 1] >> 4]);
189
+ wr_byte(w, hd[(unsigned char)p[i + 1] & 0xf]);
190
+ i++;
191
+ continue;
192
+ }
193
+ /* U+FFFE/U+FFFF are noncharacters: the printable validator
194
+ * rejects them raw, so emitting them raw makes the document
195
+ * un-re-parseable (the nightly's "["\\uFFFE"]" catch). Their
196
+ * escapes re-enter the doc the same way they left. */
197
+ if (c == 0xef && i + 2 < n && (unsigned char)p[i + 1] == 0xbf &&
198
+ ((unsigned char)p[i + 2] == 0xbe || (unsigned char)p[i + 2] == 0xbf)) {
199
+ if (run) {
200
+ wr_put(w, p + i - run, run);
201
+ run = 0;
202
+ }
203
+ wr_put(w, (unsigned char)p[i + 2] == 0xbe ? "\\uFFFE" : "\\uFFFF", 6);
204
+ i += 2;
205
+ continue;
206
+ }
171
207
  /* U+FEFF must never ride raw: at the buffer head it IS the
172
208
  * BOM and re-parse would strip it (the nightly's UTF-32LE
173
209
  * catch); Psych escapes it everywhere ("") */
@@ -364,39 +400,79 @@ static void emit_canonical_scalar(yep_writer* w, const yep_dnode* n) {
364
400
  /* The fidelity-mode emission route for one scalar (#352): the dry
365
401
  * pass derives it exactly once; the wet pass consumes the recorded
366
402
  * byte and skips the multiline/blockable/safety analysis entirely. */
367
- enum { YEP_SC_NOTHING = 0, YEP_SC_PLAIN, YEP_SC_SQ, YEP_SC_DQ, YEP_SC_LITERAL };
403
+ enum { YEP_SC_NOTHING = 0, YEP_SC_PLAIN, YEP_SC_SQ, YEP_SC_DQ, YEP_SC_LITERAL, YEP_SC_TILDE };
368
404
  #define YEP_SC_MEMO_MIN 32u /* bytes; derive-below, memoize-above */
369
405
 
370
- static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
371
- int has_tag) {
372
- /* U+0085 is a break (only its \\N escape is byte-stable) and U+FEFF
373
- * raw is the BOM (re-parse would strip it): both force the
374
- * double-quoted route BEFORE the plain fast path can take them
375
- * (the nightly's fuzz_roundtrip crashers). The NEL scan matches
376
- * the UTF-8 PAIR only — a lone 0x85 is a continuation byte
377
- * (U+315B = E3 85 9B). */
378
- int has_nel = 0;
379
- int has_bom = 0;
406
+ /* U+0085 is a break (only its \\N escape is byte-stable), U+FEFF raw is
407
+ * the BOM (re-parse would strip it), and U+2028/29 plus the
408
+ * noncharacters are their twins: content carrying any of these forces
409
+ * the double-quoted route BEFORE the plain fast path can take it (the
410
+ * nightly's fuzz_roundtrip crashers). The NEL scan matches the UTF-8
411
+ * PAIR only — a lone 0x85 is a continuation byte (U+315B = E3 85 9B).
412
+ * The block map's explicit-key promotion consults the same predicate:
413
+ * a key whose content demotes to DQ re-parses as a QUOTED key and must
414
+ * ride simple, or round two's render differs (the nightly's round 118). */
415
+ static int sc_dq_forced(const char* p, uint32_t len) {
380
416
  for (uint32_t k = 0; k + 1 < len; k++) {
381
417
  if ((unsigned char)p[k] == 0xc2 && (unsigned char)p[k + 1] == 0x85) {
382
- has_nel = 1;
383
- break;
418
+ return 1;
384
419
  }
385
420
  /* LS/PS are breaks like NEL: only their escapes are stable */
386
421
  if ((unsigned char)p[k] == 0xe2 && k + 2 < len && (unsigned char)p[k + 1] == 0x80 &&
387
422
  ((unsigned char)p[k + 2] == 0xa8 || (unsigned char)p[k + 2] == 0xa9)) {
388
- has_nel = 1;
389
- break;
423
+ return 1;
390
424
  }
391
425
  if ((unsigned char)p[k] == 0xef && k + 2 < len && (unsigned char)p[k + 1] == 0xbb &&
392
426
  (unsigned char)p[k + 2] == 0xbf) {
393
- has_bom = 1;
394
- break;
427
+ return 1;
428
+ }
429
+ if ((unsigned char)p[k] == 0xef && k + 2 < len && (unsigned char)p[k + 1] == 0xbf &&
430
+ ((unsigned char)p[k + 2] == 0xbe || (unsigned char)p[k + 2] == 0xbf)) {
431
+ return 1; /* noncharacter: only its escape re-parses */
395
432
  }
396
433
  }
397
- if (has_nel || has_bom) {
434
+ return 0;
435
+ }
436
+
437
+ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
438
+ int has_tag) {
439
+ if (sc_dq_forced(p, len)) {
398
440
  return YEP_SC_DQ;
399
441
  }
442
+ if (w->in_flow && len > 0) {
443
+ /* flow context: the indicators TERMINATE a plain scalar — a
444
+ * raw ']' inside "{}" re-parses as a stray close (the
445
+ * nightly's "555555ta]" explicit-key crasher) */
446
+ for (uint32_t k = 0; k < len; k++) {
447
+ switch (p[k]) {
448
+ case ',':
449
+ case '[':
450
+ case ']':
451
+ case '{':
452
+ case '}':
453
+ case '"': /* flips the flow scanner into quoted mode */
454
+ case '\\': /* opens an escape sequence there */
455
+ case '\'': /* opens flow single-quote mode */
456
+ case '!': /* a tag indicator everywhere — "a!%!!a" rode
457
+ * plain inside a flow-rendered explicit key and
458
+ * the re-parse died in tag scanning (the
459
+ * nightly's run 38139449828) */
460
+ case '&': /* an anchor indicator — "&)J" inside a
461
+ * flow-rendered explicit key opened an anchor
462
+ * scan on ')' and the re-parse died ("did not
463
+ * find expected alphabetic or numeric
464
+ * character"; the nightly's run 38250474732) */
465
+ case '*': /* the alias twin of '&' */
466
+ case ':': /* a separating ':' — "{a: b : c}" died at the
467
+ * second ':' (the completed-pair law, catch 60);
468
+ * the nightly's run 38255134508 */
469
+ case '?': /* the explicit-key twin of ':' */
470
+ return YEP_SC_DQ;
471
+ default:
472
+ break;
473
+ }
474
+ }
475
+ }
400
476
  if ((sty_in == 1) && len > 0 &&
401
477
  (as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len))) {
402
478
  return YEP_SC_PLAIN; /* plain-safe implies no breaks: memchr skipped */
@@ -418,6 +494,15 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
418
494
  }
419
495
  uint8_t sty = sty_in;
420
496
  if (sty == 4 || sty == 5) {
497
+ /* BLOCK SCALARS ARE ILLEGAL IN FLOW: a flow-rendered explicit
498
+ * key's subtree wrote `yaml: |` + literal content lines and
499
+ * the emission could never re-parse (the nightly's run
500
+ * 38154662070, "? !\n- name: ..." — the forced-flow key
501
+ * subtree carried literal members). In flow, literal/folded
502
+ * values ride DQ. */
503
+ if (w->in_flow) {
504
+ return YEP_SC_DQ;
505
+ }
421
506
  if (multiline && as_key) {
422
507
  return YEP_SC_DQ;
423
508
  }
@@ -493,6 +578,13 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
493
578
  }
494
579
  switch (route) {
495
580
  case YEP_SC_NOTHING:
581
+ if (w->flow_item) {
582
+ /* a null flow-SEQ item must stay re-parseable: nothing
583
+ * after '[' reads as a leading comma ("[, x]", rejected by
584
+ * libyaml and Psych alike) — libyaml renders it '~' */
585
+ wr_put(w, "~", 1);
586
+ break;
587
+ }
496
588
  break; /* an empty plain value emits as nothing (libyaml's null
497
589
  rendering, issue #290) */
498
590
  case YEP_SC_PLAIN:
@@ -515,6 +607,42 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
515
607
  * flow context raw, and renaming preserves the binding semantics.
516
608
  * Deterministic by definition order -> byte-stable across roundtrips.
517
609
  * The emitter's nametab lives in yep_emitter (owner: yep_emit_run). */
610
+ /* An anchor name carrying an indicator byte rides raw fine in block
611
+ * context (the suite's W5VH/2SXE/8XYN round-trip byte-stably), but in
612
+ * FLOW context the bytes flip scanners or terminate the entry — and
613
+ * anchors have no quoting syntax. Such a document has no safe flow
614
+ * spelling: mark it unserializable; serialize fails rather than
615
+ * emitting a form that can never re-parse (the nightly's "&\""/bang/
616
+ * question anchor vein). */
617
+ static void wr_anchor_check(yep_writer* w, const yep_view* name) {
618
+ if (!w->in_flow) {
619
+ return;
620
+ }
621
+ for (uint32_t i = 0; i < name->len; i++) {
622
+ switch (name->p[i]) {
623
+ case '"':
624
+ case '\'':
625
+ case '!':
626
+ case '?':
627
+ case '&':
628
+ case '*':
629
+ case ',':
630
+ case '[':
631
+ case ']':
632
+ case '{':
633
+ case '}':
634
+ case '%':
635
+ case '@':
636
+ case '`':
637
+ case '#':
638
+ w->unserializable = 1;
639
+ return;
640
+ default:
641
+ break;
642
+ }
643
+ }
644
+ }
645
+
518
646
  static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
519
647
  yep_writer* w = &em->w;
520
648
  if (n->anchor.len == 0) {
@@ -532,6 +660,7 @@ static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
532
660
  int nl = snprintf(name, sizeof(name), "a%u", idx);
533
661
  wr_put(w, name, (uint32_t)nl);
534
662
  } else {
663
+ wr_anchor_check(w, &adec);
535
664
  wr_put(w, (const char*)adec.p, adec.len);
536
665
  }
537
666
  wr_byte(w, ' ');
@@ -540,6 +669,19 @@ static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
540
669
  static void emit_alias_ex(yep_emitter* em, const yep_dnode* n) {
541
670
  yep_writer* w = &em->w;
542
671
  yep_view name_v = wv(w, n->value);
672
+ if (w->in_flow && !w->canonical) {
673
+ /* the alias twin of wr_anchor_check: a flow alias is spelled
674
+ * raw and the re-parse truncates the name -- a trailing ':'
675
+ * reads as the key/value separator there ("*n:}" names "n",
676
+ * the block definition "n:" went unmatched: the nightly's
677
+ * run 39021984786, "alias references an undefined anchor"
678
+ * 3:15), and quote bytes hit the engine's flow-alias law.
679
+ * Block aliases ride raw (2SXE/Y2GN round-trip byte-stably). */
680
+ wr_anchor_check(w, &name_v);
681
+ if (name_v.len > 0 && name_v.p[name_v.len - 1] == ':') {
682
+ w->unserializable = 1;
683
+ }
684
+ }
543
685
  wr_byte(w, '*');
544
686
  if (w->canonical) {
545
687
  uint32_t idx = yep_nametab_get(&em->canon_names, name_v);
@@ -556,25 +698,112 @@ static void emit_alias_ex(yep_emitter* em, const yep_dnode* n) {
556
698
  }
557
699
  }
558
700
 
701
+ /* Tag bytes outside ns-tag-char ride %XX (the parse side decodes them
702
+ * per the URI rule — a decoded newline emitted raw was the nightly's
703
+ * byte-instability crasher "!%0a"). Uppercase hex, libyaml's form. */
704
+ static void wr_tag_uri(yep_writer* w, const yep_view* v) {
705
+ static const char hd[] = "0123456789ABCDEF";
706
+ const unsigned char* p = (const unsigned char*)v->p;
707
+ for (uint32_t i = 0; i < v->len; i++) {
708
+ unsigned char c = p[i];
709
+ if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
710
+ c == '-' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || c == '&' ||
711
+ c == '=' || c == '+' || c == '$' || c == ',' || c == '.' || c == '!' || c == '~' ||
712
+ c == '*' || c == '\'' || c == '(' || c == ')') {
713
+ wr_byte(w, (char)c);
714
+ } else {
715
+ wr_byte(w, '%');
716
+ wr_byte(w, hd[c >> 4]);
717
+ wr_byte(w, hd[c & 0xf]);
718
+ }
719
+ }
720
+ }
721
+
559
722
  /* Explicit tag, verbatim URI form ("!<tag:…> "). Never skipped: a
560
723
  * recorded tag always re-emits (implied-by-style skipping made the
561
724
  * roundtrip byte-unstable: plain+!!str and ""+!!str collided). */
562
- static void emit_tag(yep_writer* w, const yep_dnode* n) {
563
- if (n->tag.len == 0) {
564
- return;
725
+ /* The tag token without separators: the core namespace rides the
726
+ * DEFAULT secondary handle (libyaml writes !!str, never
727
+ * !<tag:yaml.org,2002:str>); a leading-'!' shorthand re-escapes its
728
+ * interior bangs (a local tag's URI can carry them — re-parsed raw
729
+ * they read as handles and the meaning flips); everything else is a
730
+ * verbatim !<URI> wrap. Hosts pass the full core URI — the verbatim
731
+ * shorthand "!!str" as a node tag collides with a LOCAL tag of the
732
+ * same bytes (the binding's chevron-key dump regressed there). */
733
+ static void wr_tag_token(yep_writer* w, const yep_view* tdec) {
734
+ static const char k_core_ns[] = "tag:yaml.org,2002:";
735
+ if (tdec->len > sizeof(k_core_ns) - 1 &&
736
+ memcmp(tdec->p, k_core_ns, sizeof(k_core_ns) - 1) == 0) {
737
+ yep_view suffix = {tdec->p + sizeof(k_core_ns) - 1, tdec->len - (sizeof(k_core_ns) - 1)};
738
+ int scan_ok = 1;
739
+ for (uint32_t si = 0; si < suffix.len; si++) {
740
+ if (!yep_scan_tag_char((unsigned char)suffix.p[si])) {
741
+ scan_ok = 0;
742
+ break;
743
+ }
744
+ }
745
+ /* the shorthand only holds if the suffix re-scans: the parse
746
+ * side's tag charset terminates on ',' (and flow indicators —
747
+ * our lenience vs libyaml's URI set), so a suffix carrying one
748
+ * re-parses truncated and the round-trip dies (the nightly's
749
+ * run 38132505901, the verbatim tag with an interior ',');
750
+ * the verbatim !<...> wrap carries those */
751
+ if (scan_ok) {
752
+ wr_put(w, "!!", 2);
753
+ wr_tag_uri(w, &suffix);
754
+ return;
755
+ }
565
756
  }
566
- yep_view tdec = wv(w, n->tag);
567
- if (tdec.len > 0 && tdec.p[0] == '!') {
568
- /* already a shorthand ("!ruby/object:K", "!foo"): verbatim —
569
- * the !<...> wrapper is for verbatim URIs only */
570
- wr_put(w, (const char*)tdec.p, tdec.len);
571
- wr_byte(w, ' ');
572
- return;
757
+ if (tdec->len > 0 && tdec->p[0] == '!') {
758
+ yep_view rest = {tdec->p + 1, tdec->len - 1};
759
+ /* the LOCAL shorthand re-scans too: the parse side's tag
760
+ * charset terminates on ',' (our lenience vs libyaml's URI
761
+ * set), so a suffix carrying one re-parses truncated — the
762
+ * same law the core-namespace arm gained (catch 47); the
763
+ * verbatim !<...> wrap carries those (the nightly's run
764
+ * 38165476712, "!<!<<b,,,,,-,,,,ar> :yam" emitted
765
+ * "!%3C%3Cb,,,,,-,,,,ar" and died at the first comma) */
766
+ int scan_ok = 1;
767
+ for (uint32_t si = 0; si < rest.len; si++) {
768
+ if (!yep_scan_tag_char((unsigned char)rest.p[si])) {
769
+ scan_ok = 0;
770
+ break;
771
+ }
772
+ }
773
+ if (scan_ok) {
774
+ wr_byte(w, '!');
775
+ uint32_t run = 0;
776
+ for (uint32_t i = 0; i < rest.len; i++) {
777
+ if (rest.p[i] == '!') {
778
+ if (run > 0) {
779
+ yep_view seg = {rest.p + i - run, run};
780
+ wr_tag_uri(w, &seg);
781
+ run = 0;
782
+ }
783
+ wr_put(w, "%21", 3);
784
+ } else {
785
+ run++;
786
+ }
787
+ }
788
+ if (run > 0) {
789
+ yep_view seg = {rest.p + rest.len - run, run};
790
+ wr_tag_uri(w, &seg);
791
+ }
792
+ return;
793
+ }
573
794
  }
574
795
  wr_byte(w, '!');
575
796
  wr_byte(w, '<');
576
- wr_put(w, (const char*)tdec.p, tdec.len);
797
+ wr_tag_uri(w, tdec);
577
798
  wr_byte(w, '>');
799
+ }
800
+
801
+ static void emit_tag(yep_writer* w, const yep_dnode* n) {
802
+ if (n->tag.len == 0) {
803
+ return;
804
+ }
805
+ yep_view tdec = wv(w, n->tag);
806
+ wr_tag_token(w, &tdec);
578
807
  wr_byte(w, ' ');
579
808
  }
580
809
 
@@ -585,14 +814,12 @@ static void emit_props_tail(yep_writer* w, const yep_dnode* n) {
585
814
  if (n->tag.len > 0) {
586
815
  wr_byte(w, ' ');
587
816
  yep_view tdec2 = wv(w, n->tag);
588
- wr_byte(w, '!');
589
- wr_byte(w, '<');
590
- wr_put(w, (const char*)tdec2.p, tdec2.len);
591
- wr_byte(w, '>');
817
+ wr_tag_token(w, &tdec2);
592
818
  }
593
819
  if (n->anchor.len > 0) {
594
820
  wr_byte(w, ' ');
595
821
  yep_view adec2 = wv(w, n->anchor);
822
+ wr_anchor_check(w, &adec2);
596
823
  wr_byte(w, '&');
597
824
  wr_put(w, (const char*)adec2.p, adec2.len);
598
825
  }
@@ -600,8 +827,60 @@ static void emit_props_tail(yep_writer* w, const yep_dnode* n) {
600
827
 
601
828
  static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key);
602
829
 
830
+ /* The simple-key law counts the WRITTEN token. A quoted render
831
+ * expands escapes (a '"' doubles, controls ride \xNN, non-ASCII may);
832
+ * raw-length predicates let escape-heavy scalars through (the
833
+ * nightly's run 38145696566: a ~1000-raw key rendering ~3000
834
+ * written). Conservative factor-4 estimate for every expandable
835
+ * byte — overestimating only takes the (always-legal) explicit form. */
836
+ static int key_written_over_limit(yep_writer* w, const yep_dnode* kn) {
837
+ uint64_t est = 2; /* the quotes */
838
+ yep_view kv_ = wv(w, kn->value);
839
+ const unsigned char* q = (const unsigned char*)kv_.p;
840
+ uint32_t n = kv_.len;
841
+ /* NO early return on an empty value: the tag arm below must run —
842
+ * a TAGGED EMPTY key writes ~2300 bytes of escaped tag and the
843
+ * re-parse's simple-key law rejects it ("the nightly's run
844
+ * 38200627503") */
845
+ if (q != NULL) {
846
+ for (uint32_t i = 0; i < n; i++) {
847
+ est += (q[i] < 0x20 || q[i] >= 0x7F || q[i] == '"' || q[i] == '\\') ? 4u : 1u;
848
+ }
849
+ }
850
+ if (kn->anchor.len > 0) {
851
+ /* the key's ANCHOR rides the same written token: the re-parse
852
+ * counts the '&name' bytes in the simple-key candidate, and
853
+ * anchor names carry interior colons (Y2GN) — a ~1700-byte
854
+ * name blew a 2-byte key past the law (the nightly's run
855
+ * 38206305189) */
856
+ est += (uint64_t)kn->anchor.len + 2u;
857
+ }
858
+ if (kn->tag.len > 0) {
859
+ /* the key's TAG rides the same written token: wr_tag_uri
860
+ * escapes every byte outside its URI set as %XX (even '_'),
861
+ * inflating a ~1000-byte tag past the re-parse's simple-key
862
+ * law (the nightly's run 38176016091: a ~975-raw tag wrote
863
+ * ~1030 and the emission never re-parsed) */
864
+ yep_view tv = wv(w, kn->tag);
865
+ const unsigned char* t = (const unsigned char*)tv.p;
866
+ for (uint32_t i = 0; i < tv.len; i++) {
867
+ unsigned char c = t[i];
868
+ int plain = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
869
+ (c >= 'A' && c <= 'Z') || c == '-' || c == ';' || c == '/' || c == '?' ||
870
+ c == ':' || c == '@' || c == '&' || c == '=' || c == '+' || c == '$' ||
871
+ c == ',' || c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' ||
872
+ c == '(' || c == ')';
873
+ est += plain ? 1u : 3u;
874
+ }
875
+ est += 6u; /* the !!/!< > wrapper and the separating space */
876
+ }
877
+ return est > 1024u;
878
+ }
879
+
603
880
  static void emit_flow(yep_emitter* em, uint32_t id) {
604
881
  yep_writer* w = &em->w;
882
+ int saved_flow = w->in_flow;
883
+ w->in_flow = 1;
605
884
  const yep_dom* d = em->doc->dom;
606
885
  const yep_dnode* n = yep_dom_node(d, id);
607
886
  emit_tag(w, n);
@@ -638,6 +917,17 @@ static void emit_flow(yep_emitter* em, uint32_t id) {
638
917
  }
639
918
  if (map) {
640
919
  int explicit_key = cn->anchor.len > 0 || cn->tag.len > 0 || cn->kind != 0;
920
+ if (!explicit_key && key_written_over_limit(w, cn)) {
921
+ { /* the quoted render's span vs the simple-key law */
922
+ /* the simple-key law: a >1024 key cannot ride as a
923
+ * flow simple key (the re-parse rejects it) — the
924
+ * explicit "? key : " form carries it. Strict JSON
925
+ * accepts keys of any length, so the JSON route
926
+ * parses them and the YAML emission must re-parse
927
+ * (the nightly's run 38143486175) */
928
+ explicit_key = 1;
929
+ }
930
+ }
641
931
  if (explicit_key) {
642
932
  wr_put(w, "? ", 2);
643
933
  emit_node(em, child, 0, 1);
@@ -656,7 +946,9 @@ static void emit_flow(yep_emitter* em, uint32_t id) {
656
946
  child = cn->next_sibling;
657
947
  cn = yep_dom_node(d, child);
658
948
  } else {
949
+ w->flow_item = 1; /* a null item must ride '~' */
659
950
  emit_node(em, child, 0, 0);
951
+ w->flow_item = 0;
660
952
  }
661
953
  idx++;
662
954
  child = cn->next_sibling;
@@ -667,6 +959,7 @@ static void emit_flow(yep_emitter* em, uint32_t id) {
667
959
  w->pretty_depth--;
668
960
  }
669
961
  wr_byte(w, map ? '}' : ']');
962
+ w->in_flow = saved_flow;
670
963
  }
671
964
 
672
965
  /* Block map: first key at the cursor (the caller placed it at
@@ -693,7 +986,8 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
693
986
  vn0->count == 0) {
694
987
  yep_view kv = wv(w, kn0->value);
695
988
  yep_view vv = wv(w, vn0->value);
696
- if (kv.len > 0 && yep_style_plain_key_safe((const char*)kv.p, kv.len) &&
989
+ if (kv.len > 0 && !key_written_over_limit(w, kn0) &&
990
+ yep_style_plain_key_safe((const char*)kv.p, kv.len) &&
697
991
  (vv.len == 0 || yep_style_plain_safe((const char*)vv.p, vv.len))) {
698
992
  /* fused member write: plain-safe spans carry no breaks,
699
993
  * so the col scan is arithmetic and one reservation
@@ -748,21 +1042,93 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
748
1042
  wr_indent(w, content_col);
749
1043
  }
750
1044
  int complex_key = (kn->kind != 0);
1045
+ /* a literal/folded key cannot ride the simple form: the value
1046
+ * separator would glue onto the block's CONTENT line (colons
1047
+ * are data there) and the re-parse reads a different tree —
1048
+ * the explicit "? key" form carries it. An EMPTY block-styled
1049
+ * key rides simple: it has no content line for the separator
1050
+ * to glue onto, and sc_route demotes it to the quoted form
1051
+ * anyway (the nightly's run 38177622733, ":\n! |-\n r\n:") */
1052
+ if (!complex_key && kn->style >= 4 && kn->style <= 5 && kn->value.len > 0 &&
1053
+ kn->tag.len > 0 && memchr(wv(w, kn->value).p, '\n', wv(w, kn->value).len) == NULL &&
1054
+ !sc_dq_forced(wv(w, kn->value).p, wv(w, kn->value).len)) {
1055
+ /* a TAGGED SINGLE-LINE literal/folded key keeps the block
1056
+ * form in sc_route (the #290 law), so the value separator
1057
+ * would glue onto the block's CONTENT line (colons are
1058
+ * data there) and the re-parse reads a different tree —
1059
+ * the explicit "? key" form carries it, and it keeps the
1060
+ * block form there too, so both renders agree. A MULTILINE
1061
+ * tagged key demotes to quoted even in the explicit path
1062
+ * (as_key), so promoting it would UNSTABILIZE it: round
1063
+ * two parses a quoted style and rides simple. Untagged and
1064
+ * empty block styles demote likewise (the nightly's runs
1065
+ * 38177622733 ":\n! |-\n r\n:" and 38181819580
1066
+ * ":\n! |\n r\n:") — and so does content carrying
1067
+ * NEL/BOM/LS/PS/noncharacters (sc_dq_forced): a BOM in a
1068
+ * key's content demoted the explicit form's key to DQ and
1069
+ * round two rode simple, one render behind (the nightly's
1070
+ * round 118) */
1071
+ complex_key = 1;
1072
+ }
1073
+ if (!complex_key && key_written_over_limit(w, kn)) {
1074
+ /* the simple-key law, block side: a >1024 key cannot ride
1075
+ * as a block simple key either — the explicit "? key" form
1076
+ * carries it (the flow member writer gained the same law;
1077
+ * the nightly's run 38143486175) */
1078
+ complex_key = 1;
1079
+ }
751
1080
  if (complex_key) {
752
1081
  w->force_flow = 1; /* collections as flow keys: compact and
753
1082
  free of the block '?' layout edges */
754
1083
  wr_put(w, "? ", 2);
755
- emit_props_tail(w, kn); /* the key's props ride "? " */
756
- if (kn->anchor.len > 0 || kn->tag.len > 0) {
757
- wr_byte(w, '\n');
758
- wr_indent(w, content_col + 2);
1084
+ /* the props emit ONCE: the DQ-promoted path writes its own
1085
+ * (emit_dq is bare), everything else lets emit_node/
1086
+ * emit_flow carry tag+anchor inline — the pre-emit made
1087
+ * every non-DQ explicit key re-emit its props ("? !\n ! |-
1088
+ " — the catch-56 law, explicit-key side) */
1089
+ int dq_promoted = (kn->kind == 0 && key_written_over_limit(w, kn));
1090
+ if (dq_promoted) {
1091
+ emit_props_tail(w, kn);
1092
+ if (kn->anchor.len > 0 || kn->tag.len > 0) {
1093
+ wr_byte(w, '\n');
1094
+ wr_indent(w, content_col + 2);
1095
+ }
1096
+ }
1097
+ if (dq_promoted) {
1098
+ /* the 1024-promoted SCALAR key must ride QUOTED in the
1099
+ * explicit form: plain, its interior ': ' sequences
1100
+ * re-parse through the inline simple-key promotion and
1101
+ * die on the very law that forced the promotion (the
1102
+ * nightly's run 38145696566, the "? licit k:[...]"
1103
+ * emission) */
1104
+ emit_dq(w, (const char*)wv(w, kn->value).p, (uint32_t)wv(w, kn->value).len);
1105
+ } else {
1106
+ emit_node(em, child, content_col, 1);
759
1107
  }
760
- emit_node(em, child, content_col, 1);
761
1108
  wr_byte(w, '\n');
762
1109
  wr_indent(w, content_col);
763
1110
  wr_put(w, ": ", 2);
764
1111
  w->force_flow = 0;
765
- emit_node(em, kn->next_sibling, content_col, 0);
1112
+ {
1113
+ /* the value's props ride the ':' line and a nested
1114
+ * block collection takes the fresh-line form, exactly
1115
+ * like the simple arm's path: the explicit twin
1116
+ * skipped both, so an anchored map value lost its
1117
+ * anchor (or glued it to the first key) and a later
1118
+ * alias emitted a dangling reference (the nightly's
1119
+ * run 38371134997, "? &d!se: &base\n name: Esame"
1120
+ * with a complex key forcing the explicit form) */
1121
+ if (!(vn->kind == 0 || vn->kind == 3 || vn->flow || vn->count == 0)) {
1122
+ int seq_val = (vn->kind == 1 && !vn->flow);
1123
+ int val_col = seq_val ? content_col : content_col + 2;
1124
+ emit_props_tail(w, vn);
1125
+ wr_byte(w, '\n');
1126
+ wr_indent(w, val_col);
1127
+ emit_node(em, kn->next_sibling, val_col - 2, 0);
1128
+ } else {
1129
+ emit_node(em, kn->next_sibling, content_col, 0);
1130
+ }
1131
+ }
766
1132
  } else {
767
1133
  emit_tag(w, kn);
768
1134
  emit_anchor_ex(em, kn);
@@ -823,17 +1189,24 @@ static void emit_block_seq(yep_emitter* em, uint32_t id, int content_col) {
823
1189
  child = cn->next_sibling;
824
1190
  continue;
825
1191
  }
826
- wr_put(w, "- ", 2);
827
- if (cn->kind != 0 && cn->kind != 3 && !cn->flow &&
1192
+ if (cn->kind != 0 && cn->kind != 3 && !cn->flow && cn->count > 0 &&
828
1193
  (cn->anchor.len > 0 || cn->tag.len > 0)) {
829
1194
  /* props may not precede a dash: they ride the dash line and
830
- * the collection starts on the next line (SY6V) */
1195
+ * the collection starts on the next line (SY6V). The dash
1196
+ * carries NO trailing space — emit_props_tail adds its own
1197
+ * separator, and "- " + " &a" doubled it (the nightly's run
1198
+ * 38161738783). An EMPTY propertied collection takes the
1199
+ * compact arm: emit_node's empty-form writes the props
1200
+ * itself ("- &a {}"), and the next-line form re-emitted
1201
+ * them ("- &a\n &a {}") — every round grew/duplicated */
1202
+ wr_byte(w, '-');
831
1203
  emit_props_tail(w, cn);
832
1204
  wr_byte(w, '\n');
833
1205
  wr_indent(w, content_col + 2);
834
1206
  emit_node(em, child, content_col, 0);
835
1207
  } else {
836
1208
  /* compact: a block collection starts right after "- " */
1209
+ wr_put(w, "- ", 2);
837
1210
  emit_node(em, child, content_col, 0);
838
1211
  }
839
1212
  idx++;
@@ -852,9 +1225,14 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key)
852
1225
  emit_alias_ex(em, n);
853
1226
  return;
854
1227
  }
855
- if ((n->kind == 1 || n->kind == 2) && (n->flow || w->canonical || w->json || w->force_flow)) {
1228
+ if ((n->kind == 1 || n->kind == 2) &&
1229
+ (n->flow || w->in_flow || w->canonical || w->json || w->force_flow)) {
856
1230
  /* force_flow holds through the subtree: a complex key's nested
857
- * collections all render flow (scalars are never collections) */
1231
+ * collections all render flow (scalars are never collections).
1232
+ * in_flow is the catch-55 law's collection arm: a block-styled
1233
+ * node nested inside a flow member renders FLOW — its block
1234
+ * dashes are not node content there (the nightly's run
1235
+ * 38168927841) */
858
1236
  emit_flow(em, id);
859
1237
  return;
860
1238
  }
@@ -923,17 +1301,22 @@ size_t yep_emit_run(yep_emitter* em, int dry) {
923
1301
  4);
924
1302
  }
925
1303
  if (root != NULL && (root->kind == 1 || root->kind == 2) && !root->flow && !w->canonical &&
926
- (root->tag.len > 0 || root->anchor.len > 0)) {
1304
+ root->count > 0 && (root->tag.len > 0 || root->anchor.len > 0)) {
1305
+ /* count > 0: an EMPTY propertied collection takes
1306
+ * emit_node's inline empty-form ("! &a {}") — the leading
1307
+ * props line DUPLICATED it ("! &a\n! &a {}") and every
1308
+ * round shed a line (the nightly's run 38167237445; the
1309
+ * catch-56 law, root side) */
927
1310
  /* root block-collection props ride their own leading line;
928
1311
  * scalars and flow collections carry them inline */
929
1312
  if (root->tag.len > 0) {
930
1313
  yep_view tdec3 = wv(w, root->tag);
931
- wr_byte(w, '!');
932
- wr_byte(w, '<');
933
- wr_put(w, (const char*)tdec3.p, tdec3.len);
934
- wr_byte(w, '>');
1314
+ wr_tag_token(w, &tdec3);
935
1315
  }
936
1316
  if (root->anchor.len > 0) {
1317
+ if (root->tag.len > 0) {
1318
+ wr_byte(w, ' '); /* props need the separator */
1319
+ }
937
1320
  yep_view adec3 = wv(w, root->anchor);
938
1321
  wr_byte(w, '&');
939
1322
  wr_put(w, (const char*)adec3.p, adec3.len);