yeptris 0.6.19.1-arm-linux-eabihf → 0.6.20.2-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.
@@ -0,0 +1,77 @@
1
+ /* ytape.h — the packed YAML record tape (#378, TODO.max-perf/12).
2
+ *
3
+ * The eager DOM builders' callback stream packed into 8-byte words:
4
+ * the common block ops carry spans only (the resolver call and the
5
+ * 48-byte node build defer to replay), the general event path packs
6
+ * into a props word plus span words. Replay calls the SAME builder
7
+ * functions with reconstructed views — resolution and placement
8
+ * semantics are the eager path's by construction (the differential
9
+ * gate pins tree identity across the corpora).
10
+ *
11
+ * Spans are input-relative when they fall inside [input, input+len]
12
+ * and fit 24 bits; anything else (pool content, oversized spans) is a
13
+ * generic (ptr, len) pair — the engine's finish pool owns that
14
+ * memory, its blocks are stable until the document frees it, and the
15
+ * lazy route keeps the pool on the document until replay copies (the
16
+ * builders copy exactly what must outlive the engine — dom_str_in).
17
+ *
18
+ * The recorder mirrors the eager builders' return-code discipline
19
+ * (depth caps answer 0 so the engine's event path owns the error;
20
+ * OOM answers <0) so error precedence survives the detour. */
21
+ #ifndef YEP_YTAPE_H
22
+ #define YEP_YTAPE_H
23
+
24
+ #include <stddef.h>
25
+
26
+ #include "parse/events.h"
27
+
28
+ #ifdef __cplusplus
29
+ extern "C" {
30
+ #endif
31
+
32
+ typedef struct yep_pool yep_pool;
33
+
34
+ typedef struct yep_ytape {
35
+ uint64_t* w;
36
+ uint32_t count;
37
+ uint32_t cap;
38
+ const char* input; /* the POST-BOM data pointer (span base) */
39
+ size_t input_len;
40
+ int depth; /* the DOM's depth mirror (cap parity) */
41
+ int max_depth; /* the engine's flow-walk limit, replayed */
42
+ int flow_strict; /* mirrors dom->flow_strict (0 everywhere today) */
43
+ int docs; /* DOCUMENT_START count (the empty-stream check) */
44
+ int oom;
45
+ uint32_t flow_mark; /* record mark for the staged flow build */
46
+ } yep_ytape;
47
+
48
+ /* Record kinds — byte 7 of a word. */
49
+ enum {
50
+ YTP_SPAN_IN = 0, /* [off:32][len:24][0x00] */
51
+ YTP_EV_COMPACT = 1, /* stream/document frames: [type:8][...][kind] */
52
+ YTP_EVENT = 2, /* props + value + [tag] + [anchor] */
53
+ YTP_SCALAR = 3, /* props + value + [tag] + [anchor]; is_item bit
54
+ * routes the replay to the dash-item builder */
55
+ YTP_ITEM = 4, /* borrowed dash item: [off:32][len:24][kind] */
56
+ YTP_PAIR = 5, /* props + key + value + [anchor] + [anchor hi] */
57
+ YTP_OPEN = 6, /* `key:`'s fresh map: [off:32][len:24][kind] */
58
+ YTP_FLOW = 7, /* props + [open:32|len:32] + [tag] + [anchor] */
59
+ YTP_SPAN_POOL = 0xFF, /* [len:32][kind] + [ptr:64] */
60
+ };
61
+
62
+ /* Recorder lifecycle. Returns 0, or -1 when the word carve fails
63
+ * (caller answers YEPTRIS_ERROR_MEMORY — bounded-parse law). */
64
+ int ytap_init(yep_ytape* t, const char* input, size_t len, int max_depth);
65
+ void ytap_sink(yep_ytape* t, yep_sink* sink);
66
+ void ytap_free(yep_ytape* t);
67
+
68
+ /* Replay: builds the node tree through the eager builders. Returns 0,
69
+ * or -1 (a builder refused — allocation/depth; the lazy route treats
70
+ * it as the JSON tape route does). Sets d->input_base/len. */
71
+ int dom_from_ytape(yep_dom* d, const yep_ytape* t);
72
+
73
+ #ifdef __cplusplus
74
+ }
75
+ #endif
76
+
77
+ #endif /* YEP_YTAPE_H */
@@ -91,7 +91,12 @@ static void wr_put(yep_writer* w, const char* p, uint32_t n) {
91
91
  w->len += n;
92
92
  return;
93
93
  }
94
- if (w->grow && w->len + n > w->cap) {
94
+ /* the +1 reserves the terminator slot: the serializers write the
95
+ * NUL at p[len] after the run, and a write landing exactly on cap
96
+ * (len + n == cap) put that NUL one byte past the buffer — the
97
+ * first byte of the next heap block's header (the #182 flake:
98
+ * corrupt deterministic, detected later, timing-varying) */
99
+ if (w->grow && w->len + n + 1 > w->cap) {
95
100
  wr_grow(w, n);
96
101
  }
97
102
  if (w->oom) {
@@ -114,7 +119,7 @@ static void wr_byte(yep_writer* w, char c) {
114
119
  w->len++;
115
120
  return;
116
121
  }
117
- if (w->grow && w->len + 1 > w->cap) {
122
+ if (w->grow && w->len + 2 > w->cap) { /* +1 write, +1 terminator */
118
123
  wr_grow(w, 1);
119
124
  }
120
125
  if (w->oom) {
@@ -5,6 +5,7 @@
5
5
  #include "common/simd_text.h"
6
6
  #include "doc.h"
7
7
  #include "dom/dom.h"
8
+ #include "dom/ytape.h"
8
9
  #include "encoding/encoding.h"
9
10
  #include "memory/allocator.h"
10
11
  #include "memory/pool.h"
@@ -33,7 +34,7 @@ YEPTRIS_API const char* yeptris_last_error(uint32_t* line, uint32_t* col) {
33
34
  }
34
35
 
35
36
  static YeptrisDocument parse_impl(const char* buf, size_t len, const YeptrisParseOptions* opts,
36
- int json_mode, YeptrisStatus* status);
37
+ int json_mode, int lazy, YeptrisStatus* status);
37
38
 
38
39
  YEPTRIS_API YeptrisDocument yeptris_parse(const char* buf, size_t len, YeptrisStatus* status) {
39
40
  return yeptris_parse_ex(buf, len, NULL, status);
@@ -47,6 +48,31 @@ yep_dom* yep_doc_dom(yeptris_document* doc) {
47
48
  if (doc == NULL) {
48
49
  return NULL;
49
50
  }
51
+ if (doc->dom == NULL && doc->lazy_tape != NULL && doc->lazy_kind == 1) {
52
+ /* #378: the YAML record tape — replay through the eager
53
+ * builders; the finish pool (whose spans the records carry)
54
+ * dies once the builders copied what must outlive it */
55
+ yep_ytape* t = (yep_ytape*)doc->lazy_tape;
56
+ yep_dom* dom = yep_dom_create(doc->sys);
57
+ if (dom != NULL) {
58
+ dom->resolver = (doc->schema == YEPTRIS_SCHEMA_11_COMPAT) ? yep_resolver_compat11()
59
+ : yep_resolver_core12();
60
+ }
61
+ if (dom != NULL && dom_from_ytape(dom, t) == 0) {
62
+ doc->dom = dom;
63
+ ytap_free(t);
64
+ yep_free(doc->sys, t);
65
+ doc->lazy_tape = NULL;
66
+ yep_pool_destroy((yep_pool*)doc->finish_pool);
67
+ doc->finish_pool = NULL;
68
+ } else {
69
+ yep_dom_destroy(dom);
70
+ dom = NULL; /* the tape stays: a later access retries, or
71
+ * free drops it (replay fails only on
72
+ * allocation/depth, as the builders do) */
73
+ }
74
+ return dom;
75
+ }
50
76
  if (doc->dom == NULL && doc->lazy_tape != NULL) {
51
77
  yeptris_json_tape* t = (yeptris_json_tape*)doc->lazy_tape;
52
78
  yep_dom* dom = yep_dom_create(doc->sys);
@@ -83,6 +109,21 @@ static int lazy_nums_settled(const yeptris_json_tape* t) {
83
109
  }
84
110
  uint32_t len = (uint32_t)((r >> 8) & 0x7FFFFFu);
85
111
  uint32_t off = (uint32_t)(r >> 32);
112
+ { /* the pure-integer fast path: all digits (optional '-'), no
113
+ * leading zero unless a lone digit — number_shape would
114
+ * consume the whole span and answer float-less; skip it */
115
+ const char* q = p + off;
116
+ size_t d = (*q == '-') ? 1 : 0;
117
+ size_t w = d;
118
+ for (; w < len; w++) {
119
+ if ((unsigned)(unsigned char)q[w] - '0' > 9u) {
120
+ break;
121
+ }
122
+ }
123
+ if (w == len && len > d && (len - d == 1 || q[d] != '0')) {
124
+ continue;
125
+ }
126
+ }
86
127
  size_t adv = 0;
87
128
  int flt = 0;
88
129
  if (yep_json_number_shape(p + off, len, &adv, &flt) == 0 || adv != (size_t)len) {
@@ -107,6 +148,7 @@ static YeptrisDocument yep_json_doc_wrap(yep_dom* dom, const char* buf, size_t l
107
148
  doc->dom = dom;
108
149
  doc->sys = sys;
109
150
  doc->lazy_tape = NULL;
151
+ doc->lazy_kind = 0;
110
152
  doc->schema = YEPTRIS_SCHEMA_12_CORE; /* strict JSON is core by construction */
111
153
  doc->transcoded = NULL;
112
154
  doc->transcoded_len = 0;
@@ -161,6 +203,7 @@ YEPTRIS_API YeptrisDocument yeptris_parse_json(const char* buf, size_t len, Yept
161
203
  YeptrisDocument h = yep_json_doc_wrap(NULL, buf, len, sys, status);
162
204
  if (h != NULL) {
163
205
  ((yeptris_document*)h)->lazy_tape = t;
206
+ ((yeptris_document*)h)->lazy_kind = 0;
164
207
  return h;
165
208
  }
166
209
  yeptris_tape_free(t); /* wrap failed (memory): below */
@@ -206,7 +249,7 @@ YEPTRIS_API YeptrisDocument yeptris_parse_json(const char* buf, size_t len, Yept
206
249
  }
207
250
  if (brc == -2) {
208
251
  yep_dom_destroy(dom);
209
- return parse_impl(buf, len, NULL, 1, status);
252
+ return parse_impl(buf, len, NULL, 1, 0, status);
210
253
  }
211
254
  return yep_json_doc_wrap(dom, buf, len, sys, status);
212
255
  }
@@ -218,7 +261,7 @@ jfail:
218
261
  }
219
262
 
220
263
  static YeptrisDocument parse_impl(const char* buf, size_t len, const YeptrisParseOptions* opts,
221
- int json_mode, YeptrisStatus* status) {
264
+ int json_mode, int lazy, YeptrisStatus* status) {
222
265
  YeptrisStatus st = YEPTRIS_OK;
223
266
  if (buf == NULL && len != 0) {
224
267
  st = YEPTRIS_ERROR_ARG;
@@ -303,24 +346,30 @@ engine_enter:
303
346
  st = YEPTRIS_ERROR_MEMORY;
304
347
  goto fail;
305
348
  }
306
- yep_dom* dom = yep_dom_create(sys);
307
- if (dom == NULL) {
308
- yep_engine_destroy(eng);
309
- yep_free(sys, transcoded);
310
- st = YEPTRIS_ERROR_MEMORY;
311
- goto fail;
349
+ /* the lazy route builds NO tree at parse: the dom (and its whole
350
+ * setup) exists only for the eager sink (the ASAN leak lesson —
351
+ * an unused dom leaked per lazy parse) */
352
+ yep_dom* dom = NULL;
353
+ if (!lazy) {
354
+ dom = yep_dom_create(sys);
355
+ if (dom == NULL) {
356
+ yep_engine_destroy(eng);
357
+ yep_free(sys, transcoded);
358
+ st = YEPTRIS_ERROR_MEMORY;
359
+ goto fail;
360
+ }
361
+ /* the direct builders resolve with the engine's schema (the typing
362
+ * SSOT — one resolver decision per scalar, whichever path builds) */
363
+ dom->resolver = (opts != NULL && opts->schema == YEPTRIS_SCHEMA_11_COMPAT)
364
+ ? yep_resolver_compat11()
365
+ : yep_resolver_core12();
366
+ /* BEFORE the run: input-slice strings borrow as views (zero copy)
367
+ * — the document keeps `transcoded` alive for exactly this. Set
368
+ * after, every scalar was arena-copied (found 2026-09-10). */
369
+ dom->input_len = data_len;
370
+ dom->input_base = transcoded ? (const char*)transcoded : buf;
371
+ yep_dom_prepare_len(dom, data_len);
312
372
  }
313
- /* the direct builders resolve with the engine's schema (the typing
314
- * SSOT — one resolver decision per scalar, whichever path builds) */
315
- dom->resolver = (opts != NULL && opts->schema == YEPTRIS_SCHEMA_11_COMPAT)
316
- ? yep_resolver_compat11()
317
- : yep_resolver_core12();
318
- /* BEFORE the run: input-slice strings borrow as views (zero copy)
319
- * — the document keeps `transcoded` alive for exactly this. Set
320
- * after, every scalar was arena-copied (found 2026-09-10). */
321
- dom->input_len = data_len;
322
- dom->input_base = transcoded ? (const char*)transcoded : buf;
323
- yep_dom_prepare_len(dom, data_len);
324
373
  {
325
374
  /* the nametab reserve survives: a memchr chain for '&' is
326
375
  * far cheaper than the class sweep it replaced */
@@ -336,15 +385,31 @@ engine_enter:
336
385
  yep_engine_prepare(eng, &amp_only);
337
386
  }
338
387
 
339
- yep_sink sink = {.on_event = yep_dom_on_event,
340
- .ctx = dom,
341
- .on_flow_build = dom_on_flow_build,
342
- .on_flow_commit = dom_on_flow_commit,
343
- .on_flow_rollback = dom_on_flow_rollback,
344
- .on_block_pair = dom_on_block_pair,
345
- .on_scalar = dom_on_scalar,
346
- .on_block_open = dom_on_block_open,
347
- .on_block_item = dom_on_block_item};
388
+ yep_ytape yt = {0}; /* zeroed: MSVC cannot couple have_tape to ytap_init's fill */
389
+ int have_tape = 0;
390
+ yep_sink sink;
391
+ if (lazy) {
392
+ /* #378: the recorder rides the SAME engine run — the fused
393
+ * arms stay hot, the builders' node lane defers to replay */
394
+ if (ytap_init(&yt, data, data_len, 0) != 0) {
395
+ yep_engine_destroy(eng);
396
+ yep_free(sys, transcoded);
397
+ st = YEPTRIS_ERROR_MEMORY;
398
+ goto fail;
399
+ }
400
+ ytap_sink(&yt, &sink);
401
+ have_tape = 1;
402
+ } else {
403
+ sink = (yep_sink){.on_event = yep_dom_on_event,
404
+ .ctx = dom,
405
+ .on_flow_build = dom_on_flow_build,
406
+ .on_flow_commit = dom_on_flow_commit,
407
+ .on_flow_rollback = dom_on_flow_rollback,
408
+ .on_block_pair = dom_on_block_pair,
409
+ .on_scalar = dom_on_scalar,
410
+ .on_block_open = dom_on_block_open,
411
+ .on_block_item = dom_on_block_item};
412
+ }
348
413
  int rc = yep_engine_run(eng, data, data_len, &sink);
349
414
  if (rc != 0) {
350
415
  const yep_error* ee = yep_engine_error(eng);
@@ -353,6 +418,9 @@ engine_enter:
353
418
  yep_error* tls = yep_error_tls();
354
419
  *tls = *ee;
355
420
  }
421
+ if (have_tape) {
422
+ ytap_free(&yt);
423
+ }
356
424
  yep_dom_destroy(dom);
357
425
  yep_engine_destroy(eng);
358
426
  yep_free(sys, transcoded);
@@ -366,12 +434,14 @@ engine_enter:
366
434
  * dangle at engine teardown (found by ASAN). */
367
435
  yep_pool* finish = yep_engine_detach_pool(eng);
368
436
  yep_engine_destroy(eng);
369
- /* DOM strings are input-offsets or arena copies; nothing in the
370
- * tree references the finish pool anymore — release it now */
371
437
 
372
438
  /* Empty stream (no documents): NULL document with YEPTRIS_OK. */
373
- if (dom->dcount == 0) {
374
- yep_dom_destroy(dom);
439
+ if (have_tape ? (yt.docs == 0) : (dom->dcount == 0)) {
440
+ if (have_tape) {
441
+ ytap_free(&yt);
442
+ } else {
443
+ yep_dom_destroy(dom);
444
+ }
375
445
  yep_pool_destroy(finish);
376
446
  yep_free(sys, transcoded);
377
447
  if (status != NULL) {
@@ -379,6 +449,13 @@ engine_enter:
379
449
  }
380
450
  return NULL;
381
451
  }
452
+ if (!have_tape) {
453
+ /* DOM strings are input-offsets or arena copies; nothing in the
454
+ * tree references the finish pool anymore — release it now */
455
+ yep_pool_destroy(finish);
456
+ finish = NULL;
457
+ } /* the lazy route keeps it: the records carry pool spans until
458
+ replay copies (doc.h's finish_pool contract) */
382
459
 
383
460
  yeptris_document* doc = yep_alloc(sys, sizeof(yeptris_document));
384
461
  if (doc == NULL) {
@@ -388,7 +465,6 @@ engine_enter:
388
465
  st = YEPTRIS_ERROR_MEMORY;
389
466
  goto fail;
390
467
  }
391
- doc->dom = dom;
392
468
  doc->sys = sys;
393
469
  doc->schema = (opts != NULL && opts->schema == YEPTRIS_SCHEMA_11_COMPAT)
394
470
  ? YEPTRIS_SCHEMA_11_COMPAT
@@ -396,12 +472,26 @@ engine_enter:
396
472
  doc->transcoded = transcoded;
397
473
  doc->transcoded_len = transcoded_len;
398
474
  doc->input = buf;
399
- /* the finish pool dies here: every DOM string is an input offset
400
- * or an arena copy (dom_ev_str) — nothing references it */
401
- yep_pool_destroy(finish);
402
- doc->finish_pool = NULL;
403
- doc->lazy_tape = NULL; /* field-by-field ctor: leave no garbage
404
- * (document_free frees a non-NULL tape) */
475
+ doc->finish_pool = finish; /* NULL on the eager route */
476
+ doc->lazy_tape = NULL;
477
+ doc->lazy_kind = 0;
478
+ if (have_tape) {
479
+ yep_ytape* heap_tape = yep_alloc(sys, sizeof(*heap_tape));
480
+ if (heap_tape == NULL) {
481
+ ytap_free(&yt);
482
+ yep_pool_destroy(finish);
483
+ yep_free(sys, transcoded);
484
+ yep_free(sys, doc);
485
+ st = YEPTRIS_ERROR_MEMORY;
486
+ goto fail;
487
+ }
488
+ *heap_tape = yt;
489
+ doc->dom = NULL;
490
+ doc->lazy_tape = heap_tape;
491
+ doc->lazy_kind = 1;
492
+ } else {
493
+ doc->dom = dom;
494
+ }
405
495
  return (YeptrisDocument)doc;
406
496
 
407
497
  fail:
@@ -414,7 +504,15 @@ fail:
414
504
  YEPTRIS_API YeptrisDocument yeptris_parse_ex(const char* buf, size_t len,
415
505
  const YeptrisParseOptions* opts,
416
506
  YeptrisStatus* status) {
417
- return parse_impl(buf, len, opts, 0, status);
507
+ return parse_impl(buf, len, opts, 0, 0, status);
508
+ }
509
+
510
+ /* #378 slice 1 (internal seam): the YAML parse rides the packed record
511
+ * tape — the tree materializes from the records on first access. Same
512
+ * grammar, same errors; parse-only workloads never build nodes. */
513
+ YeptrisDocument yeptris_parse_ytape_ex(const char* buf, size_t len, const YeptrisParseOptions* opts,
514
+ YeptrisStatus* status) {
515
+ return parse_impl(buf, len, opts, 0, 1, status);
418
516
  }
419
517
 
420
518
  YEPTRIS_API void yeptris_document_free(YeptrisDocument handle) {
@@ -423,7 +521,11 @@ YEPTRIS_API void yeptris_document_free(YeptrisDocument handle) {
423
521
  return;
424
522
  }
425
523
  if (doc->lazy_tape != NULL) { /* never materialized: free stays free */
426
- yeptris_tape_free((yeptris_json_tape*)doc->lazy_tape);
524
+ if (doc->lazy_kind == 1) {
525
+ ytap_free((yep_ytape*)doc->lazy_tape);
526
+ } else {
527
+ yeptris_tape_free((yeptris_json_tape*)doc->lazy_tape);
528
+ }
427
529
  yep_free(doc->sys, doc->lazy_tape);
428
530
  }
429
531
  yep_dom_destroy(doc->dom);
@@ -761,12 +761,13 @@ yep_jw_status yep_json_walk_next(yep_json_walk* w, yep_json_tok* t) {
761
761
  * parity error. The mask-level identity set (escape scanner via the
762
762
  * ODD_BITS borrow trick, prefix-xor string parity) is simdjson's. */
763
763
 
764
- YEPTRIS_API int yep_json_stage1_scalar(const char* p, size_t len, uint32_t* idx, size_t* nidx) {
764
+ YEPTRIS_API int yep_json_stage1_scalar(const char* p, size_t len, uint32_t* idx, size_t* nidx,
765
+ unsigned* flags) {
765
766
  size_t n = 0;
766
- uint64_t prev_in_string = 0, esc_carry = 0, follows_carry = 0;
767
+ uint64_t prev_in_string = 0, esc_carry = 0;
767
768
  for (size_t off = 0; off < len; off += 64) {
768
769
  size_t cn = len - off < 64 ? len - off : 64;
769
- uint64_t q = 0, bs = 0, op = 0, ws = 0;
770
+ uint64_t q = 0, bs = 0, op = 0, ws = 0, c0 = 0;
770
771
  for (size_t k = 0; k < cn; k++) {
771
772
  unsigned char c = (unsigned char)p[off + k];
772
773
  uint64_t bit = 1ull << k;
@@ -779,14 +780,51 @@ YEPTRIS_API int yep_json_stage1_scalar(const char* p, size_t len, uint32_t* idx,
779
780
  } else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
780
781
  ws |= bit;
781
782
  }
783
+ if (c < 0x20) {
784
+ c0 |= bit;
785
+ }
782
786
  }
783
787
  uint64_t valid = cn == 64 ? ~0ull : ((1ull << cn) - 1ull);
784
- n = yep_json_stage1_resolve(q, bs, op, ws, valid, &prev_in_string, &esc_carry,
785
- &follows_carry, off, idx, n);
788
+ n = yep_json_stage1_resolve(q, bs, op, ws, c0, valid, &prev_in_string, &esc_carry, off, idx,
789
+ n, flags);
786
790
  }
787
791
  *nidx = n;
788
792
  return prev_in_string ? 0 : 1;
789
793
  }
790
794
 
795
+ YEPTRIS_API int yep_json_stage1_masks_scalar(const char* p, size_t len, struct yep_s1_block* blocks,
796
+ size_t* nblocks, unsigned* flags) {
797
+ size_t nb = 0;
798
+ uint64_t prev_in_string = 0, esc_carry = 0;
799
+ for (size_t off = 0; off < len; off += 64) {
800
+ size_t cn = len - off < 64 ? len - off : 64;
801
+ uint64_t q = 0, bs = 0, op = 0, c0 = 0;
802
+ for (size_t k = 0; k < cn; k++) {
803
+ unsigned char c = (unsigned char)p[off + k];
804
+ uint64_t bit = 1ull << k;
805
+ if (c == '"') {
806
+ q |= bit;
807
+ } else if (c == '\\') {
808
+ bs |= bit;
809
+ } else if (c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':') {
810
+ op |= bit;
811
+ }
812
+ if (c < 0x20) {
813
+ c0 |= bit;
814
+ }
815
+ }
816
+ uint64_t valid = cn == 64 ? ~0ull : ((1ull << cn) - 1ull);
817
+ blocks[nb].q = q;
818
+ blocks[nb].bs = bs;
819
+ blocks[nb].op = op;
820
+ blocks[nb].c0 = c0;
821
+ blocks[nb].tokens =
822
+ yep_json_stage1_tokens(q, bs, op, c0, valid, &prev_in_string, &esc_carry, flags);
823
+ nb++;
824
+ }
825
+ *nblocks = nb;
826
+ return prev_in_string ? 0 : 1;
827
+ }
828
+
791
829
  /* The per-chunk classifier: the kernels table's json_chunk slot
792
830
  * (scalar reference in common, NEON/AVX2 TUs on their ISAs). */
@@ -93,8 +93,15 @@ YEPTRIS_API int yep_json_string(const char* p, size_t len, size_t* i, size_t* cl
93
93
  * (scalar reference; the kernels table's json_stage1 slot carries the
94
94
  * ISA twins). Writes the token positions stage 2 dispatches on —
95
95
  * operators, string OPEN quotes, scalar-run starts — and returns 0
96
- * only for an unterminated string. idx holds len+2 entries. */
97
- YEPTRIS_API int yep_json_stage1_scalar(const char* p, size_t len, uint32_t* idx, size_t* nidx);
96
+ * only for an unterminated string. idx holds len+2 entries. flags
97
+ * (never NULL) accumulates the YEP_S1_* doc-level facts. */
98
+ YEPTRIS_API int yep_json_stage1_scalar(const char* p, size_t len, uint32_t* idx, size_t* nidx,
99
+ unsigned* flags);
100
+ /* The classification half (no emission) — the kernels table's
101
+ * json_stage1_masks slot carries the ISA twins. */
102
+ struct yep_s1_block; /* defined in scan/json.c (the span blocks) */
103
+ YEPTRIS_API int yep_json_stage1_masks_scalar(const char* p, size_t len, struct yep_s1_block* blocks,
104
+ size_t* nblocks, unsigned* flags);
98
105
 
99
106
  /* The per-chunk byte-class classifier behind the flow kernels: the
100
107
  * kernels table's json_chunk slot (common/simd_text.h) — yep_chunk_masks