yeptris 0.6.22.1 → 0.6.24.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: b02278afda0c454592a7e6b4d95fb4649960c08a4b7e3ad7e5260467333edc57
4
- data.tar.gz: c554fd944064c8b691003b83d9e3a6adc8500a04865dcf6c4fa28db1ba320db5
3
+ metadata.gz: 5e37c8d5060f33c0a23b4ecf7fb540f811f8f540c1a942c09b26e57c10f7f138
4
+ data.tar.gz: a4574d33636fec838f1ceb5c06724507a496b4a41a16e7d9a764adb35f6d8fc6
5
5
  SHA512:
6
- metadata.gz: 7a1ed5aae840b006a5e2bb6ed27d8a1292969cb14b7963be92bd625f8ac0957ae974de91f73f11f8f642375eda5f773b747c21ee51479473742f7ff323f3b72a
7
- data.tar.gz: c21b92bec887dce27696762deb7818d30243647775e6113d6ccd4a2d07c759153be042f66f764c962afab3092bb30faacf265c51a1ccb5812ff07c89c8f918a4
6
+ metadata.gz: 5a3f7b3a97b1c38801e8ae382755f1795928905bcd85cca4d77f40724c94399792b14c8882388b034f120d027f4cdd1ee8eccdea3a40e0198fc9327bffb2fe47
7
+ data.tar.gz: 5e75a67767f990861d1343ddd05c6e0599e122fe8954c138ecafd9905ed017db28838a80dc05546e9d8bbb0f87ae95857a392fa04844a0721bc7ed5433e27a0f
@@ -17,6 +17,16 @@ class Yeptris::Document
17
17
  attr_reader :parse_schema
18
18
 
19
19
  def initialize(c_ptr = nil, freed = Freed.new(:alive), parse_schema = :core_12)
20
+ # The pointer is produced by the FFI parse/create calls; a nil (or
21
+ # non-pointer) argument is an API misuse — Document.parse and
22
+ # Document.create are the entry points. Fail with that guidance
23
+ # instead of a NoMethodError from the finalizer guard below (the
24
+ # FFI::Pointer#null? probe does not exist on nil).
25
+ unless c_ptr.is_a?(::FFI::Pointer)
26
+ raise ArgumentError,
27
+ "a document FFI pointer is required — build documents with " \
28
+ "Yeptris::Document.parse or Yeptris::Document.create"
29
+ end
20
30
  @c_ptr = c_ptr
21
31
  @freed = freed
22
32
  @parse_schema = parse_schema
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.22.1".freeze
6
+ VERSION = "0.6.24.1".freeze
7
7
 
8
8
  # Informational notices are OPT-IN (yeptris-ruby#217): consumers
