yeptris 0.6.26.2-arm-linux-eabihf → 0.6.28.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.
@@ -140,11 +140,12 @@ static int tape_put_root_scalar(tape_ctx* c, const char* p, size_t len, size_t a
140
140
  /* The punctuation inline (the simdjson tape-builder lesson): after a
141
141
  * value or a key, the next token is a comma/colon or the close —
142
142
  * consume it inline instead of paying a full loop iteration for a
143
- * one-byte state flip. Anything else (close, garbage, a tab) falls
144
- * through with the cursor parked for the main loop to judge. */
143
+ * one-byte state flip. Anything else (close, garbage) falls through
144
+ * with the cursor parked for the main loop to judge. Whitespace here
145
+ * is RFC 8259's set — tab included. */
145
146
  static inline uint8_t tape_inline_punct(const char* p, size_t len, size_t* i, uint8_t want) {
146
147
  size_t j = *i;
147
- while (j < len && (p[j] == ' ' || p[j] == '\n' || p[j] == '\r')) {
148
+ while (j < len && (p[j] == ' ' || p[j] == '\t' || p[j] == '\n' || p[j] == '\r')) {
148
149
  j++;
149
150
  }
150
151
  if (j < len && (uint8_t)p[j] == want) {
@@ -186,10 +187,10 @@ static YeptrisStatus tape_walk(const char* p, size_t len, size_t open, yeptris_j
186
187
 
187
188
  size_t i = open + 1;
188
189
  for (;;) {
189
- while (i < len && (p[i] == ' ' || p[i] == '\n' || p[i] == '\r')) {
190
+ while (i < len && (p[i] == ' ' || p[i] == '\t' || p[i] == '\n' || p[i] == '\r')) {
190
191
  i++;
191
192
  }
192
- if (i >= len || p[i] == '\t') {
193
+ if (i >= len) {
193
194
  goto reject;
194
195
  }
195
196
  size_t at = i;
@@ -329,8 +330,8 @@ static YeptrisStatus tape_walk(const char* p, size_t len, size_t open, yeptris_j
329
330
  }
330
331
  if (at + wl < len) {
331
332
  char z = p[at + wl];
332
- if (z != ' ' && z != '\n' && z != '\r' && z != ',' && z != ']' && z != '}' &&
333
- z != ':') {
333
+ if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
334
+ z != '}' && z != ':') {
334
335
  goto reject;
335
336
  }
336
337
  }
@@ -546,70 +547,663 @@ static YEP_UNUSED_FN YeptrisStatus tape_walk_idx(const char* p, size_t len, size
546
547
  }
547
548
  if (at + wl < len) {
548
549
  char z = p[at + wl];
549
- if (z != ' ' && z != '\n' && z != '\r' && z != ',' && z != ']' && z != '}' &&
550
- z != ':') {
550
+ if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
551
+ z != '}' && z != ':') {
551
552
  goto reject;
552
553
  }
553
554
  }
554
- kinds[count] = c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE);
555
- offs[count] = (uint32_t)at;
556
- lens[count] = (uint32_t)wl;
557
- count++;
558
- prev_end = at + wl;
559
- top_expect = JW_COMMA_OR_CLOSE;
560
- key_slot = 0;
561
- continue;
555
+ kinds[count] = c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE);
556
+ offs[count] = (uint32_t)at;
557
+ lens[count] = (uint32_t)wl;
558
+ count++;
559
+ prev_end = at + wl;
560
+ top_expect = JW_COMMA_OR_CLOSE;
561
+ key_slot = 0;
562
+ continue;
563
+ }
564
+ goto reject;
565
+ }
566
+ /* trailing whitespace only */
567
+ while (prev_end < len && (p[prev_end] == ' ' || p[prev_end] == '\t' || p[prev_end] == '\n' ||
568
+ p[prev_end] == '\r')) {
569
+ prev_end++;
570
+ }
571
+ if (prev_end != len) {
572
+ goto reject;
573
+ }
574
+ if (count > len + 2) {
575
+ goto mem;
576
+ }
577
+ t->count = count;
578
+ t->_src = p;
579
+ t->_srclen = len;
580
+ return YEPTRIS_OK;
581
+
582
+ mem:
583
+ yeptris_tape_free(t);
584
+ return YEPTRIS_ERROR_MEMORY;
585
+ reject:
586
+ yeptris_tape_free(t);
587
+ return YEPTRIS_ERROR_PARSE;
588
+ }
589
+
590
+ /* The LENIENT token walk (the simdjson deferred contract): stage-1
591
+ * positions consumed with the grammar arms removed. Structural
592
+ * alternation mirrors tape_walk_idx state for state — bracket
593
+ * matching, key/value placement, literals (packed compares), and
594
+ * string closes/escapes (yep_json_string) all stay parse-time
595
+ * checks; NUMBER grammar does not — the arm records the scalar-run
596
+ * span (start to the next token's position, trailing ws trimmed)
597
+ * as YEP_T_NUM and yeptris_tape_convert owns validation and the
598
+ * int/float split. A malformed number ("12ab") therefore rejects at
599
+ * drain, not parse. */
600
+ /* The fused LENIENT walk: the strict walk's byte loop with the number
601
+ * grammar deferred — the arm records the run to its natural delimiter
602
+ * (the classify scan carries no accept/reject structure) and
603
+ * yeptris_tape_convert owns validation. Everything else matches
604
+ * tape_walk state for state. */
605
+ /* LNUM_RUN — the initial digit run of a number span: SWAR words, eight
606
+ * bytes per pass, with the byte loop below as tail and classifier.
607
+ * digit iff 0x30-0x39: a lane flags when (c+0x46) carries into bit7
608
+ * (c > '9'), when ((c|0x80)-0xB0) goes negative (c < '0'), or when the
609
+ * byte is non-ASCII — and since the all-digit prefix never borrows
610
+ * across lanes, the flags are exact up to the first non-digit, where
611
+ * the scan stops and the byte loop classifies what follows (the
612
+ * consumed-only digits_only law is untouched: the run sets saw_digit,
613
+ * nothing else). */
614
+ #define LNUM_RUN \
615
+ do { \
616
+ size_t ks_ = k; \
617
+ while (k + 8 <= len) { \
618
+ uint64_t w_; \
619
+ memcpy(&w_, p + k, 8); \
620
+ uint64_t nd_ = \
621
+ ((w_ + 0x4646464646464646ull) & 0x8080808080808080ull) | \
622
+ (((w_ | 0x8080808080808080ull) - 0xB0B0B0B0B0B0B0B0ull) & 0x8080808080808080ull) | \
623
+ (w_ & 0x8080808080808080ull); \
624
+ if (nd_ != 0) { \
625
+ k += (size_t)yep_ctz64(nd_) >> 3; \
626
+ break; \
627
+ } \
628
+ k += 8; \
629
+ } \
630
+ if (k > ks_) { \
631
+ saw_digit = 1; \
632
+ } \
633
+ } while (0)
634
+
635
+ /* The strict RFC 8259 number check over one number-ish run — the
636
+ * in-walk float/exp validator (the out-of-line number_shape call +
637
+ * rescan was 13% of json-doc; the digits-only case never gets here).
638
+ * One pass, no state machine words: sign, digits (leading-zero rule),
639
+ * optional .digits, optional e[+-]digits, consume exactly len.
640
+ * always_inline: the spans are 3-8 bytes and the out-of-line call was
641
+ * the cost (the rescan itself is nothing). */
642
+ #if defined(__GNUC__)
643
+ #define TAPE_NUM_OK_INLINE __attribute__((always_inline)) inline
644
+ #else
645
+ #define TAPE_NUM_OK_INLINE
646
+ #endif
647
+ static TAPE_NUM_OK_INLINE int tape_num_ok(const char* p, size_t len) {
648
+ size_t k = 0;
649
+ if (k < len && p[k] == '-') {
650
+ k++;
651
+ }
652
+ size_t int_start = k;
653
+ while (k < len && (unsigned)(unsigned char)p[k] - '0' <= 9u) {
654
+ k++;
655
+ }
656
+ if (k == int_start) {
657
+ return 0; /* no integer part */
658
+ }
659
+ if (k - int_start > 1 && p[int_start] == '0') {
660
+ return 0; /* leading zero */
661
+ }
662
+ if (k < len && p[k] == '.') {
663
+ k++;
664
+ size_t frac = k;
665
+ while (k < len && (unsigned)(unsigned char)p[k] - '0' <= 9u) {
666
+ k++;
667
+ }
668
+ if (k == frac) {
669
+ return 0;
670
+ }
671
+ }
672
+ if (k < len && (p[k] == 'e' || p[k] == 'E')) {
673
+ k++;
674
+ if (k < len && (p[k] == '+' || p[k] == '-')) {
675
+ k++;
676
+ }
677
+ size_t exp = k;
678
+ while (k < len && (unsigned)(unsigned char)p[k] - '0' <= 9u) {
679
+ k++;
680
+ }
681
+ if (k == exp) {
682
+ return 0;
683
+ }
684
+ }
685
+ return k == len;
686
+ }
687
+
688
+ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open,
689
+ yeptris_json_tape* t, int strict_nums, int clean_only,
690
+ int reuse_block) {
691
+ /* reuse_block: the caller owns the struct and zeroed it (the flow
692
+ * recorder's scratch) — a prior walk's block may be reused through
693
+ * the _srclen watermark. Consumer structs carry untrustworthy
694
+ * fields and always carve fresh. */
695
+ if (reuse_block && t->_block != NULL && t->recs != NULL && t->_rec_primary &&
696
+ t->_srclen >= len + 2) {
697
+ t->_cols_ready = 0;
698
+ t->_rec_primary = 1;
699
+ } else if (tape_carve(t, len) != YEPTRIS_OK) {
700
+ return YEPTRIS_ERROR_MEMORY;
701
+ }
702
+ t->_rec_primary = 1;
703
+ /* the interleaved records are the primary storage (item 07); the
704
+ * columns materialize lazily via yeptris_tape_columns */
705
+ t->_rec_primary = 1;
706
+ yeptris_tape_rec* recs = t->recs;
707
+ uint32_t open_at[YEP_JSON_WALK_DEPTH];
708
+ uint8_t kind[YEP_JSON_WALK_DEPTH];
709
+
710
+ recs[0] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | YEP_T_DOC;
711
+
712
+ uint8_t top_kind = p[open] == '[' ? 0 : 1;
713
+ recs[1] =
714
+ ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | (top_kind ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN);
715
+ size_t count = 2;
716
+ int depth = 1;
717
+ open_at[0] = 1;
718
+ kind[0] = top_kind;
719
+
720
+ size_t i = open + 1;
721
+ /* clean_only (parse_json's route): tabs and non-ASCII bytes REJECT
722
+ * — the caller falls to the strict sequence, which owns the pinned
723
+ * error precedences those inputs carry (the tab reject, the UTF-8
724
+ * gate). This replaces the entry's full-input gate_scan +
725
+ * tab-memchr pre-passes: same routing by construction, zero scans
726
+ * before the walk. */
727
+ int cl = clean_only;
728
+ if (top_kind) {
729
+ goto lmap1;
730
+ }
731
+ goto lseq1;
732
+
733
+ /* strict_nums: the parse_json route's contract is RFC 8259 AT
734
+ * parse time, so its number arms validate the run in-place (the
735
+ * settle pass disappears — the span is cache-warm here); the
736
+ * standalone lenient entry keeps its deferred contract (0: the
737
+ * run records unvalidated, yeptris_tape_convert owns it). */
738
+
739
+ /* The specialized member loops (the #342 dispatch-chain cut): once
740
+ * inside a container the grammar is a 2-state cycle — member or
741
+ * separator — so the general chain's expect/key_slot machine and
742
+ * its per-token re-dispatch are replaced by direct loops per kind.
743
+ * Acceptance is IDENTICAL to the chain (the pinning suites hold:
744
+ * Tape suite, LenientMatchesStrict, ErrorParity, the json corpora).
745
+ *
746
+ * lmap / lseq: positioned at a member (or the container's closer).
747
+ * lvalue: scans one value (string/number/literal/container).
748
+ * lafter: positioned after a complete value — ',' or the closer.
749
+ * A close pops the frame; depth 0 ends the walk. */
750
+
751
+ #define LWS() \
752
+ do { \
753
+ while (i < len && \
754
+ (p[i] == ' ' || p[i] == '\n' || p[i] == '\r' || (p[i] == '\t' && !cl))) { \
755
+ i++; \
756
+ } \
757
+ if (i >= len) { \
758
+ goto lreject; \
759
+ } \
760
+ } while (0)
761
+
762
+ #define LPUSH(k) \
763
+ do { \
764
+ if (depth >= YEP_JSON_WALK_DEPTH) { \
765
+ goto lreject; \
766
+ } \
767
+ recs[count] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | \
768
+ (uint64_t)((k) ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN); \
769
+ kind[depth] = (k); \
770
+ open_at[depth] = (uint32_t)count; \
771
+ count++; \
772
+ depth++; \
773
+ i++; \
774
+ } while (0)
775
+
776
+ #define LCLOSE() \
777
+ do { \
778
+ uint32_t lo_ = open_at[depth - 1]; \
779
+ recs[count] = ((uint64_t)lo_ << 32) | ((uint64_t)0 << 8) | YEP_T_CLOSE; \
780
+ recs[lo_] = \
781
+ (recs[lo_] & ~(uint64_t)0xFFFFFFFF00000000u) | ((uint64_t)(uint32_t)count << 32); \
782
+ count++; \
783
+ i++; \
784
+ depth--; \
785
+ if (depth == 0) { \
786
+ goto ldone; \
787
+ } \
788
+ if (kind[depth - 1]) { \
789
+ goto lmapafter; \
790
+ } \
791
+ goto lseqafter; \
792
+ } while (0)
793
+
794
+ /* LSTR_SCAN(after) — the fused string arm, positioned at p[i] == '"'.
795
+ * One SWAR round settles the common short span; quote_scan (the SIMD
796
+ * close scan) settles longer no-escape spans, with a c0 sweep over the
797
+ * content (quote_scan never tests control bytes); the kernel runs only
798
+ * when escapes appear — its grammar authority covers the escape rules
799
+ * quote_scan skips. `after` names the post-member label. */
800
+ #define LSTR_SCAN(after) \
801
+ do { \
802
+ size_t j_ = i + 1; \
803
+ int esc_ = 0; \
804
+ if (j_ + 8 <= len) { \
805
+ uint64_t w_; \
806
+ memcpy(&w_, p + j_, 8); \
807
+ uint64_t qm_ = (w_ ^ 0x2222222222222222ull); \
808
+ uint64_t bm_ = (w_ ^ 0x5C5C5C5C5C5C5C5Cull); \
809
+ qm_ = (qm_ - 0x0101010101010101ull) & ~qm_ & 0x8080808080808080ull; \
810
+ bm_ = (bm_ - 0x0101010101010101ull) & ~bm_ & 0x8080808080808080ull; \
811
+ uint64_t c0_ = (w_ - 0x2020202020202020ull) & ~w_ & 0x8080808080808080ull; \
812
+ uint64_t hi_ = cl ? (w_ & 0x8080808080808080ull) : 0; \
813
+ if (qm_ != 0 && ((bm_ | c0_ | hi_) & (qm_ - 1)) == 0) { \
814
+ size_t cl_ = j_ + (size_t)yep_ctz64(qm_) / 8; \
815
+ recs[count] = ((uint64_t)((uint32_t)j_) << 32) | \
816
+ ((uint64_t)((uint32_t)(cl_ - j_)) << 8) | (uint64_t)(YEP_T_STR); \
817
+ count++; \
818
+ i = cl_ + 1; \
819
+ goto after; \
820
+ } \
821
+ /* rounds 2-4 (up to 32 bytes inline): the email-class strings \
822
+ * close here without the quote_scan call and its redundant \
823
+ * content re-sweep; reached only when round 1 had no close, \
824
+ * so the short-string case pays nothing */ \
825
+ if ((qm_ | bm_ | c0_ | hi_) == 0) { \
826
+ size_t nr_ = (len - j_) >> 3; \
827
+ if (nr_ > 4) { \
828
+ nr_ = 4; \
829
+ } \
830
+ for (size_t r_ = 1; r_ < nr_; r_++) { \
831
+ size_t b2_ = j_ + (r_ << 3); \
832
+ memcpy(&w_, p + b2_, 8); \
833
+ qm_ = (w_ ^ 0x2222222222222222ull); \
834
+ bm_ = (w_ ^ 0x5C5C5C5C5C5C5C5Cull); \
835
+ qm_ = (qm_ - 0x0101010101010101ull) & ~qm_ & 0x8080808080808080ull; \
836
+ bm_ = (bm_ - 0x0101010101010101ull) & ~bm_ & 0x8080808080808080ull; \
837
+ c0_ = (w_ - 0x2020202020202020ull) & ~w_ & 0x8080808080808080ull; \
838
+ hi_ = cl ? (w_ & 0x8080808080808080ull) : 0; \
839
+ if (qm_ != 0 && ((bm_ | c0_ | hi_) & (qm_ - 1)) == 0) { \
840
+ size_t cl_ = b2_ + (size_t)yep_ctz64(qm_) / 8; \
841
+ recs[count] = ((uint64_t)((uint32_t)j_) << 32) | \
842
+ ((uint64_t)((uint32_t)(cl_ - j_)) << 8) | \
843
+ (uint64_t)(YEP_T_STR); \
844
+ count++; \
845
+ i = cl_ + 1; \
846
+ goto after; \
847
+ } \
848
+ if ((qm_ | bm_ | c0_ | hi_) != 0) { \
849
+ break; /* escape, c0 or hi: kernel authority */ \
850
+ } \
851
+ } \
852
+ } \
853
+ } \
854
+ { \
855
+ const yep_text_kernels* kk_ = yep_text_active(); \
856
+ ptrdiff_t r_ = kk_->quote_scan(p + j_, len - j_, '"', &esc_); \
857
+ if (r_ >= 0 && esc_ == 0) { \
858
+ size_t cl_ = j_ + (size_t)r_; \
859
+ for (size_t b_ = j_; b_ < cl_;) { \
860
+ size_t room_ = cl_ - b_; \
861
+ if (room_ >= 8) { \
862
+ uint64_t v_; \
863
+ memcpy(&v_, p + b_, 8); \
864
+ uint64_t bad_ = \
865
+ (v_ - 0x2020202020202020ull) & ~v_ & 0x8080808080808080ull; \
866
+ if (cl) { \
867
+ bad_ |= v_ & 0x8080808080808080ull; \
868
+ } \
869
+ if (bad_) { \
870
+ goto lreject; \
871
+ } \
872
+ b_ += 8; \
873
+ } else { \
874
+ if ((unsigned char)p[b_] < 0x20 || (cl && (unsigned char)p[b_] >= 0x80)) { \
875
+ goto lreject; \
876
+ } \
877
+ b_++; \
878
+ } \
879
+ } \
880
+ recs[count] = ((uint64_t)((uint32_t)j_) << 32) | \
881
+ ((uint64_t)((uint32_t)(cl_ - j_)) << 8) | (uint64_t)(YEP_T_STR); \
882
+ count++; \
883
+ i = cl_ + 1; \
884
+ goto after; \
885
+ } \
886
+ } \
887
+ { \
888
+ size_t at_ = i; \
889
+ size_t cl_ = 0; \
890
+ if (!yep_json_string(p, len, &i, &cl_, &esc_)) { \
891
+ goto lreject; \
892
+ } \
893
+ if (cl) { /* escaped strings skip the sweeps above: the \
894
+ * clean route still owes the strict sequence's \
895
+ * byte guarantees over the content */ \
896
+ for (size_t b_ = at_ + 1; b_ < cl_; b_++) { \
897
+ unsigned char uc_ = (unsigned char)p[b_]; \
898
+ if (uc_ < 0x20 || uc_ >= 0x80) { \
899
+ goto lreject; \
900
+ } \
901
+ } \
902
+ } \
903
+ recs[count] = ((uint64_t)((uint32_t)(at_ + 1)) << 32) | \
904
+ ((uint64_t)((uint32_t)(cl_ - at_ - 1)) << 8) | (uint64_t)(YEP_T_STR); \
905
+ count++; \
906
+ i = cl_ + 1; \
907
+ } \
908
+ goto after; \
909
+ } while (0)
910
+
911
+ /* The map member cycle — ONE contiguous region (the json-doc lesson:
912
+ * a goto web across value/key/after regions scattered the hot path and
913
+ * cost 2.7x on the flat-map shape). Values scan inline; only nested
914
+ * containers, literals, and the kernel string fallback leave the
915
+ * cycle. Acceptance is the chain's: no trailing commas, string keys
916
+ * only, `:` required, closers only in first-member position. */
917
+ lmap1: /* {} closes here */
918
+ LWS();
919
+ if (p[i] == '}') {
920
+ LCLOSE();
921
+ }
922
+ if (p[i] != '"') {
923
+ goto lreject;
924
+ }
925
+ goto lmapkey;
926
+
927
+ lmap: /* a member MUST follow (after ','): JW_KEY — a closer rejects */
928
+ /* compact fast path: the byte is the key's quote already — LWS's
929
+ * loop would examine it once and exit */
930
+ if (i < len && p[i] == '"') {
931
+ goto lmapkey;
932
+ }
933
+ LWS();
934
+ if (p[i] != '"') {
935
+ goto lreject;
936
+ }
937
+ lmapkey: { LSTR_SCAN(lmapcolon); }
938
+ lmapcolon:
939
+ if (i < len && p[i] == ':') {
940
+ i++;
941
+ /* compact fast path: the value byte follows the colon with no
942
+ * whitespace — one load dispatches it (the ws test below is
943
+ * what LWS would run anyway) */
944
+ if (i < len) {
945
+ char v = p[i];
946
+ if (v != ' ' && v != '\n' && v != '\r' && (cl || v != '\t')) {
947
+ goto lmapval;
948
+ }
949
+ }
950
+ LWS();
951
+ goto lmapval;
952
+ }
953
+ LWS();
954
+ if (p[i] != ':') {
955
+ goto lreject;
956
+ }
957
+ i++;
958
+ LWS();
959
+ lmapval: {
960
+ char c = p[i];
961
+ if (c == '"') {
962
+ LSTR_SCAN(lmapafter);
963
+ }
964
+ if ((unsigned)(c - '0') <= 9u || c == '-') {
965
+ size_t k = i + 1;
966
+ /* the digits-only tracking makes the pure-integer case a
967
+ * 3-cycle leading-zero check — number_shape (a call + a
968
+ * rescan, 13% of json-doc) runs only for dot/exp spans */
969
+ int digits_only = 1, saw_digit = (c != '-');
970
+ LNUM_RUN;
971
+ while (k < len) {
972
+ char d = p[k];
973
+ if ((unsigned)(d - '0') <= 9u) {
974
+ k++;
975
+ saw_digit = 1;
976
+ continue;
977
+ }
978
+ /* the flag clears only on a CONSUMED byte: the run's
979
+ * terminator (',', '}', ']', whitespace) must leave
980
+ * digits_only intact, or every plain integer followed
981
+ * by a delimiter falls into the full validator */
982
+ if (d == '-' || d == '+' || d == '.' || d == 'e' || d == 'E') {
983
+ digits_only = 0;
984
+ k++;
985
+ continue;
986
+ }
987
+ break;
988
+ }
989
+ if (strict_nums) {
990
+ int ok;
991
+ if (digits_only && saw_digit) {
992
+ size_t d0 = (c == '-') ? i + 1 : i;
993
+ ok = (k - d0 == 1) || p[d0] != '0';
994
+ } else {
995
+ ok = tape_num_ok(p + i, (size_t)(k - i));
996
+ }
997
+ if (!ok) {
998
+ goto lreject;
999
+ }
1000
+ }
1001
+ recs[count] =
1002
+ ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
1003
+ (uint64_t)(strict_nums ? ((digits_only && saw_digit) ? YEP_T_INT : YEP_T_FLOAT)
1004
+ : (uint64_t)(YEP_T_NUM));
1005
+ count++;
1006
+ i = k;
1007
+ goto lmapafter;
1008
+ }
1009
+ if (c == 't' || c == 'f' || c == 'n') {
1010
+ size_t wl = c == 'f' ? 5 : 4;
1011
+ if (i + wl > len) {
1012
+ goto lreject;
1013
+ }
1014
+ uint32_t got4;
1015
+ memcpy(&got4, p + i, 4);
1016
+ if (got4 != (c == 't' ? 0x65757274u /* "true" */
1017
+ : c == 'n' ? 0x6C6C756Eu /* "null" */
1018
+ : 0x736C6166u /* "fals" */) ||
1019
+ (c == 'f' && p[i + 4] != 'e')) {
1020
+ goto lreject;
1021
+ }
1022
+ if (i + wl < len) {
1023
+ char z = p[i + wl];
1024
+ if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
1025
+ z != '}' && z != ':') {
1026
+ goto lreject;
1027
+ }
1028
+ }
1029
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
1030
+ (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
1031
+ count++;
1032
+ i += wl;
1033
+ goto lmapafter;
1034
+ }
1035
+ if (c == '{') {
1036
+ LPUSH(1);
1037
+ goto lmap1;
1038
+ }
1039
+ if (c == '[') {
1040
+ LPUSH(0);
1041
+ goto lseq1;
1042
+ }
1043
+ goto lreject;
1044
+ }
1045
+
1046
+ lmapafter:
1047
+ /* compact fast path: value ',' key directly — the two LWS hops the
1048
+ * general form runs both exit on byte one here */
1049
+ if (i < len && p[i] == ',') {
1050
+ i++;
1051
+ if (i < len && p[i] == '"') {
1052
+ goto lmapkey;
1053
+ }
1054
+ goto lmap;
1055
+ }
1056
+ LWS();
1057
+ if (p[i] == ',') {
1058
+ i++;
1059
+ goto lmap;
1060
+ }
1061
+ if (p[i] == '}') {
1062
+ LCLOSE();
1063
+ }
1064
+ goto lreject;
1065
+
1066
+ /* The sequence member cycle — the same single-region shape. */
1067
+ lseq1: /* [] closes here */
1068
+ LWS();
1069
+ if (p[i] == ']') {
1070
+ LCLOSE();
1071
+ }
1072
+ goto lseqval;
1073
+
1074
+ lseq: /* a member MUST follow (after ','): JW_VALUE */
1075
+ LWS();
1076
+ lseqval: {
1077
+ char c = p[i];
1078
+ if (c == '"') {
1079
+ LSTR_SCAN(lseqafter);
1080
+ }
1081
+ if ((unsigned)(c - '0') <= 9u || c == '-') {
1082
+ size_t k = i + 1;
1083
+ int digits_only = 1, saw_digit = (c != '-');
1084
+ LNUM_RUN;
1085
+ while (k < len) {
1086
+ char d = p[k];
1087
+ if ((unsigned)(d - '0') <= 9u) {
1088
+ k++;
1089
+ saw_digit = 1;
1090
+ continue;
1091
+ }
1092
+ /* consumed-only clearing: see the map arm's comment */
1093
+ if (d == '-' || d == '+' || d == '.' || d == 'e' || d == 'E') {
1094
+ digits_only = 0;
1095
+ k++;
1096
+ continue;
1097
+ }
1098
+ break;
1099
+ }
1100
+ if (strict_nums) {
1101
+ int ok;
1102
+ if (digits_only && saw_digit) {
1103
+ size_t d0 = (c == '-') ? i + 1 : i;
1104
+ ok = (k - d0 == 1) || p[d0] != '0';
1105
+ } else {
1106
+ ok = tape_num_ok(p + i, (size_t)(k - i));
1107
+ }
1108
+ if (!ok) {
1109
+ goto lreject;
1110
+ }
1111
+ }
1112
+ recs[count] =
1113
+ ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
1114
+ (uint64_t)(strict_nums ? ((digits_only && saw_digit) ? YEP_T_INT : YEP_T_FLOAT)
1115
+ : (uint64_t)(YEP_T_NUM));
1116
+ count++;
1117
+ i = k;
1118
+ goto lseqafter;
1119
+ }
1120
+ if (c == 't' || c == 'f' || c == 'n') {
1121
+ size_t wl = c == 'f' ? 5 : 4;
1122
+ if (i + wl > len) {
1123
+ goto lreject;
1124
+ }
1125
+ uint32_t got4;
1126
+ memcpy(&got4, p + i, 4);
1127
+ if (got4 != (c == 't' ? 0x65757274u /* "true" */
1128
+ : c == 'n' ? 0x6C6C756Eu /* "null" */
1129
+ : 0x736C6166u /* "fals" */) ||
1130
+ (c == 'f' && p[i + 4] != 'e')) {
1131
+ goto lreject;
1132
+ }
1133
+ if (i + wl < len) {
1134
+ char z = p[i + wl];
1135
+ if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
1136
+ z != '}' && z != ':') {
1137
+ goto lreject;
1138
+ }
1139
+ }
1140
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
1141
+ (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
1142
+ count++;
1143
+ i += wl;
1144
+ goto lseqafter;
1145
+ }
1146
+ if (c == '{') {
1147
+ LPUSH(1);
1148
+ goto lmap1;
1149
+ }
1150
+ if (c == '[') {
1151
+ LPUSH(0);
1152
+ goto lseq1;
1153
+ }
1154
+ goto lreject;
1155
+ }
1156
+
1157
+ lseqafter:
1158
+ /* compact fast path: value ',' value directly — both LWS hops exit
1159
+ * on byte one here */
1160
+ if (i < len && p[i] == ',') {
1161
+ i++;
1162
+ if (i < len) {
1163
+ char v = p[i];
1164
+ if (v != ' ' && v != '\n' && v != '\r' && (cl || v != '\t')) {
1165
+ goto lseqval;
1166
+ }
562
1167
  }
563
- goto reject;
1168
+ goto lseq;
564
1169
  }
565
- /* trailing whitespace only */
566
- while (prev_end < len && (p[prev_end] == ' ' || p[prev_end] == '\t' || p[prev_end] == '\n' ||
567
- p[prev_end] == '\r')) {
568
- prev_end++;
1170
+ LWS();
1171
+ if (p[i] == ',') {
1172
+ i++;
1173
+ goto lseq;
569
1174
  }
570
- if (prev_end != len) {
571
- goto reject;
1175
+ if (p[i] == ']') {
1176
+ LCLOSE();
572
1177
  }
573
- if (count > len + 2) {
574
- goto mem;
1178
+ goto lreject;
1179
+
1180
+ ldone:
1181
+ #undef LWS
1182
+ #undef LNUM_RUN
1183
+ #undef LPUSH
1184
+ #undef LCLOSE
1185
+ #undef LSTR_SCAN
1186
+
1187
+ {
1188
+ size_t tail = i;
1189
+ while (tail < len &&
1190
+ (p[tail] == ' ' || p[tail] == '\n' || p[tail] == '\r' || (p[tail] == '\t' && !cl))) {
1191
+ tail++;
1192
+ }
1193
+ if (tail != len) {
1194
+ goto lreject;
575
1195
  }
1196
+ }
576
1197
  t->count = count;
577
1198
  t->_src = p;
578
1199
  t->_srclen = len;
579
1200
  return YEPTRIS_OK;
580
1201
 
581
- mem:
582
- yeptris_tape_free(t);
583
- return YEPTRIS_ERROR_MEMORY;
584
- reject:
1202
+ lreject:
585
1203
  yeptris_tape_free(t);
586
1204
  return YEPTRIS_ERROR_PARSE;
587
1205
  }
588
1206
 
589
- /* The LENIENT token walk (the simdjson deferred contract): stage-1
590
- * positions consumed with the grammar arms removed. Structural
591
- * alternation mirrors tape_walk_idx state for state — bracket
592
- * matching, key/value placement, literals (packed compares), and
593
- * string closes/escapes (yep_json_string) all stay parse-time
594
- * checks; NUMBER grammar does not — the arm records the scalar-run
595
- * span (start to the next token's position, trailing ws trimmed)
596
- * as YEP_T_NUM and yeptris_tape_convert owns validation and the
597
- * int/float split. A malformed number ("12ab") therefore rejects at
598
- * drain, not parse. */
599
- /* The fused LENIENT walk: the strict walk's byte loop with the number
600
- * grammar deferred — the arm records the run to its natural delimiter
601
- * (the classify scan carries no accept/reject structure) and
602
- * yeptris_tape_convert owns validation. Everything else matches
603
- * tape_walk state for state. */
604
- /* LNUM_RUN — the initial digit run of a number span: SWAR words, eight
605
- * bytes per pass, with the byte loop below as tail and classifier.
606
- * digit iff 0x30-0x39: a lane flags when (c+0x46) carries into bit7
607
- * (c > '9'), when ((c|0x80)-0xB0) goes negative (c < '0'), or when the
608
- * byte is non-ASCII — and since the all-digit prefix never borrows
609
- * across lanes, the flags are exact up to the first non-digit, where
610
- * the scan stops and the byte loop classifies what follows (the
611
- * consumed-only digits_only law is untouched: the run sets saw_digit,
612
- * nothing else). */
613
1207
  #define LNUM_RUN \
614
1208
  do { \
615
1209
  size_t ks_ = k; \
@@ -630,77 +1224,33 @@ reject:
630
1224
  saw_digit = 1; \
631
1225
  } \
632
1226
  } while (0)
633
-
634
- /* The strict RFC 8259 number check over one number-ish run — the
635
- * in-walk float/exp validator (the out-of-line number_shape call +
636
- * rescan was 13% of json-doc; the digits-only case never gets here).
637
- * One pass, no state machine words: sign, digits (leading-zero rule),
638
- * optional .digits, optional e[+-]digits, consume exactly len.
639
- * always_inline: the spans are 3-8 bytes and the out-of-line call was
640
- * the cost (the rescan itself is nothing). */
641
- #if defined(__GNUC__)
642
- #define TAPE_NUM_OK_INLINE __attribute__((always_inline)) inline
643
- #else
644
- #define TAPE_NUM_OK_INLINE
645
- #endif
646
- static TAPE_NUM_OK_INLINE int tape_num_ok(const char* p, size_t len) {
647
- size_t k = 0;
648
- if (k < len && p[k] == '-') {
649
- k++;
650
- }
651
- size_t int_start = k;
652
- while (k < len && (unsigned)(unsigned char)p[k] - '0' <= 9u) {
653
- k++;
654
- }
655
- if (k == int_start) {
656
- return 0; /* no integer part */
657
- }
658
- if (k - int_start > 1 && p[int_start] == '0') {
659
- return 0; /* leading zero */
660
- }
661
- if (k < len && p[k] == '.') {
662
- k++;
663
- size_t frac = k;
664
- while (k < len && (unsigned)(unsigned char)p[k] - '0' <= 9u) {
665
- k++;
666
- }
667
- if (k == frac) {
668
- return 0;
669
- }
670
- }
671
- if (k < len && (p[k] == 'e' || p[k] == 'E')) {
672
- k++;
673
- if (k < len && (p[k] == '+' || p[k] == '-')) {
674
- k++;
675
- }
676
- size_t exp = k;
677
- while (k < len && (unsigned)(unsigned char)p[k] - '0' <= 9u) {
678
- k++;
679
- }
680
- if (k == exp) {
681
- return 0;
682
- }
683
- }
684
- return k == len;
685
- }
686
-
687
- YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open,
688
- yeptris_json_tape* t, int strict_nums, int clean_only) {
1227
+ static YeptrisStatus yep_tape_walk_columns_fused(const char* p, size_t len, size_t open,
1228
+ yeptris_json_tape* t, int strict_nums,
1229
+ int clean_only) {
1230
+ /* The columns-primary edition of the record walk below: the same
1231
+ * arms (specialized member cycles, LSTR_SCAN, the digits law)
1232
+ * emitting the three columns directly — one pass, no records, no
1233
+ * materialization pass. The strict route's columns-eager contract
1234
+ * at the lenient lane's memory traffic. The record walk stays
1235
+ * byte-identical beside it; the differential suites pin the
1236
+ * equivalence. */
689
1237
  if (tape_carve(t, len) != YEPTRIS_OK) {
690
1238
  return YEPTRIS_ERROR_MEMORY;
691
1239
  }
692
- /* the interleaved records are the primary storage (item 07); the
693
- * columns materialize lazily via yeptris_tape_columns */
694
- t->_rec_primary = 1;
695
- yeptris_tape_rec* recs = t->recs;
1240
+ uint8_t* kinds = t->kinds;
1241
+ uint32_t* offs = t->offs;
1242
+ uint32_t* lens = t->lens;
696
1243
  uint32_t open_at[YEP_JSON_WALK_DEPTH];
697
1244
  uint8_t kind[YEP_JSON_WALK_DEPTH];
698
1245
 
699
- recs[0] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | YEP_T_DOC;
1246
+ kinds[0] = (uint8_t)YEP_T_DOC;
1247
+ offs[0] = 0;
1248
+ lens[0] = 0;
700
1249
 
701
1250
  uint8_t top_kind = p[open] == '[' ? 0 : 1;
702
- recs[1] =
703
- ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | (top_kind ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN);
1251
+ kinds[1] = top_kind ? (uint8_t)YEP_T_MAP_OPEN : (uint8_t)YEP_T_SEQ_OPEN;
1252
+ offs[1] = 0;
1253
+ lens[1] = 0;
704
1254
  size_t count = 2;
705
1255
  int depth = 1;
706
1256
  open_at[0] = 1;
@@ -753,8 +1303,9 @@ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open
753
1303
  if (depth >= YEP_JSON_WALK_DEPTH) { \
754
1304
  goto lreject; \
755
1305
  } \
756
- recs[count] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | \
757
- (uint64_t)((k) ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN); \
1306
+ kinds[count] = (k) ? (uint8_t)YEP_T_MAP_OPEN : (uint8_t)YEP_T_SEQ_OPEN; \
1307
+ offs[count] = 0; \
1308
+ lens[count] = 0; \
758
1309
  kind[depth] = (k); \
759
1310
  open_at[depth] = (uint32_t)count; \
760
1311
  count++; \
@@ -765,9 +1316,10 @@ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open
765
1316
  #define LCLOSE() \
766
1317
  do { \
767
1318
  uint32_t lo_ = open_at[depth - 1]; \
768
- recs[count] = ((uint64_t)lo_ << 32) | ((uint64_t)0 << 8) | YEP_T_CLOSE; \
769
- recs[lo_] = \
770
- (recs[lo_] & ~(uint64_t)0xFFFFFFFF00000000u) | ((uint64_t)(uint32_t)count << 32); \
1319
+ kinds[count] = (uint8_t)YEP_T_CLOSE; \
1320
+ offs[count] = lo_; \
1321
+ lens[count] = 0; \
1322
+ offs[lo_] = (uint32_t)count; \
771
1323
  count++; \
772
1324
  i++; \
773
1325
  depth--; \
@@ -801,8 +1353,9 @@ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open
801
1353
  uint64_t hi_ = cl ? (w_ & 0x8080808080808080ull) : 0; \
802
1354
  if (qm_ != 0 && ((bm_ | c0_ | hi_) & (qm_ - 1)) == 0) { \
803
1355
  size_t cl_ = j_ + (size_t)yep_ctz64(qm_) / 8; \
804
- recs[count] = ((uint64_t)((uint32_t)j_) << 32) | \
805
- ((uint64_t)((uint32_t)(cl_ - j_)) << 8) | (uint64_t)(YEP_T_STR); \
1356
+ kinds[count] = (uint8_t)YEP_T_STR; \
1357
+ offs[count] = (uint32_t)(j_); \
1358
+ lens[count] = (uint32_t)((cl_ - j_)); \
806
1359
  count++; \
807
1360
  i = cl_ + 1; \
808
1361
  goto after; \
@@ -827,9 +1380,9 @@ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open
827
1380
  hi_ = cl ? (w_ & 0x8080808080808080ull) : 0; \
828
1381
  if (qm_ != 0 && ((bm_ | c0_ | hi_) & (qm_ - 1)) == 0) { \
829
1382
  size_t cl_ = b2_ + (size_t)yep_ctz64(qm_) / 8; \
830
- recs[count] = ((uint64_t)((uint32_t)j_) << 32) | \
831
- ((uint64_t)((uint32_t)(cl_ - j_)) << 8) | \
832
- (uint64_t)(YEP_T_STR); \
1383
+ kinds[count] = (uint8_t)YEP_T_STR; \
1384
+ offs[count] = (uint32_t)(j_); \
1385
+ lens[count] = (uint32_t)((cl_ - j_)); \
833
1386
  count++; \
834
1387
  i = cl_ + 1; \
835
1388
  goto after; \
@@ -866,8 +1419,9 @@ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open
866
1419
  b_++; \
867
1420
  } \
868
1421
  } \
869
- recs[count] = ((uint64_t)((uint32_t)j_) << 32) | \
870
- ((uint64_t)((uint32_t)(cl_ - j_)) << 8) | (uint64_t)(YEP_T_STR); \
1422
+ kinds[count] = (uint8_t)YEP_T_STR; \
1423
+ offs[count] = (uint32_t)(j_); \
1424
+ lens[count] = (uint32_t)((cl_ - j_)); \
871
1425
  count++; \
872
1426
  i = cl_ + 1; \
873
1427
  goto after; \
@@ -889,8 +1443,9 @@ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open
889
1443
  } \
890
1444
  } \
891
1445
  } \
892
- recs[count] = ((uint64_t)((uint32_t)(at_ + 1)) << 32) | \
893
- ((uint64_t)((uint32_t)(cl_ - at_ - 1)) << 8) | (uint64_t)(YEP_T_STR); \
1446
+ kinds[count] = (uint8_t)YEP_T_STR; \
1447
+ offs[count] = (uint32_t)((at_ + 1)); \
1448
+ lens[count] = (uint32_t)((cl_ - at_ - 1)); \
894
1449
  count++; \
895
1450
  i = cl_ + 1; \
896
1451
  } \
@@ -987,8 +1542,11 @@ lmapval: {
987
1542
  goto lreject;
988
1543
  }
989
1544
  }
990
- recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
991
- (uint64_t)(YEP_T_NUM);
1545
+ kinds[count] =
1546
+ strict_nums ? ((digits_only && saw_digit) ? (uint8_t)YEP_T_INT : (uint8_t)YEP_T_FLOAT)
1547
+ : (uint8_t)YEP_T_NUM;
1548
+ offs[count] = (uint32_t)i;
1549
+ lens[count] = (uint32_t)(k - i);
992
1550
  count++;
993
1551
  i = k;
994
1552
  goto lmapafter;
@@ -1013,8 +1571,10 @@ lmapval: {
1013
1571
  goto lreject;
1014
1572
  }
1015
1573
  }
