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.
- checksums.yaml +4 -4
- data/README.adoc +9 -0
- data/ext/yeptris_native/json_ruby.c +43 -11
- data/lib/yeptris/3.4/native.so +0 -0
- data/lib/yeptris/ffi.rb +5 -0
- data/lib/yeptris/json.rb +48 -2
- data/lib/yeptris/node.rb +12 -3
- data/lib/yeptris/psych/visitors.rb +1 -1
- data/lib/yeptris/yaml.rb +1 -1
- data/lib/yeptris.rb +1 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +17 -5
- data/vendor/libyeptris/src/yeptris/dom/dom.c +52 -2
- data/vendor/libyeptris/src/yeptris/dom/ytape.c +238 -16
- data/vendor/libyeptris/src/yeptris/emit/emit.c +22 -1
- data/vendor/libyeptris/src/yeptris/emit/style.c +13 -1
- data/vendor/libyeptris/src/yeptris/emit/writer.c +395 -54
- data/vendor/libyeptris/src/yeptris/emit/writer.h +7 -0
- data/vendor/libyeptris/src/yeptris/events/values.c +1 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.c +1178 -127
- data/vendor/libyeptris/src/yeptris/parse/engine.h +3 -0
- data/vendor/libyeptris/src/yeptris/parse/numbers.c +28 -3
- data/vendor/libyeptris/src/yeptris/parse/scalars.h +12 -0
- data/vendor/libyeptris/src/yeptris/parse.c +12 -4
- data/vendor/libyeptris/src/yeptris/scan/scan.c +42 -0
- data/vendor/libyeptris/src/yeptris/scan/scan.h +7 -0
- data/vendor/libyeptris/src/yeptris/tape.c +88 -1
- data/vendor/libyeptris/src/yeptris/tape_in.h +2 -0
- metadata +1 -1
|
@@ -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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
413
|
-
break;
|
|
431
|
+
return 1; /* noncharacter: only its escape re-parses */
|
|
414
432
|
}
|
|
415
433
|
}
|
|
416
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
/*
|
|
609
|
-
*
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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,
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) &&
|
|
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
|
-
|
|
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) {
|