yeptris 0.6.48.0-arm-linux-eabihf → 0.6.50.0-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.
@@ -254,6 +254,52 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument handle, const yeptris_emi
254
254
  return out;
255
255
  }
256
256
 
257
+ /* #739: the ONE-pass JSON route — one wet pass into a growable
258
+ * buffer; the dry exact-sizing pass priced the compact lane ~2x
259
+ * (the #352 slice-2 pattern). The estimate starts at the input size
260
+ * (+64) and doubles on demand; shrink-to-fit at the end. Owns the
261
+ * emitter's canon_names cleanup. */
262
+ static char* yep_json_run_growable(yep_emitter* em, size_t* len) {
263
+ size_t est = em->doc->dom->input_len + 64;
264
+ if (est < 256) {
265
+ est = 256;
266
+ }
267
+ char* out = (char*)malloc(est);
268
+ if (out == NULL) {
269
+ if (em->canon_names.sys != NULL) {
270
+ yep_nametab_free(&em->canon_names);
271
+ }
272
+ return NULL;
273
+ }
274
+ em->w.p = out;
275
+ em->w.cap = est;
276
+ em->w.grow = 1;
277
+ size_t wrote = yep_emit_run(em, 0);
278
+ em->w.grow = 0;
279
+ out = em->w.p; /* wr_grow reallocs through w->p — the local is stale */
280
+ if (em->w.oom || em->w.unserializable) {
281
+ free(out);
282
+ if (em->canon_names.sys != NULL) {
283
+ yep_nametab_free(&em->canon_names);
284
+ }
285
+ return NULL;
286
+ }
287
+ if (em->w.cap - (wrote + 1) > em->w.cap / 8) {
288
+ char* tight = (char*)realloc(out, wrote + 1);
289
+ if (tight != NULL) {
290
+ out = tight;
291
+ }
292
+ }
293
+ if (em->canon_names.sys != NULL) {
294
+ yep_nametab_free(&em->canon_names);
295
+ }
296
+ out[wrote] = '\0';
297
+ if (len != NULL) {
298
+ *len = wrote;
299
+ }
300
+ return out;
301
+ }
302
+
257
303
  YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
258
304
  if (handle == NULL) {
259
305
  return NULL;
@@ -290,51 +336,7 @@ YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
290
336
  em.w.sink_aborted = 0;
291
337
  em.w.flushed = 0;
292
338
  memset(&em.canon_names, 0, sizeof(em.canon_names));
293
- if (em.w.canonical && !yep_nametab_init(&em.canon_names, yep_system_allocator())) {
294
- return NULL;
295
- }
296
- /* #739: ONE wet pass into a growable buffer — the dry exact-sizing
297
- * pass priced ~2x on the json lane (the #352 slice-2 pattern);
298
- * the estimate starts at the input size (+64) and doubles on
299
- * demand, shrink-to-fit at the end. */
300
- size_t est = em.doc->dom->input_len + 64;
301
- if (est < 256) {
302
- est = 256;
303
- }
304
- char* out = (char*)malloc(est);
305
- if (out == NULL) {
306
- if (em.canon_names.sys != NULL) {
307
- yep_nametab_free(&em.canon_names);
308
- }
309
- return NULL;
310
- }
311
- em.w.p = out;
312
- em.w.cap = est;
313
- em.w.grow = 1;
314
- size_t wrote = yep_emit_run(&em, 0);
315
- em.w.grow = 0;
316
- out = em.w.p; /* wr_grow reallocs through w->p — the local is stale */
317
- if (em.w.oom || em.w.unserializable) {
318
- free(out);
319
- if (em.canon_names.sys != NULL) {
320
- yep_nametab_free(&em.canon_names);
321
- }
322
- return NULL;
323
- }
324
- if (em.w.cap - (wrote + 1) > em.w.cap / 8) {
325
- char* tight = (char*)realloc(out, wrote + 1);
326
- if (tight != NULL) {
327
- out = tight;
328
- }
329
- }
330
- if (em.canon_names.sys != NULL) {
331
- yep_nametab_free(&em.canon_names);
332
- }
333
- out[wrote] = '\0';
334
- if (len != NULL) {
335
- *len = wrote;
336
- }
337
- return out;
339
+ return yep_json_run_growable(&em, len);
338
340
  }