9
9
  # embed yeptris inside CLIs that assert clean stderr, and a
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.22
6
+ VERSION 0.6.25
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -28,8 +28,13 @@ int yep_style_plain_safe(const char* p, uint32_t len) {
28
28
  case '-':
29
29
  case '?':
30
30
  /* indicators only before a blank or EOL: "-5" and "?x" are
31
- * plain (libyaml emits negative numbers plain; Psych, too) */
32
- return !(len == 1 || is_blank(p[1]));
31
+ * plain (libyaml emits negative numbers plain; Psych, too) —
32
+ * but fall through: the document-marker check below owns
33
+ * the bare "---"/"..." (libyaml quotes those) */
34
+ if (len == 1 || is_blank(p[1])) {
35
+ return 0;
36
+ }
37
+ break;
33
38
  case ',':
34
39
  case '[':
35
40
  case ']':
@@ -50,20 +55,67 @@ int yep_style_plain_safe(const char* p, uint32_t len) {
50
55
  default:
51
56
  break;
52
57
  }
53
- for (uint32_t i = 0; i < len; i++) {
54
- char c = p[i];
55
- if (is_break(c) || c == '\t') {
56
- return 0;
57
- }
58
- if (c == ':') {
59
- if (i + 1 >= len || is_blank(p[i + 1])) {
58
+ /* The interior scan in SWAR words: every reject condition is a
59
+ * byte-class adjacency, so one masked word test is exact —
60
+ * break/tab bytes reject directly; ':' rejects with a blank at its
61
+ * NEXT lane (or the span's EOL); '#' with a blank at its PREVIOUS
62
+ * lane (the span's first byte is exempt — the head switch already
63
+ * handled a leading '#'). Cross-word adjacency rides one carried
64
+ * byte per boundary; the tail runs the byte loop, whose rules read
65
+ * real neighbors and need no carries. */
66
+ {
67
+ const uint64_t ONES = 0x0101010101010101ull;
68
+ const uint64_t HIGH = 0x8080808080808080ull;
69
+ #define YST_ZDET(w, ch) \
70
+ ((((w) ^ (uint64_t)(ch) * ONES) - ONES) & ~((w) ^ (uint64_t)(ch) * ONES) & HIGH)
71
+ uint32_t i = 0;
72
+ int prev_blank7 = 0;
73
+ while (i + 8 <= len) {
74
+ uint64_t w;
75
+ memcpy(&w, p + i, 8);
76
+ uint64_t space = YST_ZDET(w, ' ');
77
+ uint64_t tab = YST_ZDET(w, '\t');
78
+ uint64_t blank = space | tab;
79
+ uint64_t colon = YST_ZDET(w, ':');
80
+ uint64_t hash = YST_ZDET(w, '#');
81
+ /* tab and the breaks reject outright */
82
+ if (tab | YST_ZDET(w, '\n') | YST_ZDET(w, '\r')) {
60
83
  return 0;
61
84
  }
85
+ /* ':' + blank next: (blank >> 8) pulls lane i+1's flag down
86
+ * into lane i; the word's last lane takes the peeked byte
87
+ * (EOL at the span end counts as blank — the rule rejects
88
+ * there) */
89
+ if (colon) {
90
+ int next_blank = i + 8 < len ? is_blank(p[i + 8]) : 1;
91
+ if (colon & ((blank >> 8) | (next_blank ? (uint64_t)0x80 << 56 : 0))) {
92
+ return 0;
93
+ }
94
+ }
95
+ /* '#' + blank prev: (blank << 8) lifts lane i-1's flag up
96
+ * into lane i; lane 0 takes the carried last-lane flag */
97
+ if (hash && (hash & ((blank << 8) | (prev_blank7 ? 0x80 : 0)))) {
98
+ return 0;
99
+ }
100
+ prev_blank7 = (int)((blank >> 63) & 1); /* lane 7's flag is its top bit */
101
+ i += 8;
62
102
  }
63
- if (c == '#') {
64
- if (i > 0 && is_blank(p[i - 1])) {
103
+ #undef YST_ZDET
104
+ for (; i < len; i++) {
105
+ char c = p[i];
106
+ if (is_break(c) || c == '\t') {
65
107
  return 0;
66
108
  }
109
+ if (c == ':') {
110
+ if (i + 1 >= len || is_blank(p[i + 1])) {
111
+ return 0;
112
+ }
113
+ }
114
+ if (c == '#') {
115
+ if (i > 0 && is_blank(p[i - 1])) {
116
+ return 0;
117
+ }
118
+ }
67
119
  }
68
120
  }
69
121
  /* document markers */
@@ -10,6 +10,10 @@
10
10
 
11
11
  #include <stdint.h>
12
12
 
13
+ #ifdef __cplusplus
14
+ extern "C" {
15
+ #endif
16
+
13
17
  /* A plain scalar may be re-emitted plain only when every byte is safe
14
18
  * in ANY position of a block context: no leading/trailing space, no
15
19
  * indicator first byte, no ": " / " #" inside, no tab, no break, and
@@ -21,4 +25,8 @@ int yep_style_plain_safe(const char* p, uint32_t len);
21
25
  * (plain_safe plus: no ':' at all unless followed by a non-blank). */
22
26
  int yep_style_plain_key_safe(const char* p, uint32_t len);
23
27
 
28
+ #ifdef __cplusplus
29
+ }
30
+ #endif
31
+
24
32
  #endif /* YEP_EMIT_STYLE_H */
@@ -52,6 +52,12 @@ YEPTRIS_API YeptrisStatus yeptris_recorder_feed(YeptrisRecorder rec, const char*
52
52
  }
53
53
  yep_engine_set_resolver(rec->eng,
54
54
  rec->compat11 ? yep_resolver_compat11() : yep_resolver_core12());
55
+ /* the compat schema is the psych/libyaml surface: its grammar
56
+ * must accept what libyaml accepts (the flow indent floor is
57
+ * suite-strict, not libyaml — see yep_engine_set_compat_grammar;
58
+ * the recorder is the py safe_load path, which rode the floor
59
+ * and rejected multi-line flows #431 fixed everywhere else) */
60
+ yep_engine_set_compat_grammar(rec->eng, rec->compat11);
55
61
  }
56
62
  if (final) {
57
63
  rec->final_fed = 1;
@@ -389,6 +389,11 @@ int yep_values_from_input(const char* yaml, size_t len, int schema_compat, yep_v
389
389
  return -1;
390
390
  }
391
391
  yep_engine_set_resolver(eng, schema_compat ? yep_resolver_compat11() : yep_resolver_core12());
392
+ if (schema_compat) {
393
+ yep_engine_set_compat_grammar(eng, 1); /* libyaml grammar parity
394
+ * (the value drains ride the
395
+ * same compat contract) */
396
+ }
392
397
 
393
398
  yep_value_ctx* c = ctx_create();
394
399
  if (c == NULL) {
@@ -124,6 +124,13 @@ struct yep_engine {
124
124
  int doc_inline; /* content node opened on the --- line */
125
125
  uint16_t flow_floor; /* parent column of the current block-level flow */
126
126
  int flow_enforce; /* flow continuation lines must out-indent it */
127
+ int compat_grammar; /* 11-COMPAT: libyaml/psych grammar parity —
128
+ * the flow indent floor (9C9N/VJP3) is a
129
+ * yaml-test-suite rule libyaml never
130
+ * enforced, and real-world locale files
131
+ * close multi-line flows at the parent's
132
+ * column; the floor stays on for the
133
+ * strict 1.2 engine the suite runs */
127
134
  };
128
135
 
129
136
  /* ------------------------------------------------------------ forwards */
@@ -2308,7 +2315,7 @@ static int e_shape_flow_value(yep_engine* e, const yep_line_shape* sh, uint16_t
2308
2315
  yep_view none = {NULL, 0};
2309
2316
  e->pos = sh->val_start;
2310
2317
  e->flow_floor = floor;
2311
- e->flow_enforce = (e->depth > 0);
2318
+ e->flow_enforce = (e->depth > 0) && !e->compat_grammar;
2312
2319
  int fast = e_flow_json(e, none, none, 0);
2313
2320
  e->flow_enforce = 0;
2314
2321
  if (fast == -2) {
@@ -2457,13 +2464,12 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
2457
2464
  }
2458
2465
  /* Whole-line fast path (TODO.restructure/54): the sink may build
2459
2466
  * the key and its classified value as one unit — no events. The
2460
- * value EVENT is prepared first (folded content, resolved alias,
2461
- * defined anchor: exactly what the chain below would emit), so a
2462
- * rejection emits key + prepared value unchanged. */
2463
- yep_event vv;
2464
- e_event_init(&vv, YEP_EV_SCALAR);
2465
- vv.line = e->line;
2466
- vv.col = e_col(e, sh->val_start) + 1;
2467
+ * value EVENT is assembled ONLY on the fallback path (the sinks
2468
+ * that take the pair read the yep_block_value and ignore it): the
2469
+ * semantic helpers run unchanged (alias resolution, anchor
2470
+ * definition, the fold), the fallback reuses the pair facts
2471
+ * captured before the fold — e_plain_multiline advances
2472
+ * line/pos across continuations, so post-fold e_col would lie. */
2467
2473
  /* the pair facts are captured BEFORE the fold: e_plain_multiline
2468
2474
  * advances line/pos across continuations */
2469
2475
  uint32_t pair_line = e->line;
@@ -2471,6 +2477,18 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
2471
2477
  yep_block_value v;
2472
2478
  memset(&v, 0, sizeof(v));
2473
2479
  v.cls = (uint8_t)sh->val;
2480
+ yep_event vv; /* the fields the fallback reads are assigned HERE and
2481
+ * overwritten by the arms/helpers — MSVC C4701 cannot
2482
+ * see through the helpers' bypointer writes, so the
2483
+ * zero-init lives in this function, not a memset */
2484
+ vv.value.p = NULL;
2485
+ vv.value.len = 0;
2486
+ vv.borrowed = 0;
2487
+ vv.anchor.p = NULL;
2488
+ vv.anchor.len = 0;
2489
+ vv.anchor_id = 0;
2490
+ vv.end_line = 0;
2491
+ vv.end_col = 0;
2474
2492
  int prepared = 0;
2475
2493
  if (sh->val == YEP_LVAL_ALIAS) {
2476
2494
  e->pos = sh->val_start;
@@ -2497,8 +2515,6 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
2497
2515
  vv.value.p = e->p + sh->val_span.start;
2498
2516
  vv.value.len = sh->val_span.end - sh->val_span.start;
2499
2517
  vv.borrowed = 1;
2500
- vv.style = YEP_STYLE_PLAIN;
2501
- vv.implicit = 1;
2502
2518
  e->pos = sh->val_span.end;
2503
2519
  if (e_plain_multiline(e, sh->val_span, key_col, &vv, 0) != 0) {
2504
2520
  return -1;
@@ -2523,9 +2539,30 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
2523
2539
  return 1;
2524
2540
  }
2525
2541
  }
2542
+ /* Fallback: assemble the value event with the helper-filled
2543
+ * fields preserved — e_alias stamps type=ALIAS + the end marks,
2544
+ * e_plain_multiline stamps the fold end marks; the pair facts
2545
+ * taken pre-fold (post-fold e_col would read the advanced
2546
+ * line/pos). Field-for-field what the pre-fill built. */
2547
+ yep_event ve;
2548
+ e_event_init(&ve, YEP_EV_SCALAR);
2549
+ ve.line = pair_line;
2550
+ ve.col = (uint32_t)pair_val_col + 1;
2551
+ ve.value = vv.value;
2552
+ ve.borrowed = vv.borrowed;
2553
+ ve.anchor = vv.anchor;
2554
+ ve.anchor_id = vv.anchor_id;
2555
+ ve.end_line = vv.end_line;
2556
+ ve.end_col = vv.end_col;
2557
+ if (sh->val == YEP_LVAL_ALIAS) {
2558
+ ve.type = YEP_EV_ALIAS;
2559
+ } else {
2560
+ ve.style = YEP_STYLE_PLAIN;
2561
+ ve.implicit = 1;
2562
+ }
2526
2563
  yep_event kv;
2527
2564
  e_key_event(e, sh, key_col, pair_line, &kv);
2528
- if (emit_now(e, &kv) != 0 || emit_now(e, &vv) != 0) {
2565
+ if (emit_now(e, &kv) != 0 || emit_now(e, &ve) != 0) {
2529
2566
  return -2;
2530
2567
  }
2531
2568
  return 1;
@@ -2705,7 +2742,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
2705
2742
  /* JSON fast path first: its validating scan already finds the
2706
2743
  * close, replacing e_skip_flow's pre-scan when it applies. */
2707
2744
  e->flow_floor = floor_col;
2708
- e->flow_enforce = (e->depth > 0);
2745
+ e->flow_enforce = (e->depth > 0) && !e->compat_grammar;
2709
2746
  int fast = e_flow_json(e, node_a, node_t, anchor_ordinal);
2710
2747
  e->flow_enforce = 0;
2711
2748
  if (fast == 1) {
@@ -2750,7 +2787,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
2750
2787
  }
2751
2788
  {
2752
2789
  e->flow_floor = floor_col;
2753
- e->flow_enforce = (e->depth > 0);
2790
+ e->flow_enforce = (e->depth > 0) && !e->compat_grammar;
2754
2791
  int rc = e_flow(e, node_a, node_t, node_aid);
2755
2792
  e->flow_enforce = 0;
2756
2793
  if (rc != 0) {
@@ -3082,6 +3119,12 @@ void yep_engine_set_resolver(yep_engine* e, const yep_resolver* r) {
3082
3119
  }
3083
3120
  }
3084
3121
 
3122
+ void yep_engine_set_compat_grammar(yep_engine* e, int on) {
3123
+ if (e != NULL) {
3124
+ e->compat_grammar = on != 0;
3125
+ }
3126
+ }
3127
+
3085
3128
  void yep_engine_set_max_depth(yep_engine* e, int depth) {
3086
3129
  if (e != NULL && depth > 0) {
3087
3130
  e->max_depth = depth > YEP_MAX_DEPTH ? YEP_MAX_DEPTH : depth;
@@ -52,6 +52,13 @@ struct yep_resolver;
52
52
  void yep_engine_set_resolver(yep_engine* e, const struct yep_resolver* r);
53
53
  void yep_engine_set_max_depth(yep_engine* e, int depth);
54
54
 
55
+ /* The 1.1-compat grammar (libyaml/psych parity): the block-level flow
56
+ * indent floor (9C9N, VJP3) is a yaml-test-suite rule libyaml never
57
+ * enforced — real-world locale files close multi-line flow collections
58
+ * at their parent's column ("k: {\n a: b\n}"). Off by default (the
59
+ * strict 1.2 engine the conformance suite runs keeps the floor). */
60
+ void yep_engine_set_compat_grammar(yep_engine* e, int on);
61
+
55
62
  /* Byte offset the last run reached (into that run's buffer); used by
56
63
  * consumers that stop the engine at a boundary and resume. */
57
64
  size_t yep_engine_pos(const yep_engine* e);
@@ -301,6 +301,12 @@ engine_enter:
301
301
  yep_engine_set_resolver(eng, opts->schema == YEPTRIS_SCHEMA_11_COMPAT
302
302
  ? yep_resolver_compat11()
303
303
  : yep_resolver_core12());
304
+ if (opts->schema == YEPTRIS_SCHEMA_11_COMPAT) {
305
+ /* the compat schema is the psych/libyaml surface: its grammar
306
+ * must accept what libyaml accepts (the flow floor is suite-
307
+ * strict, not libyaml — see yep_engine_set_compat_grammar) */
308
+ yep_engine_set_compat_grammar(eng, 1);
309
+ }
304
310
  if (opts->max_depth > 0) {
305
311
  yep_engine_set_max_depth(eng, opts->max_depth);
306
312
  }
@@ -601,6 +601,36 @@ reject:
601
601
  * (the classify scan carries no accept/reject structure) and
602
602
  * yeptris_tape_convert owns validation. Everything else matches
603
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
+ #define LNUM_RUN \
614
+ do { \
615
+ size_t ks_ = k; \
616
+ while (k + 8 <= len) { \
617
+ uint64_t w_; \
618
+ memcpy(&w_, p + k, 8); \
619
+ uint64_t nd_ = \
620
+ ((w_ + 0x4646464646464646ull) & 0x8080808080808080ull) | \
621
+ (((w_ | 0x8080808080808080ull) - 0xB0B0B0B0B0B0B0B0ull) & 0x8080808080808080ull) | \
622
+ (w_ & 0x8080808080808080ull); \
623
+ if (nd_ != 0) { \
624
+ k += (size_t)yep_ctz64(nd_) >> 3; \
625
+ break; \
626
+ } \
627
+ k += 8; \
628
+ } \
629
+ if (k > ks_) { \
630
+ saw_digit = 1; \
631
+ } \
632
+ } while (0)
633
+
604
634
  /* The strict RFC 8259 number check over one number-ish run — the
605
635
  * in-walk float/exp validator (the out-of-line number_shape call +
606
636
  * rescan was 13% of json-doc; the digits-only case never gets here).
@@ -884,103 +914,132 @@ lmap1: /* {} closes here */
884
914
  goto lmapkey;
885
915
 
886
916
  lmap: /* a member MUST follow (after ','): JW_KEY — a closer rejects */
917
+ /* compact fast path: the byte is the key's quote already — LWS's
918
+ * loop would examine it once and exit */
919
+ if (i < len && p[i] == '"') {
920
+ goto lmapkey;
921
+ }
887
922
  LWS();
888
923
  if (p[i] != '"') {
889
924
  goto lreject;
890
925
  }
891
926
  lmapkey: { LSTR_SCAN(lmapcolon); }
892
927
  lmapcolon:
928
+ if (i < len && p[i] == ':') {
929
+ i++;
930
+ /* compact fast path: the value byte follows the colon with no
931
+ * whitespace — one load dispatches it (the ws test below is
932
+ * what LWS would run anyway) */
933
+ if (i < len) {
934
+ char v = p[i];
935
+ if (v != ' ' && v != '\n' && v != '\r' && (cl || v != '\t')) {
936
+ goto lmapval;
937
+ }
938
+ }
939
+ LWS();
940
+ goto lmapval;
941
+ }
893
942
  LWS();
894
943
  if (p[i] != ':') {
895
944
  goto lreject;
896
945
  }
897
946
  i++;
898
947
  LWS();
899
- {
900
- char c = p[i];
901
- if (c == '"') {
902
- LSTR_SCAN(lmapafter);
903
- }
904
- if ((unsigned)(c - '0') <= 9u || c == '-') {
905
- size_t k = i + 1;
906
- /* the digits-only tracking makes the pure-integer case a
907
- * 3-cycle leading-zero check — number_shape (a call + a
908
- * rescan, 13% of json-doc) runs only for dot/exp spans */
909
- int digits_only = 1, saw_digit = (c != '-');
910
- while (k < len) {
911
- char d = p[k];
912
- if ((unsigned)(d - '0') <= 9u) {
913
- k++;
914
- saw_digit = 1;
915
- continue;
916
- }
917
- /* the flag clears only on a CONSUMED byte: the run's
918
- * terminator (',', '}', ']', whitespace) must leave
919
- * digits_only intact, or every plain integer followed
920
- * by a delimiter falls into the full validator */
921
- if (d == '-' || d == '+' || d == '.' || d == 'e' || d == 'E') {
922
- digits_only = 0;
923
- k++;
924
- continue;
925
- }
926
- break;
948
+ lmapval: {
949
+ char c = p[i];
950
+ if (c == '"') {
951
+ LSTR_SCAN(lmapafter);
952
+ }
953
+ if ((unsigned)(c - '0') <= 9u || c == '-') {
954
+ size_t k = i + 1;
955
+ /* the digits-only tracking makes the pure-integer case a
956
+ * 3-cycle leading-zero check — number_shape (a call + a
957
+ * rescan, 13% of json-doc) runs only for dot/exp spans */
958
+ int digits_only = 1, saw_digit = (c != '-');
959
+ LNUM_RUN;
960
+ while (k < len) {
961
+ char d = p[k];
962
+ if ((unsigned)(d - '0') <= 9u) {
963
+ k++;
964
+ saw_digit = 1;
965
+ continue;
927
966
  }
928
- if (strict_nums) {
929
- int ok;
930
- if (digits_only && saw_digit) {
931
- size_t d0 = (c == '-') ? i + 1 : i;
932
- ok = (k - d0 == 1) || p[d0] != '0';
933
- } else {
934
- ok = tape_num_ok(p + i, (size_t)(k - i));
935
- }
936
- if (!ok) {
937
- goto lreject;
938
- }
967
+ /* the flag clears only on a CONSUMED byte: the run's
968
+ * terminator (',', '}', ']', whitespace) must leave
969
+ * digits_only intact, or every plain integer followed
970
+ * by a delimiter falls into the full validator */
971
+ if (d == '-' || d == '+' || d == '.' || d == 'e' || d == 'E') {
972
+ digits_only = 0;
973
+ k++;
974
+ continue;
939
975
  }
940
- recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
941
- (uint64_t)(YEP_T_NUM);
942
- count++;
943
- i = k;
944
- goto lmapafter;
976
+ break;
945
977
  }
946
- if (c == 't' || c == 'f' || c == 'n') {
947
- size_t wl = c == 'f' ? 5 : 4;
948
- if (i + wl > len) {
949
- goto lreject;
978
+ if (strict_nums) {
979
+ int ok;
980
+ if (digits_only && saw_digit) {
981
+ size_t d0 = (c == '-') ? i + 1 : i;
982
+ ok = (k - d0 == 1) || p[d0] != '0';
983
+ } else {
984
+ ok = tape_num_ok(p + i, (size_t)(k - i));
950
985
  }
951
- uint32_t got4;
952
- memcpy(&got4, p + i, 4);
953
- if (got4 != (c == 't' ? 0x65757274u /* "true" */
954
- : c == 'n' ? 0x6C6C756Eu /* "null" */
955
- : 0x736C6166u /* "fals" */) ||
956
- (c == 'f' && p[i + 4] != 'e')) {
986
+ if (!ok) {
957
987
  goto lreject;
958
988
  }
959
- if (i + wl < len) {
960
- char z = p[i + wl];
961
- if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
962
- z != '}' && z != ':') {
963
- goto lreject;
964
- }
965
- }
966
- recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)wl) << 8) |
967
- (uint64_t)(c == 'n' ? YEP_T_NULL : (c == 't' ? YEP_T_TRUE : YEP_T_FALSE));
968
- count++;
969
- i += wl;
970
- goto lmapafter;
971
989
  }
972
- if (c == '{') {
973
- LPUSH(1);
974
- goto lmap1;
990
+ recs[count] = ((uint64_t)((uint32_t)i) << 32) | ((uint64_t)((uint32_t)(k - i)) << 8) |
991
+ (uint64_t)(YEP_T_NUM);
992
+ count++;
993
+ i = k;
994
+ goto lmapafter;
995
+ }
996
+ if (c == 't' || c == 'f' || c == 'n') {
997
+ size_t wl = c == 'f' ? 5 : 4;
998
+ if (i + wl > len) {
999
+ goto lreject;
975
1000
  }
976
- if (c == '[') {
977
- LPUSH(0);
978
- goto lseq1;
1001
+ uint32_t got4;
1002
+ memcpy(&got4, p + i, 4);
1003
+ if (got4 != (c == 't' ? 0x65757274u /* "true" */
1004
+ : c == 'n' ? 0x6C6C756Eu /* "null" */
1005
+ : 0x736C6166u /* "fals" */) ||
1006
+ (c == 'f' && p[i + 4] != 'e')) {
1007
+ goto lreject;
979
1008
  }
980
- goto lreject;
1009
+ if (i + wl < len) {
1010
+ char z = p[i + wl];
1011
+ if (z != ' ' && z != '\t' && z != '\n' && z != '\r' && z != ',' && z != ']' &&
1012
+ z != '}' && z != ':') {
1013
+ goto lreject;
1014
+ }
1015
+ }
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));
1018
+ count++;
1019
+ i += wl;
1020
+ goto lmapafter;
981
1021
  }
1022
+ if (c == '{') {
1023
+ LPUSH(1);
1024
+ goto lmap1;
1025
+ }
1026
+ if (c == '[') {
1027
+ LPUSH(0);
1028
+ goto lseq1;
1029
+ }
1030
+ goto lreject;
1031
+ }
982
1032
 
983
1033
  lmapafter:
1034
+ /* compact fast path: value ',' key directly — the two LWS hops the
1035
+ * general form runs both exit on byte one here */
1036
+ if (i < len && p[i] == ',') {
1037
+ i++;
1038
+ if (i < len && p[i] == '"') {
1039
+ goto lmapkey;
1040
+ }
1041
+ goto lmap;
1042
+ }
984
1043
  LWS();
985
1044
  if (p[i] == ',') {
986
1045
  i++;
@@ -1009,6 +1068,7 @@ lseqval: {
1009
1068
  if ((unsigned)(c - '0') <= 9u || c == '-') {
1010
1069
  size_t k = i + 1;
1011
1070
  int digits_only = 1, saw_digit = (c != '-');
1071
+ LNUM_RUN;
1012
1072
  while (k < len) {
1013
1073
  char d = p[k];
1014
1074
  if ((unsigned)(d - '0') <= 9u) {
@@ -1080,6 +1140,18 @@ lseqval: {
1080
1140
  }
1081
1141
 
1082
1142
  lseqafter:
1143
+ /* compact fast path: value ',' value directly — both LWS hops exit
1144
+ * on byte one here */
1145
+ if (i < len && p[i] == ',') {
1146
+ i++;
1147
+ if (i < len) {
1148
+ char v = p[i];
1149
+ if (v != ' ' && v != '\n' && v != '\r' && (cl || v != '\t')) {
1150
+ goto lseqval;
1151
+ }
1152
+ }
1153
+ goto lseq;
1154
+ }
1083
1155
  LWS();
1084
1156
  if (p[i] == ',') {
1085
1157
  i++;
@@ -1092,6 +1164,7 @@ lseqafter:
1092
1164
 
1093
1165
  ldone:
1094
1166
  #undef LWS
1167
+ #undef LNUM_RUN
1095
1168
  #undef LPUSH
1096
1169
  #undef LCLOSE
1097
1170
  #undef LSTR_SCAN
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.22.1
4
+ version: 0.6.24.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-25 00:00:00.000000000 Z
11
+ date: 2026-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi