yeptris 0.6.32.1-arm-linux-eabihf → 0.6.33.2-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,26 @@ 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
+ }
171
193
  /* U+FFFE/U+FFFF are noncharacters: the printable validator
172
194
  * rejects them raw, so emitting them raw makes the document
173
195
  * un-re-parseable (the nightly's "["\\uFFFE"]" catch). Their
@@ -378,44 +400,79 @@ static void emit_canonical_scalar(yep_writer* w, const yep_dnode* n) {
378
400
  /* The fidelity-mode emission route for one scalar (#352): the dry
379
401
  * pass derives it exactly once; the wet pass consumes the recorded
380
402
  * byte and skips the multiline/blockable/safety analysis entirely. */
381
- 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 };
382
404
  #define YEP_SC_MEMO_MIN 32u /* bytes; derive-below, memoize-above */
383
405
 
384
- static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
385
- int has_tag) {
386
- /* U+0085 is a break (only its \\N escape is byte-stable) and U+FEFF
387
- * raw is the BOM (re-parse would strip it): both force the
388
- * double-quoted route BEFORE the plain fast path can take them
389
- * (the nightly's fuzz_roundtrip crashers). The NEL scan matches
390
- * the UTF-8 PAIR only — a lone 0x85 is a continuation byte
391
- * (U+315B = E3 85 9B). */
392
- int has_nel = 0;
393
- 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) {
394
416
  for (uint32_t k = 0; k + 1 < len; k++) {
395
417
  if ((unsigned char)p[k] == 0xc2 && (unsigned char)p[k + 1] == 0x85) {
396
- has_nel = 1;
397
- break;
418
+ return 1;
398
419
  }
399
420
  /* LS/PS are breaks like NEL: only their escapes are stable */
400
421
  if ((unsigned char)p[k] == 0xe2 && k + 2 < len && (unsigned char)p[k + 1] == 0x80 &&
401
422
  ((unsigned char)p[k + 2] == 0xa8 || (unsigned char)p[k + 2] == 0xa9)) {
402
- has_nel = 1;
403
- break;
423
+ return 1;
404
424
  }
405
425
  if ((unsigned char)p[k] == 0xef && k + 2 < len && (unsigned char)p[k + 1] == 0xbb &&
406
426
  (unsigned char)p[k + 2] == 0xbf) {
407
- has_bom = 1;
408
- break;
427
+ return 1;
409
428
  }
410
429
  if ((unsigned char)p[k] == 0xef && k + 2 < len && (unsigned char)p[k + 1] == 0xbf &&
411
430
  ((unsigned char)p[k + 2] == 0xbe || (unsigned char)p[k + 2] == 0xbf)) {
412
- has_nel = 1; /* noncharacter: only its escape re-parses */
413
- break;
431
+ return 1; /* noncharacter: only its escape re-parses */
414
432
  }
415
433
  }
416
- 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)) {
417
440
  return YEP_SC_DQ;
418
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
+ }
419
476
  if ((sty_in == 1) && len > 0 &&
420
477
  (as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len))) {
421
478
  return YEP_SC_PLAIN; /* plain-safe implies no breaks: memchr skipped */
@@ -437,6 +494,15 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
437
494
  }
438
495
  uint8_t sty = sty_in;
439
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
+ }
440
506
  if (multiline && as_key) {
441
507
  return YEP_SC_DQ;
442
508
  }
@@ -512,6 +578,13 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
512
578
  }
513
579
  switch (route) {
514
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
+ }
515
588
  break; /* an empty plain value emits as nothing (libyaml's null
516
589
  rendering, issue #290) */
517
590
  case YEP_SC_PLAIN:
@@ -534,6 +607,42 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
534
607
  * flow context raw, and renaming preserves the binding semantics.
535
608
  * Deterministic by definition order -> byte-stable across roundtrips.
536
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
+
537
646
  static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
538
647
  yep_writer* w = &em->w;
539
648
  if (n->anchor.len == 0) {
@@ -551,6 +660,7 @@ static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
551
660
  int nl = snprintf(name, sizeof(name), "a%u", idx);
552
661
  wr_put(w, name, (uint32_t)nl);
553
662
  } else {
663
+ wr_anchor_check(w, &adec);
554
664
  wr_put(w, (const char*)adec.p, adec.len);
555
665
  }
556
666
  wr_byte(w, ' ');
@@ -559,6 +669,19 @@ static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
559
669
  static void emit_alias_ex(yep_emitter* em, const yep_dnode* n) {
560
670
  yep_writer* w = &em->w;
561
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
+ }
562
685
  wr_byte(w, '*');
