yeptris 0.6.18.2-arm-linux-eabihf → 0.6.20.1-arm-linux-eabihf

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/ext/build_windows_native.rb +31 -12
  3. data/lib/yeptris/3.4/native.so +0 -0
  4. data/lib/yeptris/materializer.rb +12 -11
  5. data/lib/yeptris/psych/handler.rb +5 -0
  6. data/lib/yeptris/psych/parser.rb +8 -3
  7. data/lib/yeptris/psych/tree_builder.rb +119 -0
  8. data/lib/yeptris/psych.rb +4 -0
  9. data/lib/yeptris.rb +15 -11
  10. data/libyeptris.so +0 -0
  11. data/vendor/libyeptris/CMakeLists.txt +1 -1
  12. data/vendor/libyeptris/src/CMakeLists.txt +1 -0
  13. data/vendor/libyeptris/src/include/yeptris/events.h +8 -1
  14. data/vendor/libyeptris/src/yeptris/cbor/decode.c +1 -0
  15. data/vendor/libyeptris/src/yeptris/common/simd_text.h +47 -13
  16. data/vendor/libyeptris/src/yeptris/common/simd_text_avx2.c +86 -8
  17. data/vendor/libyeptris/src/yeptris/common/simd_text_neon.c +82 -8
  18. data/vendor/libyeptris/src/yeptris/common/simd_text_scalar.c +10 -6
  19. data/vendor/libyeptris/src/yeptris/doc.h +8 -4
  20. data/vendor/libyeptris/src/yeptris/dom/ytape.c +640 -0
  21. data/vendor/libyeptris/src/yeptris/dom/ytape.h +77 -0
  22. data/vendor/libyeptris/src/yeptris/emit/writer.c +7 -2
  23. data/vendor/libyeptris/src/yeptris/events/capture.c +5 -1
  24. data/vendor/libyeptris/src/yeptris/parse/engine.c +150 -3
  25. data/vendor/libyeptris/src/yeptris/parse/events.h +6 -0
  26. data/vendor/libyeptris/src/yeptris/parse.c +144 -42
  27. data/vendor/libyeptris/src/yeptris/scan/json.c +43 -5
  28. data/vendor/libyeptris/src/yeptris/scan/json.h +9 -2
  29. data/vendor/libyeptris/src/yeptris/tape.c +539 -163
  30. data/vendor/libyeptris/src/yeptris/tape_in.h +12 -0
  31. metadata +5 -2
  32. data/lib/yeptris/native-3.4.so +0 -0
@@ -618,200 +618,335 @@ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open
618
618
  uint8_t top_kind = p[open] == '[' ? 0 : 1;
619
619
  recs[1] =
620
620
  ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | (top_kind ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN);
621
- uint32_t top_open = 1;
622
621
  size_t count = 2;
623
- uint8_t top_expect = top_kind ? JW_KEY_OR_CLOSE : JW_VALUE_OR_CLOSE;
624
- int key_slot = top_kind ? 1 : 0;
625
622
  int depth = 1;
626
- open_at[0] = top_open;
623
+ open_at[0] = 1;
627
624
  kind[0] = top_kind;
628
625
 
629
626
  size_t i = open + 1;