339
341
 
340
342
  YEPTRIS_API char* yeptris_serialize_json_ex(YeptrisDocument handle, size_t* len, int compact) {
@@ -376,27 +378,7 @@ YEPTRIS_API char* yeptris_serialize_json_ex(YeptrisDocument handle, size_t* len,
376
378
  em.w.sink_aborted = 0;
377
379
  em.w.flushed = 0;
378
380
  memset(&em.canon_names, 0, sizeof(em.canon_names));
379
- if (em.w.canonical && !yep_nametab_init(&em.canon_names, yep_system_allocator())) {
380
- return NULL;
381
- }
382
- size_t need = yep_emit_run(&em, 1);
383
- char* out = malloc(need + 1);
384
- if (out == NULL) {
385
- if (em.canon_names.sys != NULL) {
386
- yep_nametab_free(&em.canon_names);
387
- }
388
- return NULL;
389
- }
390
- em.w.p = out;
391
- size_t wrote = yep_emit_run(&em, 0);
392
- if (em.canon_names.sys != NULL) {
393
- yep_nametab_free(&em.canon_names);
394
- }
395
- out[wrote] = '\0';
396
- if (len != NULL) {
397
- *len = wrote;
398
- }
399
- return out;
381
+ return yep_json_run_growable(&em, len);
400
382
  }
401
383
 
402
384
  YEPTRIS_API char* yeptris_serialize_json_pretty(YeptrisDocument handle, size_t* len,
@@ -440,27 +422,7 @@ char* yep_serialize_json_compact(const yeptris_document* doc, size_t* len) {
440
422
  em.w.sink_aborted = 0;
441
423
  em.w.flushed = 0;
442
424
  memset(&em.canon_names, 0, sizeof(em.canon_names));
443
- if (em.w.canonical && !yep_nametab_init(&em.canon_names, yep_system_allocator())) {
444
- return NULL;
445
- }
446
- size_t need = yep_emit_run(&em, 1);
447
- char* out = malloc(need + 1);
448
- if (out == NULL) {
449
- if (em.canon_names.sys != NULL) {
450
- yep_nametab_free(&em.canon_names);
451
- }
452
- return NULL;
453
- }
454
- em.w.p = out;
455
- size_t wrote = yep_emit_run(&em, 0);
456
- if (em.canon_names.sys != NULL) {
457
- yep_nametab_free(&em.canon_names);
458
- }
459
- out[wrote] = '\0';
460
- if (len != NULL) {
461
- *len = wrote;
462
- }
463
- return out;
425
+ return yep_json_run_growable(&em, len);
464
426
  }
465
427
 
466
428
  char* yep_serialize_json_pretty(const yeptris_document* doc, size_t* len) {
@@ -75,6 +75,49 @@ int yep_style_plain_safe(const char* p, uint32_t len) {
75
75
  const uint64_t HIGH = 0x8080808080808080ull;
76
76
  #define YST_ZDET(w, ch) \
77
77
  ((((w) ^ (uint64_t)(ch) * ONES) - ONES) & ~((w) ^ (uint64_t)(ch) * ONES) & HIGH)
78
+ /* the all-boring accept (#742): a span of pure [0-9A-Za-z_]
79
+ * makes EVERY rule in this function vacuous — no indicators
80
+ * (the head switch), no blanks at the ends, no BOM, no
81
+ * ': '/' #' adjacency, no breaks, and no ---/... marker — so
82
+ * one range test per word accepts without the six-mask walk.
83
+ * Anything else falls through to the exact scan below. */
84
+ {
85
+ uint32_t bi = 0;
86
+ int boring = 1;
87
+ while (bi + 8 <= len) {
88
+ uint64_t bw;
89
+ memcpy(&bw, p + bi, 8);
90
+ if (bw & HIGH) {
91
+ boring = 0;
92
+ break;
93
+ }
94
+ /* per-lane unsigned range checks via borrow bits: alpha
95
+ * is (t - 'a') and ('z' - t) both borrow-free with
96
+ * t = w|0x20 (A-Z fold); digits bound 0x30..0x39 */
97
+ uint64_t t = bw | (ONES * 0x20);
98
+ uint64_t bad = ((t - ONES * 0x61) | (ONES * 0x7A - t)) & HIGH; /* alpha */
99
+ bad |= ((bw - ONES * 0x30) | (ONES * 0x39 - bw)) & HIGH; /* digit */
100
+ bad &= ~YST_ZDET(bw, '_'); /* the one extra boring byte */
101
+ if (bad != 0) {
102
+ boring = 0;
103
+ break;
104
+ }
105
+ bi += 8;
106
+ }
107
+ if (boring) {
108
+ for (; bi < len; bi++) {
109
+ char c = p[bi];
110
+ int a = (c | 0x20);
111
+ if (!((c >= '0' && c <= '9') || (a >= 'a' && a <= 'z') || c == '_')) {
112
+ boring = 0;
113
+ break;
114
+ }
115
+ }
116
+ if (boring) {
117
+ return 1;
118
+ }
119
+ }
120
+ }
78
121
  uint32_t i = 0;
79
122
  int prev_blank7 = 0;
80
123
  while (i + 8 <= len) {
@@ -528,9 +528,18 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
528
528
  }
529
529
  }
530
530
  }
531
- if ((sty_in == 1) && len > 0 &&
532
- (as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len))) {
533
- return YEP_SC_PLAIN; /* plain-safe implies no breaks: memchr skipped */
531
+ /* the plain verdict is computed AT MOST ONCE: the case-1 arm below
532
+ * re-tests the same bytes when the head check already ran — the
533
+ * #742 profile's second call site (every plain-UNSAFE scalar paid
534
+ * the walk twice) */
535
+ int plain_ok = 0;
536
+ int plain_checked = 0;
537
+ if ((sty_in == 1) && len > 0) {
538
+ plain_ok = as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len);
539
+ plain_checked = 1;
540
+ if (plain_ok) {
541
+ return YEP_SC_PLAIN; /* plain-safe implies no breaks: memchr skipped */
542
+ }
534
543
  }
535
544
  int multiline = (len > 0 && memchr(p, '\n', len) != NULL);
536
545
  int blockable = 0;
@@ -584,7 +593,11 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
584
593
  if (len == 0) {
585
594
  return as_key ? YEP_SC_DQ : YEP_SC_NOTHING;
586
595
  }
587
- if (as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len)) {
596
+ if (!plain_checked) {
597
+ plain_ok = as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len);
598
+ plain_checked = 1;
599
+ }
600
+ if (plain_ok) {
588
601
  return YEP_SC_PLAIN;
589
602
  }
