yeptris 0.6.8.1 → 0.6.9.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b263a55abf16d57caf2c85268f108dc87059f9188c21e1b2a622129a6a8056f9
4
- data.tar.gz: dcd7ae19f1d52c9615d1d4dd169a632483e8bc40f6303f0f4591aff8b1684c30
3
+ metadata.gz: e582f07e09108f29273584fd4e7cb2387bc9410fda6d11a2f0209c40211d8a1c
4
+ data.tar.gz: 717cca7da0595ecd5ed4d356deabeca49343ce9d81cb40d13d5d2056595d0881
5
5
  SHA512:
6
- metadata.gz: 999a6efa21ae21f9defc24546fa66e083ae9cf95dde6e6d93266c5ad75542b7c1a2923d19bda028124c1f825e447ab4062faf44ba31d6bf1199447e51492d582
7
- data.tar.gz: 13afb6d81ca5795279dcebd790b4fae32d3e41abff68faacc7510d20c6557d0d358fb1bd217feee8d95b7aeb21b7b36cfc6f0d7f21e31a5e92e6e97f1f5c1292
6
+ metadata.gz: 552e9e7a9a024ebfb948717acbd15d845844159bd9b66fe4b2db29b5ae611e9cc09584aa1b66fc2a38c813532b4ba779c750b0c5e10cb0ecd81fe960afb5e3e8
7
+ data.tar.gz: f2a9fc0879fae9b0c498203552d48ac7574807eba66187e28e551efaff472f74dc3881fb4a43d552494a59b75f1af2298b6b0b2fa3167d5aabc8d5231a4ef4f8
data/lib/yeptris/ffi.rb CHANGED
@@ -66,10 +66,16 @@ module Yeptris
66
66
  # the caller's string (no arena copy, no second validating parse).
67
67
  # v2 records: numbers are spans at parse; yeptris_tape_convert
68
68
  # materializes them in one bulk call.
69
+ # v3 (libyeptris 0.6.9): the interleaved records (recs) are the
70
+ # primary storage on the lenient route; the strict route keeps the
71
+ # columns eager. The layout MUST mirror yeptris_json_tape — a stale
72
+ # layout makes C write past the FFI buffer (a silent heap overflow
73
+ # that only some allocators catch; the 0.6.9.1 windows crash).
69
74
  class JsonTape < ::FFI::Struct
70
75
  layout :count, :size_t, :kinds, :pointer, :offs, :pointer,
71
- :lens, :pointer, :int_min, :int64, :_src, :pointer, :_srclen, :size_t,
72
- :_block, :pointer
76
+ :lens, :pointer, :recs, :pointer, :_cols_ready, :int,
77
+ :_rec_primary, :int, :int_min, :int64, :_src, :pointer,
78
+ :_srclen, :size_t, :_block, :pointer
73
79
  end
74
80
 
75
81
  attach_function :yeptris_parse_json_tape, %i[pointer size_t pointer], :int
data/lib/yeptris/json.rb CHANGED
@@ -24,6 +24,7 @@ module Yeptris
24
24
  # behavior — strictness follows it (issue #37, found by canon's
25
25
  # CI where json 3.0.0 resolved while the dev box had 2.x).
26
26
  require "json"
27
+ require "date" # place_obj's `when Date` — psych loads it elsewhere, JSON.dump must not depend on that
27
28
  STRICT_DUPLICATE_KEYS = Gem::Version.new(::JSON::VERSION) >= Gem::Version.new("3")
28
29
 
29
30
  module_function
@@ -21,3 +21,22 @@ end
21
21
  # rebind is this file's whole purpose
22
22
  Object.class_eval { remove_const(:Psych) } if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych)
23
23
  ::Psych = Yeptris::Psych
24
+
25
+ # yaml-first boot order: ::YAML still references the ORIGINAL stdlib
26
+ # module object, whose singleton methods route through stdlib's parse
27
+ # stream — the UTF-8 tagging (and every other normalization) never
28
+ # runs (#135's spec/sdo discriminant). Delegate the original's public
29
+ # singleton surface to the Yeptris face so BOTH constants behave
30
+ # identically regardless of which module object a caller holds.
31
+ # ORIGINAL exists only when stdlib psych was already loaded (the
32
+ # drop-in-before-psych order needs no delegation — nothing references
33
+ # the original yet).
34
+ if ::Yeptris::Psych.const_defined?(:ORIGINAL, false)
35
+ ::Psych::ORIGINAL.singleton_class.class_eval do
36
+ ::Psych::ORIGINAL.singleton_methods(false).each do |m|
37
+ define_method(m) do |*args, **kwargs, &block|
38
+ ::Yeptris::Psych.public_send(m, *args, **kwargs, &block)
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/yeptris.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Yeptris
4
4
  # The gem's version lives in the parent namespace's file — the last