563
686
  if (w->canonical) {
564
687
  uint32_t idx = yep_nametab_get(&em->canon_names, name_v);
@@ -599,24 +722,88 @@ static void wr_tag_uri(yep_writer* w, const yep_view* v) {
599
722
  /* Explicit tag, verbatim URI form ("!<tag:…> "). Never skipped: a
600
723
  * recorded tag always re-emits (implied-by-style skipping made the
601
724
  * roundtrip byte-unstable: plain+!!str and ""+!!str collided). */
602
- static void emit_tag(yep_writer* w, const yep_dnode* n) {
603
- if (n->tag.len == 0) {
604
- 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
+ }
605
756
  }
606
- yep_view tdec = wv(w, n->tag);
607
- if (tdec.len > 0 && tdec.p[0] == '!') {
608
- /* already a shorthand ("!ruby/object:K", "!foo"): verbatim —
609
- * the !<...> wrapper is for verbatim URIs only */
610
- yep_view rest = {tdec.p + 1, tdec.len - 1};
611
- wr_byte(w, '!');
612
- wr_tag_uri(w, &rest);
613
- wr_byte(w, ' ');
614
- 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
+ }
615
794
  }
616
795
  wr_byte(w, '!');
617
796
  wr_byte(w, '<');
618
- wr_tag_uri(w, &tdec);
797
+ wr_tag_uri(w, tdec);
619
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);
620
807
  wr_byte(w, ' ');
621
808
  }
622
809
 
@@ -627,14 +814,12 @@ static void emit_props_tail(yep_writer* w, const yep_dnode* n) {
627
814
  if (n->tag.len > 0) {
628
815
  wr_byte(w, ' ');
629
816
  yep_view tdec2 = wv(w, n->tag);
630
- wr_byte(w, '!');
631
- wr_byte(w, '<');
632
- wr_tag_uri(w, &tdec2);
633
- wr_byte(w, '>');
817
+ wr_tag_token(w, &tdec2);
634
818
  }
635
819
  if (n->anchor.len > 0) {
636
820
  wr_byte(w, ' ');
637
821
  yep_view adec2 = wv(w, n->anchor);
822
+ wr_anchor_check(w, &adec2);
638
823
  wr_byte(w, '&');
639
824
  wr_put(w, (const char*)adec2.p, adec2.len);
640
825
  }
@@ -642,8 +827,60 @@ static void emit_props_tail(yep_writer* w, const yep_dnode* n) {
642
827
 
643
828
  static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key);
644
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
+
645
880
  static void emit_flow(yep_emitter* em, uint32_t id) {
646
881
  yep_writer* w = &em->w;
882
+ int saved_flow = w->in_flow;
883
+ w->in_flow = 1;
647
884
  const yep_dom* d = em->doc->dom;
648
885
  const yep_dnode* n = yep_dom_node(d, id);
649
886
  emit_tag(w, n);
@@ -680,6 +917,17 @@ static void emit_flow(yep_emitter* em, uint32_t id) {
680
917
  }
681
918
  if (map) {
682
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
+ }
683
931
  if (explicit_key) {
684
932
  wr_put(w, "? ", 2);
685
933
  emit_node(em, child, 0, 1);
@@ -698,7 +946,9 @@ static void emit_flow(yep_emitter* em, uint32_t id) {
698
946
  child = cn->next_sibling;
699
947
  cn = yep_dom_node(d, child);
700
948
  } else {
949
+ w->flow_item = 1; /* a null item must ride '~' */
701
950
  emit_node(em, child, 0, 0);
951
+ w->flow_item = 0;
702
952
  }
703
953
  idx++;
704
954
  child = cn->next_sibling;
@@ -709,6 +959,7 @@ static void emit_flow(yep_emitter* em, uint32_t id) {
709
959
  w->pretty_depth--;
710
960
  }
711
961
  wr_byte(w, map ? '}' : ']');
962
+ w->in_flow = saved_flow;
712
963
  }
713
964
 
714
965
  /* Block map: first key at the cursor (the caller placed it at
@@ -735,7 +986,8 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
735
986
  vn0->count == 0) {
736
987
  yep_view kv = wv(w, kn0->value);
737
988
  yep_view vv = wv(w, vn0->value);
738
- 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) &&
739
991
  (vv.len == 0 || yep_style_plain_safe((const char*)vv.p, vv.len))) {
740
992
  /* fused member write: plain-safe spans carry no breaks,
741
993
  * so the col scan is arithmetic and one reservation
@@ -790,21 +1042,93 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
790
1042
  wr_indent(w, content_col);
791
1043
  }
792
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
+ }
793
1080
  if (complex_key) {
794
1081
  w->force_flow = 1; /* collections as flow keys: compact and
795
1082
  free of the block '?' layout edges */
796
1083
  wr_put(w, "? ", 2);
797
- emit_props_tail(w, kn); /* the key's props ride "? " */
798
- if (kn->anchor.len > 0 || kn->tag.len > 0) {
799
- wr_byte(w, '\n');
800
- 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);
801
1107
  }
802
- emit_node(em, child, content_col, 1);
803
1108
  wr_byte(w, '\n');
804
1109
  wr_indent(w, content_col);
805
1110
  wr_put(w, ": ", 2);
806
1111
  w->force_flow = 0;
807
- 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
+ }
808
1132
  } else {
809
1133
  emit_tag(w, kn);
810
1134
  emit_anchor_ex(em, kn);
@@ -865,17 +1189,24 @@ static void emit_block_seq(yep_emitter* em, uint32_t id, int content_col) {
865
1189
  child = cn->next_sibling;
866
1190
  continue;
867
1191
  }
868
- wr_put(w, "- ", 2);
869
- if (cn->kind != 0 && cn->kind != 3 && !cn->flow &&
1192
+ if (cn->kind != 0 && cn->kind != 3 && !cn->flow && cn->count > 0 &&
870
1193
  (cn->anchor.len > 0 || cn->tag.len > 0)) {
871
1194
  /* props may not precede a dash: they ride the dash line and
872
- * 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, '-');
873
1203
  emit_props_tail(w, cn);
874
1204
  wr_byte(w, '\n');
875
1205
  wr_indent(w, content_col + 2);
876
1206
  emit_node(em, child, content_col, 0);
877
1207
  } else {
878
1208
  /* compact: a block collection starts right after "- " */
1209
+ wr_put(w, "- ", 2);
879
1210
  emit_node(em, child, content_col, 0);
880
1211
  }
881
1212
  idx++;
@@ -894,9 +1225,14 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key)
894
1225
  emit_alias_ex(em, n);
895
1226
  return;
896
1227
  }