590
603
  if (!multiline) {
@@ -941,8 +954,16 @@ static int key_written_over_limit(yep_writer* w, const yep_dnode* kn) {
941
954
  if (w->json) {
942
955
  return 0; /* JSON has no simple-key law: every key writes (#739) */
943
956
  }
944
- uint64_t est = 2; /* the quotes */
945
957
  yep_view kv_ = wv(w, kn->value);
958
+ if (kn->anchor.len == 0 && kn->tag.len == 0 && kv_.len <= 255) {
959
+ /* the exact bound: even at the factor-4 expansion the walk
960
+ * budgets per byte, 255 raw bytes top out at est = 2 + 1020 —
961
+ * under the law. Table-shaped documents pay the two field
962
+ * checks instead of the byte walk (the #742 profile's 22%
963
+ * lane) */
964
+ return 0;
965
+ }
966
+ uint64_t est = 2; /* the quotes */
946
967
  const unsigned char* q = (const unsigned char*)kv_.p;
947
968
  uint32_t n = kv_.len;
948
969
  /* NO early return on an empty value: the tag arm below must run —
@@ -1384,6 +1405,98 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key)
1384
1405
  emit_scalar(w, n, parent_col, as_key);
1385
1406
  }
1386
1407
 
1408
+ /* ============================================================
1409
+ * yeptris#739 slice 3: JSON's own tree walk. In JSON mode every
1410
+ * collection is flow, the separators are fixed, and the YAML
1411
+ * machinery (props lines, style analysis, explicit-key law,
1412
+ * pretty depth) never fires — yet the generic emit_node ->
1413
+ * emit_flow path re-tests all of it per node and looks every
1414
+ * child up twice. This walk emits the same bytes with one
1415
+ * dispatch per node and two per map pair. Pretty output keeps
1416
+ * the generic path: its depth/indent bookkeeping is the point
1417
+ * there.
1418
+ * ============================================================ */
1419
+ static void emit_json_node(yep_emitter* em, uint32_t id);
1420
+
1421
+ static void emit_json_flow(yep_emitter* em, uint32_t id) {
1422
+ yep_writer* w = &em->w;
1423
+ int saved_flow = w->in_flow;
1424
+ w->in_flow = 1;
1425
+ const yep_dom* d = em->doc->dom;
1426
+ const yep_dnode* n = yep_dom_node(d, id);
1427
+ if (n->tag.len > 0) {
1428
+ emit_tag(w, n);
1429
+ }
1430
+ if (n->anchor.len > 0) {
1431
+ emit_anchor_ex(em, n);
1432
+ }
1433
+ int map = (n->kind == 2);
1434
+ wr_byte(w, map ? '{' : '[');
1435
+ uint32_t child = n->first_child;
1436
+ uint32_t idx = 0;
1437
+ while (child != UINT32_MAX) {
1438
+ const yep_dnode* cn = yep_dom_node(d, child);
1439
+ if (idx > 0) {
1440
+ if (w->json_compact) {
1441
+ wr_byte(w, ',');
1442
+ } else {
1443
+ wr_put(w, ", ", 2);
1444
+ }
1445
+ }
1446
+ if (map) {
1447
+ /* the one generic-path rule JSON keeps: a non-scalar key
1448
+ * (or one carrying props) rides the explicit "? key : "
1449
+ * form — same bytes emit_flow writes */
1450
+ if (cn->anchor.len > 0 || cn->tag.len > 0 || cn->kind != 0) {
1451
+ wr_put(w, "? ", 2);
1452
+ emit_json_node(em, child);
1453
+ wr_put(w, " : ", 3);
1454
+ } else {
1455
+ emit_json_node(em, child);
1456
+ if (w->json_compact) {
1457
+ wr_byte(w, ':');
1458
+ } else {
1459
+ wr_put(w, ": ", 2);
1460
+ }
1461
+ }
1462
+ uint32_t val = cn->next_sibling;
1463
+ emit_json_node(em, val);
1464
+ const yep_dnode* vn = yep_dom_node(d, val);
1465
+ child = vn != NULL ? vn->next_sibling : UINT32_MAX;
1466
+ } else {
1467
+ emit_json_node(em, child);
1468
+ child = cn->next_sibling;
1469
+ }
1470
+ idx++;
1471
+ }
1472
+ wr_byte(w, map ? '}' : ']');
1473
+ w->in_flow = saved_flow;
1474
+ }
1475
+
1476
+ static void emit_json_node(yep_emitter* em, uint32_t id) {
1477
+ yep_writer* w = &em->w;
1478
+ const yep_dom* d = em->doc->dom;
1479
+ const yep_dnode* n = yep_dom_node(d, id);
1480
+ if (n == NULL) {
1481
+ return;
1482
+ }
1483
+ if (n->kind == 3) { /* alias */
1484
+ emit_alias_ex(em, n);
1485
+ return;
1486
+ }
1487
+ if (n->kind == 1 || n->kind == 2) {
1488
+ emit_json_flow(em, id);
1489
+ return;
1490
+ }
1491
+ if (n->tag.len > 0) {
1492
+ emit_tag(w, n);
1493
+ }
1494
+ if (n->anchor.len > 0) {
1495
+ emit_anchor_ex(em, n);
1496
+ }
1497
+ emit_canonical_scalar(w, n);
1498
+ }
1499
+
1387
1500
  /* ============================================================
1388
1501
  * yeptris-ruby#322: the marshal READER — Ruby Marshal.dump bytes
1389
1502
  * to JSON text in one C call (no per-scalar crossings, no object
@@ -1975,7 +2088,11 @@ size_t yep_emit_run(yep_emitter* em, int dry) {
1975
2088
  /* JSON has no multi-document streams and no document marker */
1976
2089
  if (d->dcount == 1) {
1977
2090
  const yep_dnode* root = yep_dom_node(d, d->docs[0]);
1978
- emit_node(em, d->docs[0], -2, 0);
2091
+ if (w->json_pretty) {
2092
+ emit_node(em, d->docs[0], -2, 0);
2093
+ } else {
2094
+ emit_json_node(em, d->docs[0]);
2095
+ }
1979
2096
  if (root != NULL && root->kind != 0 && w->last != '\n') {
1980
2097
  wr_byte(w, '\n');
1981
2098
  }
@@ -4192,7 +4192,7 @@ yep_engine* yep_engine_create(const yep_allocator* sys) {
4192
4192
  * nightly's run 38524928004: "\0\n---\r<U+2028>" whole-parsed as an
4193
4193
  * empty stream while the chunked cut skipped the copy). */
4194
4194
  static const char* e_normalize_breaks(yep_engine* e, const char* p, size_t len, size_t* out_len) {
4195
- char* out = yep_engine_normalize_breaks(e->sys, p, len, out_len);
4195
+ char* out = yep_engine_normalize_breaks(e->sys, p, len, out_len, NULL);
4196
4196
  if (out == NULL) {
4197
4197
  return p;
4198
4198
  }
@@ -4567,7 +4567,10 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
4567
4567
  }
4568
4568
 
4569
4569
  char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_t len,
4570
- size_t* out_len) {
4570
+ size_t* out_len, int* kept_raw) {
4571
+ if (kept_raw != NULL) {
4572
+ *kept_raw = 0;
4573
+ }
4571
4574
  const unsigned char* q = (const unsigned char*)p;
4572
4575
  const yep_text_kernels* k = yep_text_active();
4573
4576
  size_t i = 0;
@@ -4597,6 +4600,9 @@ char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_
4597
4600
  j += t.n;
4598
4601
  }
4599
4602
  if (!found) {
4603
+ if (kept_raw != NULL) {
4604
+ *kept_raw = 1; /* every exotic byte rides raw */
4605
+ }
4600
4606
  return NULL; /* the exotic bytes all ride inside quoted regions:
4601
4607
  * the zero-copy path keeps them raw (the fix) */
4602
4608
  }
@@ -4611,6 +4617,14 @@ char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_
4611
4617
  for (size_t j = 0; j < len;) {
4612
4618
  yep_norm_tok t = norm_step(&cp, p, len, j);
4613
4619
  if (t.fold_off < 0) {
4620
+ if (kept_raw != NULL && *kept_raw == 0) {
4621
+ for (size_t m = 0; m + 1 < t.n; m++) {
4622
+ if (yep_scan_mb_break_len(p + j + m, t.n - m, 0) > 0) {
4623
+ *kept_raw = 1;
4624
+ break;
4625
+ }
4626
+ }
4627
+ }
4614
4628
  memcpy(out + o, p + j, t.n);
4615
4629
  o += t.n;
4616
4630
  } else {
@@ -76,8 +76,13 @@ const yep_error* yep_engine_error(const yep_engine* e);
76
76
  * the stopset kernel). One-shot parse runs this BEFORE the engine so
77
77
  * the copy owns the document's transcoded slot; the engine's per-feed
78
78
  * copy (norm_buf) stays for the streaming feeds. */
79
+ /* kept_raw (nullable) reports whether raw NEL/LS/PS SURVIVED the
80
+ * quote-aware walk (they ride inside quoted regions). The packed-tape
81
+ * fused runner predates the multibyte break cut — a document still
82
+ * carrying raw sequences parses on the engine route instead (the
83
+ * nightly's r75). */
79
84
  char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_t len,
80
- size_t* out_len);
85
+ size_t* out_len, int* kept_raw);
81
86
 
82
87
  #ifdef __cplusplus
83
88
  }