5
5
  # internal require (yeptris/version) retired with it.
6
- VERSION = "0.6.8.1".freeze
6
+ VERSION = "0.6.9.1".freeze
7
7
  # The error hierarchy lives in THIS file (the parent namespace's
8
8
  # own file): nested constants do not trigger a parent-constant
9
9
  # autoload, and the law forbids internal requires — defining the
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.8
6
+ VERSION 0.6.9
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -46,15 +46,32 @@ enum {
46
46
  spans; simdjson's deferred contract) */
47
47
  };
48
48
 
49
+ /* The interleaved record (TODO.max-perf/07): one 8-byte store per
50
+ * token instead of three column stores. Layout: off:u32 | len:u23 |
51
+ * kind:u8 — a span longer than 0xFEFFFF bytes carries len == 0xFFFFFF
52
+ * with the true length stored as a CONT-terminated extension record
53
+ * (kind YEP_T_CONT; unreachable for <16 MiB tokens). The columns
54
+ * stay the compatibility ABI: yeptris_tape_columns materializes them
55
+ * lazily from the records on first touch. */
56
+ #define YEP_T_CONT 8 /* record-length extension (not a token kind) */
57
+ typedef uint64_t yeptris_tape_rec;
58
+
49
59
  typedef struct yeptris_json_tape {
50
60
  size_t count;
51
- uint8_t* kinds; /* yeptris tape kind per record */
52
- uint32_t* offs; /* span starts (STR/INT/FLOAT); container links */
53
- uint32_t* lens; /* span lengths (STR/INT/FLOAT) */
54
- int64_t int_min; /* set by yeptris_tape_convert when an INT span
55
- exceeds int64 (materialize-time discovery) */
56
- const void* _src; /* the parsed buffer (spans borrow it; it must
57
- outlive the tape — same contract as the spans) */
61
+ uint8_t* kinds; /* compat columns materialized lazily (see
62
+ yeptris_tape_columns) when the interleaved
63
+ records are the primary storage */
64
+ uint32_t* offs; /* span starts (STR/INT/FLOAT); container links */
65
+ uint32_t* lens; /* span lengths (STR/INT/FLOAT) */
66
+ yeptris_tape_rec* recs; /* primary storage (may be NULL on legacy
67
+ column-built tapes: the strict route) */
68
+ int _cols_ready; /* columns materialized from recs already */
69
+ int _rec_primary; /* the lenient route: recs carry the data and
70
+ the columns are lazy */
71
+ int64_t int_min; /* set by yeptris_tape_convert when an INT span
72
+ exceeds int64 (materialize-time discovery) */
73
+ const void* _src; /* the parsed buffer (spans borrow it; it must
74
+ outlive the tape — same contract as the spans) */
58
75
  size_t _srclen;
59
76
  void* _block; /* the carved allocation base */
60
77
  } yeptris_json_tape;
@@ -89,6 +106,13 @@ YEPTRIS_API void yeptris_tape_free(yeptris_json_tape* tape);
89
106
  * number records converted, or SIZE_MAX if a span does not re-scan
90
107
  * as a number (impossible for a tape this library produced).
91
108
  * Sets t->int_min when an INT span exceeds int64. */
109
+ /* Materializes the kinds/offs/lens columns from the interleaved
110
+ * records (no-op when the tape was built column-primary or the
111
+ * columns are already materialized). Consumers reading the column
112
+ * pointers directly (the FFI binding) must call this once first.
113
+ * Returns 0 on success, nonzero on allocation failure. */
114
+ YEPTRIS_API int yeptris_tape_columns(yeptris_json_tape* t);
115
+
92
116
  YEPTRIS_API size_t yeptris_tape_convert(yeptris_json_tape* t, size_t from, size_t to,
93
117
  int64_t* ivals, double* dvals);
94
118
 