897
- 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)) {
898
1230
  /* force_flow holds through the subtree: a complex key's nested
899
- * 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) */
900
1236
  emit_flow(em, id);
901
1237
  return;
902
1238
  }
@@ -965,17 +1301,22 @@ size_t yep_emit_run(yep_emitter* em, int dry) {
965
1301
  4);
966
1302
  }
967
1303
  if (root != NULL && (root->kind == 1 || root->kind == 2) && !root->flow && !w->canonical &&
968
- (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) */
969
1310
  /* root block-collection props ride their own leading line;
970
1311
  * scalars and flow collections carry them inline */
971
1312
  if (root->tag.len > 0) {
972
1313
  yep_view tdec3 = wv(w, root->tag);
973
- wr_byte(w, '!');
974
- wr_byte(w, '<');
975
- wr_put(w, (const char*)tdec3.p, tdec3.len);
976
- wr_byte(w, '>');
1314
+ wr_tag_token(w, &tdec3);
977
1315
  }
978
1316
  if (root->anchor.len > 0) {
1317
+ if (root->tag.len > 0) {
1318
+ wr_byte(w, ' '); /* props need the separator */
1319
+ }
979
1320
  yep_view adec3 = wv(w, root->anchor);
980
1321
  wr_byte(w, '&');
981
1322
  wr_put(w, (const char*)adec3.p, adec3.len);
@@ -55,6 +55,13 @@ typedef struct yep_writer {
55
55
  size_t cap;
56
56
  int grow;
57
57
  int oom;
58
+ int in_flow; /* scalars inside flow collections: plain must drop
59
+ ',', '[', ']', '{', '}' — flow terminators */
60
+ int flow_item; /* the NEXT scalar is a flow-SEQ item: a null must
61
+ emit '~' (a bare nothing reads as "[, x]") */
62
+ int unserializable; /* an anchor name that cannot ride raw in flow
63
+ context: the document has no safe spelling —
64
+ serialize fails rather than corrupt */
58
65
  } yep_writer;
59
66
 
60
67
  typedef struct yep_emitter {
@@ -445,6 +445,7 @@ int yep_values_from_input(const char* yaml, size_t len, int schema_compat, yep_v
445
445
  .on_block_open = NULL,
446
446
  .on_block_item = NULL};
447
447
  /* strict JSON carries no anchors: no nametab reserve needed */
448
+ yep_engine_front_done(eng);
448
449
  if (yep_engine_run(eng, data, data_len, &sink) == 0 && transform(c, &store) == 0) {
449
450
  prc = 0;
450
451
  } else if (c->oom) {