1016
- recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
1017
- (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
1574
+ kinds[count] = c == 'n' ? (uint8_t)YEP_T_NULL
1575
+ : (c == 't' ? (uint8_t)YEP_T_TRUE : (uint8_t)YEP_T_FALSE);
1576
+ offs[count] = (uint32_t)i;
1577
+ lens[count] = (uint32_t)wl;
1018
1578
  count++;
1019
1579
  i += wl;
1020
1580
  goto lmapafter;
@@ -1096,8 +1656,11 @@ lseqval: {
1096
1656
  goto lreject;
1097
1657
  }
1098
1658
  }
1099
- recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
1100
- (uint64_t)(YEP_T_NUM);
1659
+ kinds[count] =
1660
+ strict_nums ? ((digits_only && saw_digit) ? (uint8_t)YEP_T_INT : (uint8_t)YEP_T_FLOAT)
1661
+ : (uint8_t)YEP_T_NUM;
1662
+ offs[count] = (uint32_t)i;
1663
+ lens[count] = (uint32_t)(k - i);
1101
1664
  count++;
1102
1665
  i = k;
1103
1666
  goto lseqafter;
@@ -1122,8 +1685,10 @@ lseqval: {
1122
1685
  goto lreject;
1123
1686
  }
1124
1687
  }