@@ -244,6 +244,9 @@ static YeptrisDocument parse_impl(const char* buf, size_t len, const YeptrisPars
244
244
  unsigned char* transcoded = NULL;
245
245
  size_t transcoded_len = 0;
246
246
  yep_encoding enc = YEP_ENC_UNKNOWN;
247
+ int norm_kept_raw = 0; /* raw NEL/LS/PS survived the quote-aware
248
+ * walk: the engine route owns the document
249
+ * (the tape runner predates the cut) */
247
250
 
248
251
  if (json_mode) {
249
252
  /* JSON mode validated grammar and UTF-8 well-formedness at the
@@ -307,7 +310,7 @@ static YeptrisDocument parse_impl(const char* buf, size_t len, const YeptrisPars
307
310
  * U+0085 raw inside strings, only the YAML reader normalizes. */
308
311
  {
309
312
  size_t nlen = 0;
310
- char* norm = yep_engine_normalize_breaks(sys, data, data_len, &nlen);
313
+ char* norm = yep_engine_normalize_breaks(sys, data, data_len, &nlen, &norm_kept_raw);
311
314
  if (norm != NULL) {
312
315
  yep_free(sys, transcoded);
313
316
  transcoded = (unsigned char*)norm;
@@ -410,12 +413,12 @@ engine_enter:
410
413
  .on_block_item = dom_on_block_item};
411
414
  }
412
415
  int rc = 0;
413
- if (have_tape && ytap_flow_rooted(&yt) == 0) {
416
+ if (have_tape && !norm_kept_raw && ytap_flow_rooted(&yt) == 0) {
414
417
  /* a pure flow-rooted document: the classification ran once and
415
418
  * the scaffolding landed — neither runner nor engine runs */
416
419
  yep_engine_destroy(eng);
417
420
  eng = NULL;
418
- } else if (have_tape &&
421
+ } else if (have_tape && !norm_kept_raw &&
419
422
  ytap_fused_run(&yt, (opts != NULL && opts->schema == YEPTRIS_SCHEMA_11_COMPAT)
420
423
  ? yep_resolver_compat11()
421
424
  : yep_resolver_core12()) == 0) {
@@ -74,6 +74,8 @@ static YeptrisStatus tape_carve(yeptris_json_tape* t, size_t len) {
74
74
  t->int_min = 0;
75
75
  t->_rec_primary = 0;
76
76
  t->_cols_ready = 0;
77
+ t->_long_key_known = 0;
78
+ t->_long_key = 0;
77
79
  return YEPTRIS_OK;
78
80
  }
79
81
 
@@ -695,6 +697,9 @@ static TAPE_NUM_OK_INLINE int tape_num_ok(const char* p, size_t len) {
695
697
  * a long simple key, so the fast parse defers to the engine's clean
696
698
  * error — the nightly's run 38143486175). */
697
699
  int yep_tape_long_simple_key(const yeptris_json_tape* s) {
700
+ if (s->_long_key_known) {
701
+ return s->_long_key; /* settled during the lenient walk */
702
+ }
698
703
  struct {
699
704
  uint8_t map;
700
705
  uint8_t key_next;
@@ -758,6 +763,8 @@ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open
758
763
  t->_srclen >= len + 2) {
759
764
  t->_cols_ready = 0;
760
765
  t->_rec_primary = 1;
766
+ t->_long_key_known = 0;
767
+ t->_long_key = 0;
761
768
  } else if (tape_carve(t, len) != YEPTRIS_OK) {
762
769
  return YEPTRIS_ERROR_MEMORY;
763
770
  }
@@ -997,7 +1004,18 @@ lmap: /* a member MUST follow (after ','): JW_KEY — a closer rejects */
997
1004
  goto lreject;
998
1005
  }
999
1006
  lmapkey: { LSTR_SCAN(lmapcolon); }
1000
- lmapcolon:
1007
+ lmapcolon: { /* the key verdict rides the walk: the just-emitted record IS the
1008
+ * key — a >1024-byte candidate pays the char-budget check once
1009
+ * instead of a post-parse full-tape re-walk (#739) */
1010
+ uint64_t kr_ = recs[count - 1];
1011
+ uint32_t kl_ = (uint32_t)(kr_ >> 8) & 0xFFFFFFu;
1012
+ if (kl_ + 2u > 1024u && !t->_long_key_known) {
1013
+ t->_long_key_known = 1;
1014
+ t->_long_key =
1015
+ (unsigned char)(yep_simple_key_chars(p + (size_t)(uint32_t)(kr_ >> 32), kl_) + 2 >
1016
+ 1024);
1017
+ }
1018
+ }
1001
1019
  if (i < len && p[i] == ':') {
1002
1020
  i++;
1003
1021
  /* compact fast path: the value byte follows the colon with no
@@ -2183,55 +2201,61 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape(const char* source, size_t len
2183
2201
  return YEPTRIS_ERROR_ARG;
2184
2202
  }
2185
2203
 
2186
- int gated = len > 0 && yep_text_active()->gate_scan(source, len);
2187
- if (!gated) {
2188
- size_t off = 0;
2189
- while (off < len && (source[off] == ' ' || source[off] == '\t' || source[off] == '\n' ||
2190
- source[off] == '\r')) {
2191
- off++;
2192
- }
2193
- if (off < len && (source[off] == '[' || source[off] == '{')) {
2194
- /* the token-contract route: stage 1 (vector indexes) + the
2195
- * lean walk. Measured BEHIND the fused walk on json-doc
2196
- * (417-426 vs 526-533: stage 1 at 1408 MB/s + a walk whose
2197
- * arms carry the same grammar cost) — gated OFF until the
2198
- * sized cuts land (the op-table classifier trick, the
2199
- * specialized map/seq loops, stage-1 string closes). The
2200
- * fused walk is the shipped route; the law. */
2204
+ /* #474: the entry's full-input gate_scan pre-pass moves to the
2205
+ * reject path (the #342/#81 law on parse_json, now here): the
2206
+ * columns-primary fused walk runs clean_only — tabs and non-ASCII
2207
+ * bytes REJECT inline — so on accepted documents the NEON gate
2208
+ * scan (the profile's 5% lane) never runs. A walk reject re-gates:
2209
+ * the record walk and the encoding-owed decision read `gated` only
2210
+ * when actually taken, and every error precedence below is
2211
+ * unchanged (the validating sequence consumes the same bit). */
2212
+ int gated;
2213
+ size_t off = 0;
2214
+ while (off < len && (source[off] == ' ' || source[off] == '\t' || source[off] == '\n' ||
2215
+ source[off] == '\r')) {
2216
+ off++;
2217
+ }
2218
+ int container = off < len && (source[off] == '[' || source[off] == '{');
2219
+ if (container) {
2220
+ /* the columns-primary fused walk: one pass, columns eager
2221
+ * by construction, strict numbers classified at walk time */
2222
+ if (yep_tape_walk_columns_fused(source, len, off, tape, 1, 1) == YEPTRIS_OK) {
2223
+ return YEPTRIS_OK;
2224
+ }
2225
+ }
2226
+ gated = len > 0 && yep_text_active()->gate_scan(source, len);
2227
+ if (container && !gated) {
2228
+ /* the token-contract route: stage 1 (vector indexes) + the
2229
+ * lean walk. Measured BEHIND the fused walk on json-doc
2230
+ * (417-426 vs 526-533: stage 1 at 1408 MB/s + a walk whose
2231
+ * arms carry the same grammar cost) — gated OFF until the
2232
+ * sized cuts land (the op-table classifier trick, the
2233
+ * specialized map/seq loops, stage-1 string closes). The
2234
+ * fused walk is the shipped route; the law. */
2201
2235
  #if YEP_TOKEN_CONTRACT_ROUTE
2202
- uint32_t* idx = (uint32_t*)malloc((len + 2) * sizeof(uint32_t));
2203
- if (idx == NULL) {
2204
- return YEPTRIS_ERROR_MEMORY;
2205
- }
2206
- size_t nidx = 0;
2207
- unsigned sflags = 0;
2208
- if (yep_text_active()->json_stage1(source, len, idx, &nidx, &sflags)) {
2209
- YeptrisStatus st = tape_walk_idx(source, len, off, idx, nidx, tape);
2210
- free(idx);
2211
- if (st != YEPTRIS_ERROR_PARSE) {
2212
- return st; /* OK or MEMORY; a reject falls through */
2213
- }
2214
- } else {
2215
- free(idx);
2216
- }
2217
- #endif
2218
- /* the record-primary fused walk first — the DOM route's
2219
- * exact call and flags (strict numbers at record time,
2220
- * clean-only gates; a reject frees the tape internally, so
2221
- * the column attempt below carves fresh). This route's
2222
- * contract is COLUMNS-EAGER (the suite reads kinds/offs/
2223
- * lens directly), so the records materialize here — the
2224
- * fused scan plus one sequential decode pass, still ahead
2225
- * of the column walk's inline three-stream writes. */
2226
- /* the columns-primary fused walk: one pass, columns eager
2227
- * by construction, strict numbers classified at walk time */
2228
- if (yep_tape_walk_columns_fused(source, len, off, tape, 1, 1) == YEPTRIS_OK) {
2229
- return YEPTRIS_OK;
2230
- }
2231
- YeptrisStatus st = tape_walk(source, len, off, tape, 1);
2236
+ uint32_t* idx = (uint32_t*)malloc((len + 2) * sizeof(uint32_t));
2237
+ if (idx == NULL) {
2238
+ return YEPTRIS_ERROR_MEMORY;
2239
+ }
2240
+ size_t nidx = 0;
2241
+ unsigned sflags = 0;
2242
+ if (yep_text_active()->json_stage1(source, len, idx, &nidx, &sflags)) {
2243
+ YeptrisStatus st = tape_walk_idx(source, len, off, idx, nidx, tape);
2244
+ free(idx);
2232
2245
  if (st != YEPTRIS_ERROR_PARSE) {
2233
2246
  return st; /* OK or MEMORY; a reject falls through */
2234
2247
  }
2248
+ } else {
2249
+ free(idx);
2250
+ }
2251
+ #endif
2252
+ /* the record walk — the DOM route's exact call and flags
2253
+ * (strict numbers at record time). Non-ASCII documents stay
2254
+ * out of it: the gate above keeps today's routing, where the
2255
+ * validated lenient walk below owns them. */
2256
+ YeptrisStatus st = tape_walk(source, len, off, tape, 1);
2257
+ if (st != YEPTRIS_ERROR_PARSE) {
2258
+ return st; /* OK or MEMORY; a reject falls through */
2235
2259
  }
2236
2260
  }
2237
2261
 
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.48.0
4
+ version: 0.6.50.0
5
5
  platform: arm-linux-eabihf
6
6
  authors:
7
7
  - Ribose Inc.
@@ -93,6 +93,8 @@ files:
93
93
  - vendor/libyeptris/src/yeptris/cbor/cbor.h
94
94
  - vendor/libyeptris/src/yeptris/cbor/decode.c
95
95
  - vendor/libyeptris/src/yeptris/cbor/encode.c
96
+ - vendor/libyeptris/src/yeptris/cbor/encode_priv.h
97
+ - vendor/libyeptris/src/yeptris/cbor/from_marshal.c
96
98
  - vendor/libyeptris/src/yeptris/cbor/sink.h
97
99
  - vendor/libyeptris/src/yeptris/common/chartype.c
98
100
  - vendor/libyeptris/src/yeptris/common/chartype.h