630
- for (;;) {
631
- while (i < len && (p[i] == ' ' || p[i] == '\n' || p[i] == '\r' || p[i] == '\t')) {
632
- i++;
633
- }
634
- if (i >= len) {
635
- goto lreject;
636
- }
637
- size_t at = i;
638
- char c = p[i];
627
+ if (top_kind) {
628
+ goto lmap1;
629
+ }
630
+ goto lseq1;
639
631
 
640
- if (top_expect == JW_COLON) {
641
- if (c != ':') {
642
- goto lreject;
643
- }
644
- top_expect = JW_VALUE;
645
- key_slot = 0;
646
- i = at + 1;
647
- continue;
648
- }
649
- if (top_expect == JW_COMMA_OR_CLOSE) {
650
- if (c == ',') {
651
- top_expect = top_kind ? JW_KEY : JW_VALUE;
652
- key_slot = top_kind ? 1 : 0;
653
- i = at + 1;
654
- continue;
655
- }
656
- if (c != ']' && c != '}') {
657
- goto lreject;
658
- }
659
- } else if (key_slot && c != '"' && c != ']' && c != '}') {
660
- goto lreject;
661
- }
632
+ /* The specialized member loops (the #342 dispatch-chain cut): once
633
+ * inside a container the grammar is a 2-state cycle — member or
634
+ * separator — so the general chain's expect/key_slot machine and
635
+ * its per-token re-dispatch are replaced by direct loops per kind.
636
+ * Acceptance is IDENTICAL to the chain (the pinning suites hold:
637
+ * Tape suite, LenientMatchesStrict, ErrorParity, the json corpora).
638
+ *
639
+ * lmap / lseq: positioned at a member (or the container's closer).
640
+ * lvalue: scans one value (string/number/literal/container).
641
+ * lafter: positioned after a complete value — ',' or the closer.
642
+ * A close pops the frame; depth 0 ends the walk. */
643
+
644
+ #define LWS() \
645
+ do { \
646
+ while (i < len && (p[i] == ' ' || p[i] == '\n' || p[i] == '\r' || p[i] == '\t')) { \
647
+ i++; \
648
+ } \
649
+ if (i >= len) { \
650
+ goto lreject; \
651
+ } \
652
+ } while (0)
653
+
654
+ #define LPUSH(k) \
655
+ do { \
656
+ if (depth >= YEP_JSON_WALK_DEPTH) { \
657
+ goto lreject; \
658
+ } \
659
+ recs[count] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | \
660
+ (uint64_t)((k) ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN); \
661
+ kind[depth] = (k); \
662
+ open_at[depth] = (uint32_t)count; \
663
+ count++; \
664
+ depth++; \
665
+ i++; \
666
+ } while (0)
667
+
668
+ #define LCLOSE() \
669
+ do { \
670
+ uint32_t lo_ = open_at[depth - 1]; \
671
+ recs[count] = ((uint64_t)lo_ << 32) | ((uint64_t)0 << 8) | YEP_T_CLOSE; \
672
+ recs[lo_] = \
673
+ (recs[lo_] & ~(uint64_t)0xFFFFFFFF00000000u) | ((uint64_t)(uint32_t)count << 32); \
674
+ count++; \
675
+ i++; \
676
+ depth--; \
677
+ if (depth == 0) { \
678
+ goto ldone; \
679
+ } \
680
+ if (kind[depth - 1]) { \
681
+ goto lmapafter; \
682
+ } \
683
+ goto lseqafter; \
684
+ } while (0)
685
+
686
+ /* LSTR_SCAN(after) — the fused string arm, positioned at p[i] == '"'.
687
+ * One SWAR round settles the common short span; quote_scan (the SIMD
688
+ * close scan) settles longer no-escape spans, with a c0 sweep over the
689
+ * content (quote_scan never tests control bytes); the kernel runs only
690
+ * when escapes appear — its grammar authority covers the escape rules
691
+ * quote_scan skips. `after` names the post-member label. */
692
+ #define LSTR_SCAN(after) \
693
+ do { \
694
+ size_t j_ = i + 1; \
695
+ int esc_ = 0; \
696
+ if (j_ + 8 <= len) { \
697
+ uint64_t w_; \
698
+ memcpy(&w_, p + j_, 8); \
699
+ uint64_t qm_ = (w_ ^ 0x2222222222222222ull); \
700
+ uint64_t bm_ = (w_ ^ 0x5C5C5C5C5C5C5C5Cull); \
701
+ qm_ = (qm_ - 0x0101010101010101ull) & ~qm_ & 0x8080808080808080ull; \
702
+ bm_ = (bm_ - 0x0101010101010101ull) & ~bm_ & 0x8080808080808080ull; \
703
+ uint64_t c0_ = (w_ - 0x2020202020202020ull) & ~w_ & 0x8080808080808080ull; \
704
+ if (qm_ != 0 && ((bm_ | c0_) & (qm_ - 1)) == 0) { \
705
+ size_t cl_ = j_ + (size_t)yep_ctz64(qm_) / 8; \
706
+ recs[count] = ((uint64_t)((uint32_t)j_) << 32) | \
707
+ ((uint64_t)((uint32_t)(cl_ - j_)) << 8) | (uint64_t)(YEP_T_STR); \
708
+ count++; \
709
+ i = cl_ + 1; \
710
+ goto after; \
711
+ } \
712
+ } \
713
+ { \
714
+ const yep_text_kernels* kk_ = yep_text_active(); \
715
+ ptrdiff_t r_ = kk_->quote_scan(p + j_, len - j_, '"', &esc_); \
716
+ if (r_ >= 0 && esc_ == 0) { \
717
+ size_t cl_ = j_ + (size_t)r_; \
718
+ for (size_t b_ = j_; b_ < cl_;) { \
719
+ size_t room_ = cl_ - b_; \
720
+ if (room_ >= 8) { \
721
+ uint64_t v_; \
722
+ memcpy(&v_, p + b_, 8); \
723
+ if ((v_ - 0x2020202020202020ull) & ~v_ & 0x8080808080808080ull) { \
724
+ goto lreject; \
725
+ } \
726
+ b_ += 8; \
727
+ } else { \
728
+ if ((unsigned char)p[b_] < 0x20) { \
729
+ goto lreject; \
730
+ } \
731
+ b_++; \
732
+ } \
733
+ } \
734
+ recs[count] = ((uint64_t)((uint32_t)j_) << 32) | \
735
+ ((uint64_t)((uint32_t)(cl_ - j_)) << 8) | (uint64_t)(YEP_T_STR); \
736
+ count++; \
737
+ i = cl_ + 1; \
738
+ goto after; \
739
+ } \
740
+ } \
741
+ { \
742
+ size_t at_ = i; \
743
+ size_t cl_ = 0; \
744
+ if (!yep_json_string(p, len, &i, &cl_, &esc_)) { \
745
+ goto lreject; \
746
+ } \
747
+ recs[count] = ((uint64_t)((uint32_t)(at_ + 1)) << 32) | \
748
+ ((uint64_t)((uint32_t)(cl_ - at_ - 1)) << 8) | (uint64_t)(YEP_T_STR); \
749
+ count++; \
750
+ i = cl_ + 1; \
751
+ } \
752
+ goto after; \
753
+ } while (0)
754
+
755
+ /* The map member cycle — ONE contiguous region (the json-doc lesson:
756
+ * a goto web across value/key/after regions scattered the hot path and
757
+ * cost 2.7x on the flat-map shape). Values scan inline; only nested
758
+ * containers, literals, and the kernel string fallback leave the
759
+ * cycle. Acceptance is the chain's: no trailing commas, string keys
760
+ * only, `:` required, closers only in first-member position. */
761
+ lmap1: /* {} closes here */
762
+ LWS();
763
+ if (p[i] == '}') {
764
+ LCLOSE();
765
+ }
766
+ if (p[i] != '"') {
767
+ goto lreject;
768
+ }
769
+ goto lmapkey;
662
770
 
771
+ lmap: /* a member MUST follow (after ','): JW_KEY — a closer rejects */
772
+ LWS();
773
+ if (p[i] != '"') {
774
+ goto lreject;
775
+ }
776
+ lmapkey: { LSTR_SCAN(lmapcolon); }
777
+ lmapcolon:
778
+ LWS();
779
+ if (p[i] != ':') {
780
+ goto lreject;
781
+ }
782
+ i++;
783
+ LWS();
784
+ {
785
+ char c = p[i];
663
786
  if (c == '"') {
664
- size_t j = at + 1;
665
- size_t close = 0;
666
- /* SWAR settle first (the lenient walk's own fast path); the
667
- * kernel stays the authority on escapes/C0/long spans */
668
- if (j + 8 <= len) {
669
- uint64_t w;
670
- memcpy(&w, p + j, 8);
671
- uint64_t qm = (w ^ 0x2222222222222222ull);
672
- uint64_t bm = (w ^ 0x5C5C5C5C5C5C5C5Cull);
673
- qm = (qm - 0x0101010101010101ull) & ~qm & 0x8080808080808080ull;
674
- bm = (bm - 0x0101010101010101ull) & ~bm & 0x8080808080808080ull;
675
- uint64_t c0 = (w - 0x2020202020202020ull) & ~w & 0x8080808080808080ull;
676
- if (qm != 0 && ((bm | c0) & (qm - 1)) == 0) {
677
- close = j + (size_t)yep_ctz64(qm) / 8;
678
- recs[count] = ((uint64_t)((uint32_t)j) << 32) |
679
- ((uint64_t)((uint32_t)(close - j)) << 8) | (uint64_t)(YEP_T_STR);
680
- count++;
681
- i = close + 1;
682
- goto lstr_done;
683
- }
684
- }
685
- int esc = 0;
686
- if (!yep_json_string(p, len, &i, &close, &esc)) {
687
- goto lreject;
688
- }
689
- recs[count] = ((uint64_t)((uint32_t)(at + 1)) << 32) |
690
- ((uint64_t)((uint32_t)(close - at - 1)) << 8) | (uint64_t)(YEP_T_STR);
691
- count++;
692
- lstr_done:
693
- if (key_slot) {
694
- if (i < len && p[i] == ':') {
695
- i++;
696
- top_expect = JW_VALUE;
697
- key_slot = 0;
698
- } else {
699
- top_expect = JW_COLON;
700
- }
701
- } else {
702
- goto lcomma;
703
- }
704
- continue;
787
+ LSTR_SCAN(lmapafter);
705
788
  }
706
789
  if ((unsigned)(c - '0') <= 9u || c == '-') {
707
- /* deferred: classify-scan to the run's end — no grammar */
708
- i = at + 1;
709
- while (i < len) {
710
- char d = p[i];
790
+ size_t k = i + 1;
791
+ while (k < len) {
792
+ char d = p[k];
711
793
  if ((d >= '0' && d <= '9') || d == '-' || d == '+' || d == '.' || d == 'e' ||
712
794
  d == 'E') {
713
- i++;
795
+ k++;
714
796
  continue;
715
797
  }
716
798
  break;
717
799
  }
718
- recs[count] = ((uint64_t)((uint32_t)at) << 32) | ((uint64_t)((uint32_t)(i - at)) << 8) |
800
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
719
801
  (uint64_t)(YEP_T_NUM);
720
802
  count++;
721
- lcomma:
722
- if (i < len && p[i] == ',') {
723
- i++;
724
- top_expect = top_kind ? JW_KEY : JW_VALUE;
725
- key_slot = top_kind ? 1 : 0;
726
- } else {
727
- top_expect = JW_COMMA_OR_CLOSE;
728
- }
729
- continue;
730
- }
731
- if (c == ']' || c == '}') {
732
- int want = c == ']' ? 0 : 1;
733
- if (top_kind != want ||
734
- (top_expect != JW_VALUE_OR_CLOSE && top_expect != JW_KEY_OR_CLOSE &&
735
- top_expect != JW_COMMA_OR_CLOSE)) {
736
- goto lreject;
737
- }
738
- recs[count] = ((uint64_t)top_open << 32) | ((uint64_t)0 << 8) | YEP_T_CLOSE;
739
- /* back-patch the opener's count link: keep the original
740
- * off/len, set the link into the record's off word */
741
- recs[top_open] = (recs[top_open] & ~(uint64_t)0xFFFFFFFF00000000u) |
742
- ((uint64_t)(uint32_t)count << 32);
743
- count++;
744
- i = at + 1;
745
- depth--;
746
- if (depth == 0) {
747
- break;
748
- }
749
- top_kind = kind[depth - 1];
750
- top_expect = JW_COMMA_OR_CLOSE;
751
- top_open = open_at[depth - 1];
752
- key_slot = 0;
753
- continue;
754
- }
755
- if (c == '{' || c == '[') {
756
- if (depth >= YEP_JSON_WALK_DEPTH) {
757
- goto lreject;
758
- }
759
- if (key_slot) {
760
- goto lreject;
761
- }
762
- kind[depth - 1] = top_kind;
763
- open_at[depth - 1] = top_open;
764
- top_kind = c == '[' ? 0 : 1;
765
- recs[count] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) |
766
- (uint64_t)(c == '[' ? YEP_T_SEQ_OPEN : YEP_T_MAP_OPEN);
767
- top_open = (uint32_t)count;
768
- count++;
769
- top_expect = top_kind ? JW_KEY_OR_CLOSE : JW_VALUE_OR_CLOSE;
770
- key_slot = top_kind ? 1 : 0;
771
- kind[depth] = top_kind;
772
- open_at[depth] = top_open;
773
- depth++;
774
- i = at + 1;
775
- continue;
803
+ i = k;
804
+ goto lmapafter;
776
805
  }
777
806
  if (c == 't' || c == 'f' || c == 'n') {
778
807
  size_t wl = c == 'f' ? 5 : 4;
779
- if (at + wl > len) {
808
+ if (i + wl > len) {
780
809
  goto lreject;
781
810
  }
782
811
  uint32_t got4;
783
- memcpy(&got4, p + at, 4);
812
+ memcpy(&got4, p + i, 4);
784
813
  if (got4 != (c == 't' ? 0x65757274u /* "true" */
785
814
  : c == 'n' ? 0x6C6C756Eu /* "null" */
786
815
  : 0x736C6166u /* "fals" */) ||
787
- (c == 'f' && p[at + 4] != 'e')) {
816
+ (c == 'f' && p[i + 4] != 'e')) {
788
817
  goto lreject;
789
818
  }
790
- if (at + wl < len) {
791
- char z = p[at + wl];
819
+ if (i + wl < len) {
820
+ char z = p[i + wl];
792
821
  if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
793
822
  z != '}' && z != ':') {
794
823
  goto lreject;
795
824
  }
796
825
  }
797
- recs[count] = ((uint64_t)((uint32_t)at) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
826
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
798
827
  (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
799
828
  count++;
800
- i = at + wl;
801
- goto lcomma;
829
+ i += wl;
830
+ goto lmapafter;
831
+ }
832
+ if (c == '{') {
833
+ LPUSH(1);
834
+ goto lmap1;
835
+ }
836
+ if (c == '[') {
837
+ LPUSH(0);
838
+ goto lseq1;
802
839
  }
803
840
  goto lreject;
804
841
  }
805
- {
806
- size_t tail = i;
807
- while (tail < len &&
808
- (p[tail] == ' ' || p[tail] == '\t' || p[tail] == '\n' || p[tail] == '\r')) {
809
- tail++;
842
+
843
+ lmapafter:
844
+ LWS();
845
+ if (p[i] == ',') {
846
+ i++;
847
+ goto lmap;
848
+ }
849
+ if (p[i] == '}') {
850
+ LCLOSE();
851
+ }
852
+ goto lreject;
853
+
854
+ /* The sequence member cycle — the same single-region shape. */
855
+ lseq1: /* [] closes here */
856
+ LWS();
857
+ if (p[i] == ']') {
858
+ LCLOSE();
859
+ }
860
+ goto lseqval;
861
+
862
+ lseq: /* a member MUST follow (after ','): JW_VALUE */
863
+ LWS();
864
+ lseqval: {
865
+ char c = p[i];
866
+ if (c == '"') {
867
+ LSTR_SCAN(lseqafter);
868
+ }
869
+ if ((unsigned)(c - '0') <= 9u || c == '-') {
870
+ size_t k = i + 1;
871
+ while (k < len) {
872
+ char d = p[k];
873
+ if ((d >= '0' && d <= '9') || d == '-' || d == '+' || d == '.' || d == 'e' ||
874
+ d == 'E') {
875
+ k++;
876
+ continue;
877
+ }
878
+ break;
810
879
  }
811
- if (tail != len) {
880
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
881
+ (uint64_t)(YEP_T_NUM);
882
+ count++;
883
+ i = k;
884
+ goto lseqafter;
885
+ }
886
+ if (c == 't' || c == 'f' || c == 'n') {
887
+ size_t wl = c == 'f' ? 5 : 4;
888
+ if (i + wl > len) {
889
+ goto lreject;
890
+ }
891
+ uint32_t got4;
892
+ memcpy(&got4, p + i, 4);
893
+ if (got4 != (c == 't' ? 0x65757274u /* "true" */
894
+ : c == 'n' ? 0x6C6C756Eu /* "null" */
895
+ : 0x736C6166u /* "fals" */) ||
896
+ (c == 'f' && p[i + 4] != 'e')) {
812
897
  goto lreject;
813
898
  }
899
+ if (i + wl < len) {
900
+ char z = p[i + wl];
901
+ if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
902
+ z != '}' && z != ':') {
903
+ goto lreject;
904
+ }
905
+ }
906
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
907
+ (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
908
+ count++;
909
+ i += wl;
910
+ goto lseqafter;
911
+ }
912
+ if (c == '{') {
913
+ LPUSH(1);
914
+ goto lmap1;
915
+ }
916
+ if (c == '[') {
917
+ LPUSH(0);
918
+ goto lseq1;
919
+ }
920
+ goto lreject;
921
+ }
922
+
923
+ lseqafter:
924
+ LWS();
925
+ if (p[i] == ',') {
926
+ i++;
927
+ goto lseq;
814
928
  }
929
+ if (p[i] == ']') {
930
+ LCLOSE();
931
+ }
932
+ goto lreject;
933
+
934
+ ldone:
935
+ #undef LWS
936
+ #undef LPUSH
937
+ #undef LCLOSE
938
+ #undef LSTR_SCAN
939
+
940
+ {
941
+ size_t tail = i;
942
+ while (tail < len &&
943
+ (p[tail] == ' ' || p[tail] == '\t' || p[tail] == '\n' || p[tail] == '\r')) {
944
+ tail++;
945
+ }
946
+ if (tail != len) {
947
+ goto lreject;
948
+ }
949
+ }
815
950
  t->count = count;
816
951
  t->_src = p;
817
952
  t->_srclen = len;
@@ -1044,6 +1179,243 @@ reject:
1044
1179
  return YEPTRIS_ERROR_PARSE;
1045
1180
  }
1046
1181
 
1182
+ /* The span walk, single-pass edition: the classifier writes per-block
1183
+ * masks (no idx array, no emission loop), and this walk derives each
1184
+ * block's token mask in registers and dispatches straight off the set
1185
+ * bits. Numbers/keywords have no entry — a pending scalar occupies
1186
+ * [val_start, next token) and is recorded (with its charset
1187
+ * validation) when the next token arrives. String closes come from a
1188
+ * backward quote scan over a few bytes. Escape-carrying documents
1189
+ * never reach here (the route sends them to the validating fused
1190
+ * walk), so the fast path never scans string content. Same
1191
+ * accept/reject contract as the fused lenient walk (the differential
1192
+ * suite pins both). */
1193
+ static YEP_UNUSED_FN YeptrisStatus tape_walk_lnt_span(const char* p, size_t len, size_t open,
1194
+ const struct yep_s1_block* blocks,
1195
+ size_t nblocks, yeptris_json_tape* t) {
1196
+ if (tape_carve(t, len) != YEPTRIS_OK) {
1197
+ return YEPTRIS_ERROR_MEMORY;
1198
+ }
1199
+ t->_rec_primary = 1;
1200
+ yeptris_tape_rec* recs = t->recs;
1201
+ uint32_t open_at[YEP_JSON_WALK_DEPTH];
1202
+ uint8_t kind[YEP_JSON_WALK_DEPTH];
1203
+
1204
+ recs[0] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | YEP_T_DOC;
1205
+ uint8_t top_kind = p[open] == '[' ? 0 : 1;
1206
+ recs[1] =
1207
+ ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | (top_kind ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN);
1208
+ uint32_t top_open = 1;
1209
+ size_t count = 2;
1210
+ uint8_t top_expect = top_kind ? JW_KEY_OR_CLOSE : JW_VALUE_OR_CLOSE;
1211
+ int key_slot = top_kind ? 1 : 0;
1212
+ int depth = 1;
1213
+ open_at[0] = top_open;
1214
+ kind[0] = top_kind;
1215
+
1216
+ size_t bi = 0;
1217
+ uint64_t tokens = 0;
1218
+ size_t off = 0;
1219
+ size_t val_start = open + 1;
1220
+ size_t pos = 0;
1221
+ for (;;) {
1222
+ while (tokens == 0) {
1223
+ if (bi >= nblocks) {
1224
+ goto reject; /* EOF mid-structure (the root never closed) */
1225
+ }
1226
+ off = (size_t)bi * 64;
1227
+ tokens = blocks[bi].tokens;
1228
+ bi++;
1229
+ }
1230
+ pos = off + (size_t)yep_ctz64(tokens);
1231
+ tokens &= tokens - 1;
1232
+ if (pos == open) {
1233
+ continue; /* the root opener: recs[1] already carries it */
1234
+ }
1235
+
1236
+ /* the pending scalar (numbers/keywords only — a quote would be
1237
+ * a token): [val_start, pos) with trailing ws trimmed. All-ws
1238
+ * residue is harmless; a required value/key is satisfied by a
1239
+ * quote entry (the string arm) or a container. */
1240
+ if (val_start < pos) {
1241
+ size_t end = pos;
1242
+ while (end > val_start && (p[end - 1] == ' ' || p[end - 1] == '\t' ||
1243
+ p[end - 1] == '\n' || p[end - 1] == '\r')) {
1244
+ end--;
1245
+ }
1246
+ size_t vs = val_start;
1247
+ while (vs < end && (p[vs] == ' ' || p[vs] == '\t' || p[vs] == '\n' || p[vs] == '\r')) {
1248
+ vs++;
1249
+ }
1250
+ if (vs < end) {
1251
+ char h = p[vs];
1252
+ if (top_expect != JW_VALUE && top_expect != JW_VALUE_OR_CLOSE) {
1253
+ goto reject; /* bytes where no value may start: {x} [1 2] */
1254
+ }
1255
+ if (h == 't' || h == 'f' || h == 'n') {
1256
+ size_t wl = h == 'f' ? 5 : 4;
1257
+ if (end - vs != wl || memcmp(p + vs,
1258
+ h == 't' ? "true"
1259
+ : h == 'n' ? "null"
1260
+ : "false",
1261
+ wl) != 0) {
1262
+ goto reject;
1263
+ }
1264
+ recs[count] =
1265
+ ((uint64_t)((uint32_t)vs) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
1266
+ (uint64_t)(h == 'n' ? YEP_T_NULL : (h == 't' ? YEP_T_TRUE : YEP_T_FALSE));
1267
+ count++;
1268
+ } else {
1269
+ if (!((unsigned)(h - '0') <= 9u || h == '-')) {
1270
+ goto reject;
1271
+ }
1272
+ for (size_t k = vs; k < end; k++) {
1273
+ char d = p[k];
1274
+ if ((d < '0' || d > '9') && d != '-' && d != '+' && d != '.' && d != 'e' &&
1275
+ d != 'E') {
1276
+ goto reject;
1277
+ }
1278
+ }
1279
+ recs[count] = ((uint64_t)((uint32_t)vs) << 32) |
1280
+ ((uint64_t)((uint32_t)(end - vs)) << 8) | (uint64_t)(YEP_T_NUM);
1281
+ count++;
1282
+ }
1283
+ top_expect = JW_COMMA_OR_CLOSE;
1284
+ key_slot = 0;
1285
+ } else if ((top_expect == JW_VALUE || top_expect == JW_KEY) &&
1286
+ (p[pos] != '"' && p[pos] != '{' && p[pos] != '[')) {
1287
+ goto reject; /* a value/key was required: [1,] {"a":} */
1288
+ }
1289
+ val_start = pos;
1290
+ } else if ((top_expect == JW_VALUE || top_expect == JW_KEY) &&
1291
+ (p[pos] != '"' && p[pos] != '{' && p[pos] != '[')) {
1292
+ goto reject; /* a value/key was required and none started */
1293
+ }
1294
+
1295
+ {
1296
+ char c = p[pos];
1297
+ if (c == '"') {
1298
+ size_t close = 0;
1299
+ if (top_expect == JW_COMMA_OR_CLOSE || top_expect == JW_COLON) {
1300
+ goto reject; /* ["a" "b"] — no punct between values */
1301
+ }
1302
+ size_t end = (tokens != 0) ? off + (size_t)yep_ctz64(tokens) : 0;
1303
+ if (end == 0) {
1304
+ size_t b2 = bi;
1305
+ while (b2 < nblocks && blocks[b2].q == 0 && blocks[b2].op == 0) {
1306
+ b2++;
1307
+ }
1308
+ end = (b2 < nblocks) ? b2 * 64 : len;
1309
+ uint64_t m = blocks[b2].q | blocks[b2].op;
1310
+ if (b2 < nblocks && m != 0) {
1311
+ end = b2 * 64 + (size_t)yep_ctz64(m);
1312
+ }
1313
+ }
1314
+ /* end may be the close quote itself (the raw q mask
1315
+ * feeds the forward position), so the scan includes it */
1316
+ for (size_t k = end + 1; k-- > pos + 1;) {
1317
+ if (p[k] == '"') {
1318
+ close = k;
1319
+ break;
1320
+ }
1321
+ }
1322
+ if (close == 0) {
1323
+ goto reject;
1324
+ }
1325
+ recs[count] = ((uint64_t)((uint32_t)(pos + 1)) << 32) |
1326
+ ((uint64_t)((uint32_t)(close - pos - 1)) << 8) |
1327
+ (uint64_t)(YEP_T_STR);
1328
+ count++;
1329
+ val_start = close + 1;
1330
+ if (key_slot) {
1331
+ top_expect = JW_COLON;
1332
+ } else {
1333
+ top_expect = JW_COMMA_OR_CLOSE;
1334
+ }
1335
+ continue;
1336
+ }
1337
+ if (c == ':') {
1338
+ if (top_expect != JW_COLON) {
1339
+ goto reject;
1340
+ }
1341
+ top_expect = JW_VALUE;
1342
+ key_slot = 0;
1343
+ val_start = pos + 1;
1344
+ continue;
1345
+ }
1346
+ if (c == ',') {
1347
+ if (top_expect != JW_COMMA_OR_CLOSE) {
1348
+ goto reject;
1349
+ }
1350
+ top_expect = top_kind ? JW_KEY : JW_VALUE;
1351
+ key_slot = top_kind ? 1 : 0;
1352
+ val_start = pos + 1;
1353
+ continue;
1354
+ }
1355
+ if (c == ']' || c == '}') {
1356
+ int want = c == ']' ? 0 : 1;
1357
+ if (top_kind != want ||
1358
+ (top_expect != JW_VALUE_OR_CLOSE && top_expect != JW_KEY_OR_CLOSE &&
1359
+ top_expect != JW_COMMA_OR_CLOSE)) {
1360
+ goto reject;
1361
+ }
1362
+ recs[count] = ((uint64_t)top_open << 32) | ((uint64_t)0 << 8) | YEP_T_CLOSE;
1363
+ recs[top_open] = (recs[top_open] & ~(uint64_t)0xFFFFFFFF00000000u) |
1364
+ ((uint64_t)(uint32_t)count << 32);
1365
+ count++;
1366
+ depth--;
1367
+ if (depth == 0) {
1368
+ break;
1369
+ }
1370
+ top_kind = kind[depth - 1];
1371
+ top_expect = JW_COMMA_OR_CLOSE;
1372
+ top_open = open_at[depth - 1];
1373
+ key_slot = 0;
1374
+ val_start = pos + 1;
1375
+ continue;
1376
+ }
1377
+ if (c == '{' || c == '[') {
1378
+ if (depth >= YEP_JSON_WALK_DEPTH || key_slot || top_expect == JW_COMMA_OR_CLOSE ||
1379
+ top_expect == JW_COLON) {
1380
+ goto reject;
1381
+ }
1382
+ kind[depth - 1] = top_kind;
1383
+ open_at[depth - 1] = top_open;
1384
+ top_kind = c == '[' ? 0 : 1;
1385
+ recs[count] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) |
1386
+ (uint64_t)(c == '[' ? YEP_T_SEQ_OPEN : YEP_T_MAP_OPEN);
1387
+ top_open = (uint32_t)count;
1388
+ count++;
1389
+ top_expect = top_kind ? JW_KEY_OR_CLOSE : JW_VALUE_OR_CLOSE;
1390
+ key_slot = top_kind ? 1 : 0;
1391
+ kind[depth] = top_kind;
1392
+ open_at[depth] = top_open;
1393
+ depth++;
1394
+ val_start = pos + 1;
1395
+ continue;
1396
+ }
1397
+ }
1398
+ goto reject;
1399
+ }
1400
+ /* trailing residue: after the root close, only ws may follow */
1401
+ size_t last = pos + 1;
1402
+ while (last < len &&
1403
+ (p[last] == ' ' || p[last] == '\t' || p[last] == '\n' || p[last] == '\r')) {
1404
+ last++;
1405
+ }
1406
+ if (last != len) {
1407
+ goto reject;
1408
+ }
1409
+ t->count = count;
1410
+ t->_src = p;
1411
+ t->_srclen = len;
1412
+ return YEPTRIS_OK;
1413
+
1414
+ reject:
1415
+ yeptris_tape_free(t);
1416
+ return YEPTRIS_ERROR_PARSE;
1417
+ }
1418
+
1047
1419
  YEPTRIS_API YeptrisStatus yeptris_parse_json_tape_lenient(const char* source, size_t len,
1048
1420
  yeptris_json_tape* tape) {
1049
1421
  if ((source == NULL && len != 0) || tape == NULL) {
@@ -1075,21 +1447,24 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape_lenient(const char* source, si
1075
1447
  }
1076
1448
 
1077
1449
  if (source[at] == '[' || source[at] == '{') {
1078
- #if YEP_TOKEN_CONTRACT_ROUTE
1079
- /* the indexed twin: stage 1 + the idx walk (reference route —
1080
- * measured behind the fused lenient walk, same differential) */
1081
- uint32_t* idx = (uint32_t*)malloc((len + 2) * sizeof(uint32_t));
1082
- if (idx == NULL) {
1450
+ /* the span route: per-block classification + the register-token
1451
+ * walk — no idx array, no emission loop, content bytes never
1452
+ * forward-scanned. Escape-carrying documents (validation needs
1453
+ * the kernel) and C0-in-string ride the fused walk, which
1454
+ * delivers today's verdicts byte for byte. */
1455
+ struct yep_s1_block* blocks = malloc((len / 64 + 2) * sizeof(struct yep_s1_block));
1456
+ if (blocks == NULL) {
1083
1457
  return YEPTRIS_ERROR_MEMORY;
1084
1458
  }
1085
- size_t nidx = 0;
1086
- if (yep_text_active()->json_stage1(source, len, idx, &nidx)) {
1087
- YeptrisStatus st = tape_walk_lnt(source, len, at, idx, nidx, tape);
1088
- free(idx);
1459
+ size_t nblocks = 0;
1460
+ unsigned flags = 0;
1461
+ if (yep_text_active()->json_stage1_masks(source, len, blocks, &nblocks, &flags) &&
1462
+ (flags & (YEP_S1_C0_IN_STRING | YEP_S1_HAS_ESCAPE)) == 0) {
1463
+ YeptrisStatus st = tape_walk_lnt_span(source, len, at, blocks, nblocks, tape);
1464
+ free(blocks);
1089
1465
  return st;
1090
1466
  }
1091
- free(idx);
1092
- #endif
1467
+ free(blocks);
1093
1468
  return yep_tape_walk_lenient_fused(source, len, at, tape);
1094
1469
  }
1095
1470
 
@@ -1194,7 +1569,8 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape(const char* source, size_t len
1194
1569
  return YEPTRIS_ERROR_MEMORY;
1195
1570
  }
1196
1571
  size_t nidx = 0;
1197
- if (yep_text_active()->json_stage1(source, len, idx, &nidx)) {
1572
+ unsigned sflags = 0;
1573
+ if (yep_text_active()->json_stage1(source, len, idx, &nidx, &sflags)) {
1198
1574
  YeptrisStatus st = tape_walk_idx(source, len, off, idx, nidx, tape);
1199
1575
  free(idx);
1200
1576
  if (st != YEPTRIS_ERROR_PARSE) {