1125
- recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
1126
- (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
1688
+ kinds[count] = c == 'n' ? (uint8_t)YEP_T_NULL
1689
+ : (c == 't' ? (uint8_t)YEP_T_TRUE : (uint8_t)YEP_T_FALSE);
1690
+ offs[count] = (uint32_t)i;
1691
+ lens[count] = (uint32_t)wl;
1127
1692
  count++;
1128
1693
  i += wl;
1129
1694
  goto lseqafter;
@@ -1182,6 +1747,7 @@ ldone:
1182
1747
  t->count = count;
1183
1748
  t->_src = p;
1184
1749
  t->_srclen = len;
1750
+ t->_cols_ready = 1;
1185
1751
  return YEPTRIS_OK;
1186
1752
 
1187
1753
  lreject:
@@ -1411,243 +1977,6 @@ reject:
1411
1977
  return YEPTRIS_ERROR_PARSE;
1412
1978
  }
1413
1979
 
1414
- /* The span walk, single-pass edition: the classifier writes per-block
1415
- * masks (no idx array, no emission loop), and this walk derives each
1416
- * block's token mask in registers and dispatches straight off the set
1417
- * bits. Numbers/keywords have no entry — a pending scalar occupies
1418
- * [val_start, next token) and is recorded (with its charset
1419
- * validation) when the next token arrives. String closes come from a
1420
- * backward quote scan over a few bytes. Escape-carrying documents
1421
- * never reach here (the route sends them to the validating fused
1422
- * walk), so the fast path never scans string content. Same
1423
- * accept/reject contract as the fused lenient walk (the differential
1424
- * suite pins both). */
1425
- static YEP_UNUSED_FN YeptrisStatus tape_walk_lnt_span(const char* p, size_t len, size_t open,
1426
- const struct yep_s1_block* blocks,
1427
- size_t nblocks, yeptris_json_tape* t) {
1428
- if (tape_carve(t, len) != YEPTRIS_OK) {
1429
- return YEPTRIS_ERROR_MEMORY;
1430
- }
1431
- t->_rec_primary = 1;
1432
- yeptris_tape_rec* recs = t->recs;
1433
- uint32_t open_at[YEP_JSON_WALK_DEPTH];
1434
- uint8_t kind[YEP_JSON_WALK_DEPTH];
1435
-
1436
- recs[0] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | YEP_T_DOC;
1437
- uint8_t top_kind = p[open] == '[' ? 0 : 1;
1438
- recs[1] =
1439
- ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | (top_kind ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN);
1440
- uint32_t top_open = 1;
1441
- size_t count = 2;
1442
- uint8_t top_expect = top_kind ? JW_KEY_OR_CLOSE : JW_VALUE_OR_CLOSE;
1443
- int key_slot = top_kind ? 1 : 0;
1444
- int depth = 1;
1445
- open_at[0] = top_open;
1446
- kind[0] = top_kind;
1447
-
1448
- size_t bi = 0;
1449
- uint64_t tokens = 0;
1450
- size_t off = 0;
1451
- size_t val_start = open + 1;
1452
- size_t pos = 0;
1453
- for (;;) {
1454
- while (tokens == 0) {
1455
- if (bi >= nblocks) {
1456
- goto reject; /* EOF mid-structure (the root never closed) */
1457
- }
1458
- off = (size_t)bi * 64;
1459
- tokens = blocks[bi].tokens;
1460
- bi++;
1461
- }
1462
- pos = off + (size_t)yep_ctz64(tokens);
1463
- tokens &= tokens - 1;
1464
- if (pos == open) {
1465
- continue; /* the root opener: recs[1] already carries it */
1466
- }
1467
-
1468
- /* the pending scalar (numbers/keywords only — a quote would be
1469
- * a token): [val_start, pos) with trailing ws trimmed. All-ws
1470
- * residue is harmless; a required value/key is satisfied by a
1471
- * quote entry (the string arm) or a container. */
1472
- if (val_start < pos) {
1473
- size_t end = pos;
1474
- while (end > val_start && (p[end - 1] == ' ' || p[end - 1] == '\t' ||
1475
- p[end - 1] == '\n' || p[end - 1] == '\r')) {
1476
- end--;
1477
- }
1478
- size_t vs = val_start;
1479
- while (vs < end && (p[vs] == ' ' || p[vs] == '\t' || p[vs] == '\n' || p[vs] == '\r')) {
1480
- vs++;
1481
- }
1482
- if (vs < end) {
1483
- char h = p[vs];
1484
- if (top_expect != JW_VALUE && top_expect != JW_VALUE_OR_CLOSE) {
1485
- goto reject; /* bytes where no value may start: {x} [1 2] */
1486
- }
1487
- if (h == 't' || h == 'f' || h == 'n') {
1488
- size_t wl = h == 'f' ? 5 : 4;
1489
- if (end - vs != wl || memcmp(p + vs,
1490
- h == 't' ? "true"
1491
- : h == 'n' ? "null"
1492
- : "false",
1493
- wl) != 0) {
1494
- goto reject;
1495
- }
1496
- recs[count] =
1497
- ((uint64_t)((uint32_t)vs) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
1498
- (uint64_t)(h == 'n' ? YEP_T_NULL : (h == 't' ? YEP_T_TRUE : YEP_T_FALSE));
1499
- count++;
1500
- } else {
1501
- if (!((unsigned)(h - '0') <= 9u || h == '-')) {
1502
- goto reject;
1503
- }
1504
- for (size_t k = vs; k < end; k++) {
1505
- char d = p[k];
1506
- if ((d < '0' || d > '9') && d != '-' && d != '+' && d != '.' && d != 'e' &&
1507
- d != 'E') {
1508
- goto reject;
1509
- }
1510
- }
1511
- recs[count] = ((uint64_t)((uint32_t)vs) << 32) |
1512
- ((uint64_t)((uint32_t)(end - vs)) << 8) | (uint64_t)(YEP_T_NUM);
1513
- count++;
1514
- }
1515
- top_expect = JW_COMMA_OR_CLOSE;
1516
- key_slot = 0;
1517
- } else if ((top_expect == JW_VALUE || top_expect == JW_KEY) &&
1518
- (p[pos] != '"' && p[pos] != '{' && p[pos] != '[')) {
1519
- goto reject; /* a value/key was required: [1,] {"a":} */
1520
- }
1521
- val_start = pos;
1522
- } else if ((top_expect == JW_VALUE || top_expect == JW_KEY) &&
1523
- (p[pos] != '"' && p[pos] != '{' && p[pos] != '[')) {
1524
- goto reject; /* a value/key was required and none started */
1525
- }
1526
-
1527
- {
1528
- char c = p[pos];
1529
- if (c == '"') {
1530
- size_t close = 0;
1531
- if (top_expect == JW_COMMA_OR_CLOSE || top_expect == JW_COLON) {
1532
- goto reject; /* ["a" "b"] — no punct between values */
1533
- }
1534
- size_t end = (tokens != 0) ? off + (size_t)yep_ctz64(tokens) : 0;
1535
- if (end == 0) {
1536
- size_t b2 = bi;
1537
- while (b2 < nblocks && blocks[b2].q == 0 && blocks[b2].op == 0) {
1538
- b2++;
1539
- }
1540
- end = (b2 < nblocks) ? b2 * 64 : len;
1541
- uint64_t m = blocks[b2].q | blocks[b2].op;
1542
- if (b2 < nblocks && m != 0) {
1543
- end = b2 * 64 + (size_t)yep_ctz64(m);
1544
- }
1545
- }
1546
- /* end may be the close quote itself (the raw q mask
1547
- * feeds the forward position), so the scan includes it */
1548
- for (size_t k = end + 1; k-- > pos + 1;) {
1549
- if (p[k] == '"') {
1550
- close = k;
1551
- break;
1552
- }
1553
- }
1554
- if (close == 0) {
1555
- goto reject;
1556
- }
1557
- recs[count] = ((uint64_t)((uint32_t)(pos + 1)) << 32) |
1558
- ((uint64_t)((uint32_t)(close - pos - 1)) << 8) |
1559
- (uint64_t)(YEP_T_STR);
1560
- count++;
1561
- val_start = close + 1;
1562
- if (key_slot) {
1563
- top_expect = JW_COLON;
1564
- } else {
1565
- top_expect = JW_COMMA_OR_CLOSE;
1566
- }
1567
- continue;
1568
- }
1569
- if (c == ':') {
1570
- if (top_expect != JW_COLON) {
1571
- goto reject;
1572
- }
1573
- top_expect = JW_VALUE;
1574
- key_slot = 0;
1575
- val_start = pos + 1;
1576
- continue;
1577
- }
1578
- if (c == ',') {
1579
- if (top_expect != JW_COMMA_OR_CLOSE) {
1580
- goto reject;
1581
- }
1582
- top_expect = top_kind ? JW_KEY : JW_VALUE;
1583
- key_slot = top_kind ? 1 : 0;
1584
- val_start = pos + 1;
1585
- continue;
1586
- }
1587
- if (c == ']' || c == '}') {
1588
- int want = c == ']' ? 0 : 1;
1589
- if (top_kind != want ||
1590
- (top_expect != JW_VALUE_OR_CLOSE && top_expect != JW_KEY_OR_CLOSE &&
1591
- top_expect != JW_COMMA_OR_CLOSE)) {
1592
- goto reject;
1593
- }
1594
- recs[count] = ((uint64_t)top_open << 32) | ((uint64_t)0 << 8) | YEP_T_CLOSE;
1595
- recs[top_open] = (recs[top_open] & ~(uint64_t)0xFFFFFFFF00000000u) |
1596
- ((uint64_t)(uint32_t)count << 32);
1597
- count++;
1598
- depth--;
1599
- if (depth == 0) {
1600
- break;
1601
- }
1602
- top_kind = kind[depth - 1];
1603
- top_expect = JW_COMMA_OR_CLOSE;
1604
- top_open = open_at[depth - 1];
1605
- key_slot = 0;
1606
- val_start = pos + 1;
1607
- continue;
1608
- }
1609
- if (c == '{' || c == '[') {
1610
- if (depth >= YEP_JSON_WALK_DEPTH || key_slot || top_expect == JW_COMMA_OR_CLOSE ||
1611
- top_expect == JW_COLON) {
1612
- goto reject;
1613
- }
1614
- kind[depth - 1] = top_kind;
1615
- open_at[depth - 1] = top_open;
1616
- top_kind = c == '[' ? 0 : 1;
1617
- recs[count] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) |
1618
- (uint64_t)(c == '[' ? YEP_T_SEQ_OPEN : YEP_T_MAP_OPEN);
1619
- top_open = (uint32_t)count;
1620
- count++;
1621
- top_expect = top_kind ? JW_KEY_OR_CLOSE : JW_VALUE_OR_CLOSE;
1622
- key_slot = top_kind ? 1 : 0;
1623
- kind[depth] = top_kind;
1624
- open_at[depth] = top_open;
1625
- depth++;
1626
- val_start = pos + 1;
1627
- continue;
1628
- }
1629
- }
1630
- goto reject;
1631
- }
1632
- /* trailing residue: after the root close, only ws may follow */
1633
- size_t last = pos + 1;
1634
- while (last < len &&
1635
- (p[last] == ' ' || p[last] == '\t' || p[last] == '\n' || p[last] == '\r')) {
1636
- last++;
1637
- }
1638
- if (last != len) {
1639
- goto reject;
1640
- }
1641
- t->count = count;
1642
- t->_src = p;
1643
- t->_srclen = len;
1644
- return YEPTRIS_OK;
1645
-
1646
- reject:
1647
- yeptris_tape_free(t);
1648
- return YEPTRIS_ERROR_PARSE;
1649
- }
1650
-
1651
1980
  YEPTRIS_API YeptrisStatus yeptris_parse_json_tape_lenient(const char* source, size_t len,
1652
1981
  yeptris_json_tape* tape) {
1653
1982
  if ((source == NULL && len != 0) || tape == NULL) {
@@ -1679,25 +2008,12 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape_lenient(const char* source, si
1679
2008
  }
1680
2009
 
1681
2010
  if (source[at] == '[' || source[at] == '{') {
1682
- /* the span route: per-block classification + the register-token
1683
- * walk — no idx array, no emission loop, content bytes never
1684
- * forward-scanned. Escape-carrying documents (validation needs
1685
- * the kernel) and C0-in-string ride the fused walk, which
1686
- * delivers today's verdicts byte for byte. */
1687
- struct yep_s1_block* blocks = malloc((len / 64 + 2) * sizeof(struct yep_s1_block));
1688
- if (blocks == NULL) {
1689
- return YEPTRIS_ERROR_MEMORY;
1690
- }
1691
- size_t nblocks = 0;
1692
- unsigned flags = 0;
1693
- if (yep_text_active()->json_stage1_masks(source, len, blocks, &nblocks, &flags) &&
1694
- (flags & (YEP_S1_C0_IN_STRING | YEP_S1_HAS_ESCAPE)) == 0) {
1695
- YeptrisStatus st = tape_walk_lnt_span(source, len, at, blocks, nblocks, tape);
1696
- free(blocks);
1697
- return st;
1698
- }
1699
- free(blocks);
1700
- return yep_tape_walk_lenient_fused(source, len, at, tape, 0, 0);
2011
+ /* the fused walk IS the lenient route: one pass beats the
2012
+ * stage-1 + span two-pass on every bench corpus (json-doc
2013
+ * 1.65x, json-users 2.2x — the ledger's #85 scoping), and
2014
+ * this exact call already carried the route's verdicts as the
2015
+ * escape/C0 fallback, so acceptance is unchanged. */
2016
+ return yep_tape_walk_lenient_fused(source, len, at, tape, 0, 0, 0);
1701
2017
  }
1702
2018
 
1703
2019
  /* scalar root: one record, same deferred split for numbers */
@@ -1812,6 +2128,19 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape(const char* source, size_t len
1812
2128
  free(idx);
1813
2129
  }
1814
2130
  #endif
2131
+ /* the record-primary fused walk first — the DOM route's
2132
+ * exact call and flags (strict numbers at record time,
2133
+ * clean-only gates; a reject frees the tape internally, so
2134
+ * the column attempt below carves fresh). This route's
2135
+ * contract is COLUMNS-EAGER (the suite reads kinds/offs/
2136
+ * lens directly), so the records materialize here — the
2137
+ * fused scan plus one sequential decode pass, still ahead
2138
+ * of the column walk's inline three-stream writes. */
2139
+ /* the columns-primary fused walk: one pass, columns eager
2140
+ * by construction, strict numbers classified at walk time */
2141
+ if (yep_tape_walk_columns_fused(source, len, off, tape, 1, 1) == YEPTRIS_OK) {
2142
+ return YEPTRIS_OK;
2143
+ }
1815
2144
  YeptrisStatus st = tape_walk(source, len, off, tape, 1);
1816
2145
  if (st != YEPTRIS_ERROR_PARSE) {
1817
2146
  return st; /* OK or MEMORY; a reject falls through */