@@ -415,6 +415,9 @@ static yeptris_plan_result* plan_result_carve(const yeptris_plan* plan, size_t r
415
415
 
416
416
  YEPTRIS_API yeptris_plan_result*
417
417
  yeptris_tape_plan_walk(const yeptris_json_tape* tape, const yeptris_plan* plan, YeptrisStatus* st) {
418
+ /* item 07: the lenient tape's columns are lazy — materialize (a
419
+ * cache write through a const view; idempotent) */
420
+ yeptris_tape_columns((yeptris_json_tape*)tape);
418
421
  if (st != NULL) {
419
422
  *st = YEPTRIS_OK;
420
423
  }
@@ -59,7 +59,8 @@ static int rec_put(tape_ctx* c, uint8_t kind, uint32_t off, uint32_t len) {
59
59
  static YeptrisStatus tape_carve(yeptris_json_tape* t, size_t len) {
60
60
  size_t cap = len + 2;
61
61
  size_t off_o = (cap + 15) & ~(size_t)15;
62
- char* block = yep_alloc(yep_system_allocator(), off_o + 2 * cap * sizeof(uint32_t));
62
+ char* block = yep_alloc(yep_system_allocator(),
63
+ off_o + 2 * cap * sizeof(uint32_t) + cap * sizeof(yeptris_tape_rec));
63
64
  if (block == NULL) {
64
65
  return YEPTRIS_ERROR_MEMORY;
65
66
  }
@@ -67,11 +68,31 @@ static YeptrisStatus tape_carve(yeptris_json_tape* t, size_t len) {
67
68
  t->kinds = (uint8_t*)block;
68
69
  t->offs = (uint32_t*)(void*)(block + off_o);
69
70
  t->lens = t->offs + cap;
71
+ t->recs = (yeptris_tape_rec*)(void*)(t->lens + cap);
70
72
  t->count = 0;
71
73
  t->int_min = 0;
74
+ t->_rec_primary = 0;
75
+ t->_cols_ready = 0;
72
76
  return YEPTRIS_OK;
73
77
  }
74
78
 
79
+ /* The lazy column materialization (TODO.max-perf/07): records are the
80
+ * primary storage on the lenient route; the column ABI materializes
81
+ * from them on first touch. */
82
+ YEPTRIS_API int yeptris_tape_columns(yeptris_json_tape* t) {
83
+ if (t == NULL || !t->_rec_primary || t->recs == NULL || t->_cols_ready) {
84
+ return 0; /* column-primary (the strict route) or ready */
85
+ }
86
+ for (size_t i = 0; i < t->count; i++) {
87
+ yeptris_tape_rec r = t->recs[i];
88
+ t->lens[i] = (uint32_t)((r >> 8) & 0xFFFFFFu);
89
+ t->offs[i] = (uint32_t)(r >> 32);
90
+ t->kinds[i] = (uint8_t)(r & 0xFFu);
91
+ }
92
+ t->_cols_ready = 1;
93
+ return 0;
94
+ }
95
+
75
96
  /* Scalar roots: the walk needs an opener, so a bare root value
76
97
  * converts through the same kernels. */
77
98
  static int tape_put_root_scalar(tape_ctx* c, const char* p, size_t len, size_t at) {
@@ -585,20 +606,18 @@ static YeptrisStatus tape_walk_lnt_fused(const char* p, size_t len, size_t open,
585
606
  if (tape_carve(t, len) != YEPTRIS_OK) {
586
607
  return YEPTRIS_ERROR_MEMORY;
587
608
  }
588
- uint8_t* kinds = t->kinds;
589
- uint32_t* offs = t->offs;
590
- uint32_t* lens = t->lens;
609
+ /* the interleaved records are the primary storage (item 07); the
610
+ * columns materialize lazily via yeptris_tape_columns */
611
+ t->_rec_primary = 1;
612
+ yeptris_tape_rec* recs = t->recs;
591
613
  uint32_t open_at[YEP_JSON_WALK_DEPTH];
592
614
  uint8_t kind[YEP_JSON_WALK_DEPTH];
593
615
 
594
- kinds[0] = YEP_T_DOC;
595
- offs[0] = 0;
596
- lens[0] = 0;
616
+ recs[0] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | YEP_T_DOC;
597
617
 
598
618
  uint8_t top_kind = p[open] == '[' ? 0 : 1;
599
- kinds[1] = top_kind ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN;
600
- offs[1] = 0;
601
- lens[1] = 0;
619
+ recs[1] =
620
+ ((uint64_t)0 << 32) | ((uint64_t)0 << 8) | (top_kind ? YEP_T_MAP_OPEN : YEP_T_SEQ_OPEN);
602
621
  uint32_t top_open = 1;
603
622
  size_t count = 2;
604
623
  uint8_t top_expect = top_kind ? JW_KEY_OR_CLOSE : JW_VALUE_OR_CLOSE;
@@ -656,9 +675,8 @@ static YeptrisStatus tape_walk_lnt_fused(const char* p, size_t len, size_t open,
656
675
  uint64_t c0 = (w - 0x2020202020202020ull) & ~w & 0x8080808080808080ull;
657
676
  if (qm != 0 && ((bm | c0) & (qm - 1)) == 0) {
658
677
  close = j + (size_t)yep_ctz64(qm) / 8;
659
- kinds[count] = YEP_T_STR;
660
- offs[count] = (uint32_t)j;
661
- lens[count] = (uint32_t)(close - j);
678
+ recs[count] = ((uint64_t)((uint32_t)j) << 32) |
679
+ ((uint64_t)((uint32_t)(close - j)) << 8) | (uint64_t)(YEP_T_STR);
662
680
  count++;
663
681
  i = close + 1;
664
682
  goto lstr_done;
@@ -668,9 +686,8 @@ static YeptrisStatus tape_walk_lnt_fused(const char* p, size_t len, size_t open,
668
686
  if (!yep_json_string(p, len, &i, &close, &esc)) {
669
687
  goto lreject;
670
688
  }
671
- kinds[count] = YEP_T_STR;
672
- offs[count] = (uint32_t)(at + 1);
673
- lens[count] = (uint32_t)(close - at - 1);
689
+ recs[count] = ((uint64_t)((uint32_t)(at + 1)) << 32) |
690
+ ((uint64_t)((uint32_t)(close - at - 1)) << 8) | (uint64_t)(YEP_T_STR);
674
691
  count++;
675
692
  lstr_done:
676
693
  if (key_slot) {
@@ -698,9 +715,8 @@ static YeptrisStatus tape_walk_lnt_fused(const char* p, size_t len, size_t open,
698
715
  }
699
716
  break;
700
717
  }
701
- kinds[count] = YEP_T_NUM;
702
- offs[count] = (uint32_t)at;
703
- lens[count] = (uint32_t)(i - at);
718
+ recs[count] = ((uint64_t)((uint32_t)at) << 32) | ((uint64_t)((uint32_t)(i - at)) << 8) |
719
+ (uint64_t)(YEP_T_NUM);
704
720
  count++;
705
721
  lcomma:
706
722
  if (i < len && p[i] == ',') {
@@ -719,10 +735,11 @@ static YeptrisStatus tape_walk_lnt_fused(const char* p, size_t len, size_t open,
719
735
  top_expect != JW_COMMA_OR_CLOSE)) {
720
736
  goto lreject;
721
737
  }
722
- kinds[count] = YEP_T_CLOSE;
723
- offs[count] = top_open;
724
- lens[count] = 0;
725
- offs[top_open] = (uint32_t)count;
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);
726
743
  count++;
727
744
  i = at + 1;
728
745
  depth--;
@@ -745,9 +762,8 @@ static YeptrisStatus tape_walk_lnt_fused(const char* p, size_t len, size_t open,
745
762
  kind[depth - 1] = top_kind;
746
763
  open_at[depth - 1] = top_open;
747
764
  top_kind = c == '[' ? 0 : 1;
748
- kinds[count] = c == '[' ? YEP_T_SEQ_OPEN : YEP_T_MAP_OPEN;
749
- offs[count] = 0;
750
- lens[count] = 0;
765
+ recs[count] = ((uint64_t)0 << 32) | ((uint64_t)0 << 8) |
766
+ (uint64_t)(c == '[' ? YEP_T_SEQ_OPEN : YEP_T_MAP_OPEN);
751
767
  top_open = (uint32_t)count;
752
768
  count++;
753
769
  top_expect = top_kind ? JW_KEY_OR_CLOSE : JW_VALUE_OR_CLOSE;
@@ -778,9 +794,8 @@ static YeptrisStatus tape_walk_lnt_fused(const char* p, size_t len, size_t open,
778
794
  goto lreject;
779
795
  }
780
796
  }
781
- kinds[count] = c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE);
782
- offs[count] = (uint32_t)at;
783
- lens[count] = (uint32_t)wl;
797
+ recs[count] = ((uint64_t)((uint32_t)at) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
798
+ (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
784
799
  count++;
785
800
  i = at + wl;
786
801
  goto lcomma;
@@ -1244,6 +1259,7 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape(const char* source, size_t len
1244
1259
 
1245
1260
  YEPTRIS_API size_t yeptris_tape_convert(yeptris_json_tape* t, size_t from, size_t to,
1246
1261
  int64_t* ivals, double* dvals) {
1262
+ yeptris_tape_columns(t);
1247
1263
  if (t == NULL || t->_src == NULL || from > to || to > t->count) {
1248
1264
  return SIZE_MAX;
1249
1265
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.8.1
4
+ version: 0.6.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.