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.
- checksums.yaml +4 -4
- data/lib/yeptris/3.4/native.so +0 -0
- 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 +273 -18
- 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 +433 -50
- data/vendor/libyeptris/src/yeptris/emit/writer.h +7 -0
- data/vendor/libyeptris/src/yeptris/events/values.c +83 -44
- data/vendor/libyeptris/src/yeptris/parse/engine.c +1189 -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,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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
/*
|
|
569
|
-
*
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) &&
|
|
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
|
-
|
|
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);
|