yeptris 0.6.59.1-arm-linux-eabihf → 0.6.60.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/ext/yeptris_native/version.h +2 -2
  3. data/lib/yeptris/3.4/native.so +0 -0
  4. data/lib/yeptris/ffi.rb +19 -0
  5. data/lib/yeptris/yaml.rb +61 -0
  6. data/lib/yeptris.rb +1 -1
  7. data/libyeptris.so +0 -0
  8. data/vendor/libyeptris/CMakeLists.txt +1 -1
  9. data/vendor/libyeptris/src/CMakeLists.txt +7 -0
  10. data/vendor/libyeptris/src/include/yeptris/emit.h +30 -2
  11. data/vendor/libyeptris/src/include/yeptris/parse.h +52 -0
  12. data/vendor/libyeptris/src/include/yeptris/resolve.h +8 -0
  13. data/vendor/libyeptris/src/include/yeptris/validate.h +222 -21
  14. data/vendor/libyeptris/src/include/yeptris/values.h +8 -2
  15. data/vendor/libyeptris/src/yeptris/cbor/cbor.h +10 -0
  16. data/vendor/libyeptris/src/yeptris/cbor/decode.c +5 -7
  17. data/vendor/libyeptris/src/yeptris/cbor/encode.c +9 -9
  18. data/vendor/libyeptris/src/yeptris/cbor/from_marshal.c +1 -1
  19. data/vendor/libyeptris/src/yeptris/common/error.h +2 -0
  20. data/vendor/libyeptris/src/yeptris/doc.h +20 -0
  21. data/vendor/libyeptris/src/yeptris/emit/emit.c +85 -13
  22. data/vendor/libyeptris/src/yeptris/emit/writer.c +158 -642
  23. data/vendor/libyeptris/src/yeptris/emit/writer.h +15 -1
  24. data/vendor/libyeptris/src/yeptris/events/push.c +28 -3
  25. data/vendor/libyeptris/src/yeptris/events/values.c +24 -0
  26. data/vendor/libyeptris/src/yeptris/marshal/to_json.c +650 -0
  27. data/vendor/libyeptris/src/yeptris/parse/engine.c +121 -40
  28. data/vendor/libyeptris/src/yeptris/parse/stream_iter.c +326 -0
  29. data/vendor/libyeptris/src/yeptris/parse.c +173 -43
  30. data/vendor/libyeptris/src/yeptris/resolve/compat11.c +1 -1
  31. data/vendor/libyeptris/src/yeptris/resolve/core12.c +11 -3
  32. data/vendor/libyeptris/src/yeptris/resolve/full12.c +201 -0
  33. data/vendor/libyeptris/src/yeptris/resolve/json12.c +12 -6
  34. data/vendor/libyeptris/src/yeptris/resolve/resolver.h +20 -0
  35. data/vendor/libyeptris/src/yeptris/validate/compile.c +39 -3
  36. data/vendor/libyeptris/src/yeptris/validate/internal.h +223 -7
  37. data/vendor/libyeptris/src/yeptris/validate/jsonschema.c +645 -115
  38. data/vendor/libyeptris/src/yeptris/validate/output.c +246 -0
  39. data/vendor/libyeptris/src/yeptris/validate/regexp.c +623 -157
  40. data/vendor/libyeptris/src/yeptris/validate/registry.c +118 -0
  41. data/vendor/libyeptris/src/yeptris/validate/resources.c +601 -0
  42. data/vendor/libyeptris/src/yeptris/validate/stream.c +1155 -0
  43. data/vendor/libyeptris/src/yeptris/validate/validate.c +288 -145
  44. metadata +8 -1
@@ -51,7 +51,6 @@ typedef enum {
51
51
  typedef struct {
52
52
  uint32_t col;
53
53
  uint8_t kind;
54
- uint8_t inline_doc; /* opened on the --- line: deeper keys only */
55
54
  } yep_frame;
56
55
 
57
56
  struct yep_engine {
@@ -162,7 +161,8 @@ struct yep_engine {
162
161
  overflow crasher) */
163
162
  int last_root_flow; /* a root-level flow node just completed */
164
163
  int tag_undef; /* an unresolved !handle! was seen (QLJ7) */
165
- int doc_inline; /* content node opened on the --- line */
164
+ int doc_inline; /* the node opens on the --- line: block collections
165
+ * refuse there (the marker-line law, #900) */
166
166
  uint32_t flow_floor; /* parent column of the current block-level flow */
167
167
  int flow_enforce; /* flow continuation lines must out-indent it */
168
168
  int compat_grammar; /* 11-COMPAT: libyaml/psych grammar parity —
@@ -218,6 +218,14 @@ static int e_fail(yep_engine* e, yep_err_code code, size_t at) {
218
218
  return -1;
219
219
  }
220
220
 
221
+ /* e_fail with an explicit message: the document-boundary diagnostics
222
+ * spell libyaml's exact words (issue #900 — the oracle's message
223
+ * surface, not the generic default-arm string). */
224
+ static int e_fail_msg(yep_engine* e, yep_err_code code, size_t at, const char* msg) {
225
+ yep_error_set(&e->err, code, e->line, e_col(e, at) + 1, at, "%s", msg);
226
+ return -1;
227
+ }
228
+
221
229
  /* A simple key (no explicit '?') is limited to one line and 1024
222
230
  * characters. The span runs from the node's first byte (opening
223
231
  * quote, alias star — not its anchor/tag properties) to the ':'. */
@@ -237,20 +245,29 @@ static void e_anchor_mark(yep_engine* e, uint32_t id);
237
245
 
238
246
  static int emit_now(yep_engine* e, const yep_event* ev) {
239
247
  e_anchor_mark(e, ev->anchor_id);
240
- if (ev->type == YEP_EV_SCALAR) {
241
- /* Resolution happens exactly here: every scalar path flows
242
- * through this choke point (the resolver is the typing SSOT) */
243
- if (ev->tag.len > 0) {
244
- yep_tag_id tid = yep_tag_from_uri((const char*)ev->tag.p, ev->tag.len);
245
- /* psych's own dumped form is the LOCAL !binary shorthand,
246
- * and stdlib to_ruby accepts it beside the core URI (#168)
247
- * — compat schemas resolve it to BINARY as well */
248
- if (tid == YEPTRIS_TAG_CUSTOM && ev->tag.len == 7 &&
249
- memcmp(ev->tag.p, "!binary", 7) == 0 && e->resolver == yep_resolver_compat11()) {
250
- tid = YEPTRIS_TAG_BINARY;
251
- }
252
- ((yep_event*)ev)->tag_id = tid;
253
- } else if (ev->implicit && e->resolver != NULL) {
248
+ if (ev->tag.len > 0) {
249
+ /* Explicit-tag resolution (the typing SSOT for SCALARS AND
250
+ * COLLECTIONS): an event's tag_id names the explicit tag's id,
251
+ * so the family walk's consistency check can tell `!!map` on a
252
+ * mapping, `!!seq` on a sequence and a consumer-defined custom
253
+ * tag (CUSTOM, no family verdict) apart — a collection event's
254
+ * id used to stay 0 ("str"), which made EVERY tagged collection
255
+ * read as `!!str` and diagnose a false mismatch (#906 slice 4's
256
+ * tag-driven lane needs the true id). Both routes read this
257
+ * field, so the DOM and event walks stay in lockstep. */
258
+ yep_tag_id tid = yep_tag_from_uri((const char*)ev->tag.p, ev->tag.len);
259
+ /* psych's own dumped form is the LOCAL !binary shorthand,
260
+ * and stdlib to_ruby accepts it beside the core URI (#168)
261
+ * — compat schemas resolve it to BINARY as well */
262
+ if (ev->type == YEP_EV_SCALAR && tid == YEPTRIS_TAG_CUSTOM && ev->tag.len == 7 &&
263
+ memcmp(ev->tag.p, "!binary", 7) == 0 && e->resolver == yep_resolver_compat11()) {
264
+ tid = YEPTRIS_TAG_BINARY;
265
+ }
266
+ ((yep_event*)ev)->tag_id = tid;
267
+ } else if (ev->type == YEP_EV_SCALAR) {
268
+ /* implicit typing: scalars only (a collection's id stays 0,
269
+ * its "no tag" value) */
270
+ if (ev->implicit && e->resolver != NULL) {
254
271
  ((yep_event*)ev)->tag_id = e->resolver->resolve(
255
272
  e->resolver->ctx, (const char*)ev->value.p, (uint32_t)ev->value.len);
256
273
  } else {
@@ -468,13 +485,24 @@ static int e_open_seq(yep_engine* e, uint32_t col, uint32_t line, uint32_t coln,
468
485
  e->frames[e->depth - 1].col == col) {
469
486
  return 0;
470
487
  }
488
+ if (e->doc_inline) {
489
+ /* the marker-line law (issue #900): a block collection cannot
490
+ * open on the --- line. libyaml's scanner disables block
491
+ * entries after the document-start indicator until a line
492
+ * break; libyaml 0.2.5/90a56d4, Psych 5 and PyYAML (pure and
493
+ * C) all reject ("block sequence entries are not allowed in
494
+ * this context"). The suite has no seq-class pin — its
495
+ * 9KBC/CXX2 fail-pins cover the mapping twin in e_open_map —
496
+ * so the oracle's law governs here. The cursor sits at the
497
+ * dash, which is libyaml's problem mark. */
498
+ return e_fail_msg(e, YEP_ERR_DOC_BOUNDARY, e->pos,
499
+ "block sequence entries are not allowed in this context");
500
+ }
471
501
  if (e->depth >= e->max_depth) {
472
502
  return e_fail(e, YEP_ERR_DEPTH, e->pos);
473
503
  }
474
504
  e->frames[e->depth].col = col;
475
505
  e->frames[e->depth].kind = YEP_FRAME_SEQ;
476
- e->frames[e->depth].inline_doc = (uint8_t)e->doc_inline;
477
- e->doc_inline = 0;
478
506
  e->depth++;
479
507
  yep_event ev;
480
508
  e_event_init(&ev, YEP_EV_SEQ_START);
@@ -506,13 +534,38 @@ static int e_open_map(yep_engine* e, uint32_t col, uint32_t line, uint32_t coln,
506
534
  e->frames[e->depth - 1].col == col) {
507
535
  return 0;
508
536
  }
537
+ if (e->doc_inline) {
538
+ /* the marker-line law (issue #900): a block mapping cannot open
539
+ * on the --- line. libyaml's scanner keeps simple keys disabled
540
+ * after the document-start indicator until a line break, so the
541
+ * pair separator reads as "mapping values are not allowed in
542
+ * this context" (the explicit-key form: "mapping keys ...");
543
+ * libyaml 0.2.5/90a56d4, Psych 5 and PyYAML (pure and C) all
544
+ * reject, and the suite pins the class INVALID (9KBC/CXX2).
545
+ * The mark is the pair separator itself — libyaml's problem
546
+ * mark ("--- a: 1" marks 1:6, the colon). */
547
+ size_t at = e->pos;
548
+ const char* msg;
549
+ if (at < e->len && e->p[at] == '?') {
550
+ msg = "mapping keys are not allowed in this context";
551
+ } else {
552
+ msg = "mapping values are not allowed in this context";
553
+ while (at < e->len && e->p[at] != '\n' && e->p[at] != '\r') {
554
+ if (e->p[at] == ':' &&
555
+ (at + 1 >= e->len || e->p[at + 1] == ' ' || e->p[at + 1] == '\t' ||
556
+ e->p[at + 1] == '\n' || e->p[at + 1] == '\r')) {
557
+ break;
558
+ }
559
+ at++;
560
+ }
561
+ }
562
+ return e_fail_msg(e, YEP_ERR_DOC_BOUNDARY, at, msg);
563
+ }
509
564
  if (e->depth >= e->max_depth) {
510
565
  return e_fail(e, YEP_ERR_DEPTH, e->pos);
511
566
  }
512
567
  e->frames[e->depth].col = col;
513
568
  e->frames[e->depth].kind = YEP_FRAME_MAP;
514
- e->frames[e->depth].inline_doc = (uint8_t)e->doc_inline;
515
- e->doc_inline = 0;
516
569
  e->depth++;
517
570
  if (silent) {
518
571
  return 0;
@@ -3496,7 +3549,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint32_t floor_col) {
3496
3549
  }
3497
3550
  if (c == '-' && (e->pos + 1 >= e->len || e->p[e->pos + 1] == ' ' || e->p[e->pos + 1] == '\t' ||
3498
3551
  e->p[e->pos + 1] == '\n' || e->p[e->pos + 1] == '\r')) {
3499
- if (e->pos > 0 && e->p[e->pos - 1] == '\t') {
3552
+ if (e->pos > 0 && e->p[e->pos - 1] == '\t' && !e->doc_inline) {
3500
3553
  return e_fail(e, YEP_ERR_UNEXPECTED, e->pos); /* "-\t-" (Y79Y) */
3501
3554
  }
3502
3555
  if (!yep_view_is_empty(anchor) || !yep_view_is_empty(tag)) {
@@ -3532,7 +3585,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint32_t floor_col) {
3532
3585
  * reference (the nightly's run 38355778230). */
3533
3586
  return e_fail(e, YEP_ERR_UNEXPECTED, e->pos);
3534
3587
  }
3535
- if (e->pos > 0 && e->p[e->pos - 1] == '\t') {
3588
+ if (e->pos > 0 && e->p[e->pos - 1] == '\t' && !e->doc_inline) {
3536
3589
  return e_fail(e, YEP_ERR_UNEXPECTED, e->pos); /* "?\t-" (Y79Y) */
3537
3590
  }
3538
3591
  uint32_t col = e_col(e, e->pos);
@@ -3788,15 +3841,17 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint32_t floor_col) {
3788
3841
  if (ctx == YEP_CTX_AFTER_COLON) {
3789
3842
  return e_fail(e, YEP_ERR_UNEXPECTED, s.end);
3790
3843
  }
3791
- if (node_at > 0 && e->p[node_at - 1] == '\t') {
3844
+ if (node_at > 0 && e->p[node_at - 1] == '\t' && !e->doc_inline) {
3845
+ /* (on the --- line the marker-line gate names the same
3846
+ * refusal with libyaml's words — #900) */
3792
3847
  return e_fail(e, YEP_ERR_UNEXPECTED, node_at); /* ":\tkey:" */
3793
3848
  }
3794
3849
  if (e_simple_key_ok(e, s.start, s.end) != 0) {
3795
3850
  return -1;
3796
3851
  }
3797
- if (e->doc_inline && (!yep_view_is_empty(anchor) || !yep_view_is_empty(tag))) {
3798
- return e_fail(e, YEP_ERR_UNEXPECTED, e->pos); /* "--- &a k: v" */
3799
- }
3852
+ /* ("--- &a k: v" rides the e_open_map gate — the marker-line
3853
+ * law has one home now; the propped form used to fail here
3854
+ * with the generic default-arm diagnostic.) */
3800
3855
  uint32_t key_col = e_col(e, node_at);
3801
3856
  int rc = e_open_map(e, key_col, e->line, key_col + 1, pend_a, pend_t, pend_aid, 0);
3802
3857
  if (rc != 0) {
@@ -4886,8 +4941,8 @@ void yep_engine_destroy(yep_engine* e) {
4886
4941
  }
4887
4942
  if (e->step != NULL) {
4888
4943
  yep_stepstate* st = e->step;
4889
- free(st->buf);
4890
- free(st);
4944
+ yep_free(e->sys, st->buf);
4945
+ yep_free(e->sys, st);
4891
4946
  e->step = NULL;
4892
4947
  }
4893
4948
  yep_nametab_free(&e->anchors);
@@ -5349,10 +5404,17 @@ int yep_engine_step(yep_engine* e, const char* chunk, size_t len, int final, con
5349
5404
  return -1;
5350
5405
  }
5351
5406
  if (e->step == NULL) {
5352
- e->step = calloc(1, sizeof(yep_stepstate));
5407
+ /* the step state rides the ENGINE's allocator (#904 item 3):
5408
+ * the raw calloc bypassed e->sys, so the fail-injection harness
5409
+ * (test_alloc_fail's counting allocator) could never reach the
5410
+ * streaming route's allocations — and the memory law
5411
+ * (allocator.h: nothing outside memory/ calls malloc directly)
5412
+ * does not stop at the engine's own struct */
5413
+ e->step = yep_alloc(e->sys, sizeof(yep_stepstate));
5353
5414
  if (e->step == NULL) {
5354
5415
  return -1;
5355
5416
  }
5417
+ memset(e->step, 0, sizeof(yep_stepstate));
5356
5418
  }
5357
5419
  yep_stepstate* st = e->step;
5358
5420
  st->tok_start = 1; /* the buffer starts at a line start */
@@ -5366,10 +5428,17 @@ int yep_engine_step(yep_engine* e, const char* chunk, size_t len, int final, con
5366
5428
  while (cap < st->len + len) {
5367
5429
  cap *= 2;
5368
5430
  }
5369
- char* nb = realloc(st->buf, cap);
5431
+ /* the pending buffer grows through e->sys too: the
5432
+ * allocator has no realloc, so growth is alloc + copy +
5433
+ * free (the arena.c pattern) */
5434
+ char* nb = yep_alloc(e->sys, cap);
5370
5435
  if (nb == NULL) {
5371
5436
  return -1;
5372
5437
  }
5438
+ if (st->buf != NULL) {
5439
+ memcpy(nb, st->buf, st->len);
5440
+ yep_free(e->sys, st->buf);
5441
+ }
5373
5442
  st->buf = nb;
5374
5443
  st->cap = cap;
5375
5444
  }
@@ -5429,8 +5498,8 @@ int yep_engine_step(yep_engine* e, const char* chunk, size_t len, int final, con
5429
5498
  /* final: parse everything that remains and close the stream */
5430
5499
  e->line_base = st->line_base;
5431
5500
  int rc = engine_run_impl(e, st->buf, st->len, sink, !st->started, 1);
5432
- free(st->buf);
5433
- free(st);
5501
+ yep_free(e->sys, st->buf);
5502
+ yep_free(e->sys, st);
5434
5503
  e->step = NULL;
5435
5504
  e->line_base = 0;
5436
5505
  return rc;
@@ -5837,6 +5906,9 @@ static int engine_run_impl(yep_engine* e, const char* buf, size_t len, const yep
5837
5906
  (li.offset + li.indent + 1 >= e->len || e->p[li.offset + li.indent + 1] == ' ' ||
5838
5907
  e->p[li.offset + li.indent + 1] == '\t' || e->p[li.offset + li.indent + 1] == '\n' ||
5839
5908
  e->p[li.offset + li.indent + 1] == '\r');
5909
+ int map_closed_on_dash = 0; /* the root map closed because this line is a
5910
+ * seq entry — libyaml's parser-state name is
5911
+ * "did not find expected key" */
5840
5912
  while (e->depth > 0) {
5841
5913
  const yep_frame* fr = &e->frames[e->depth - 1];
5842
5914
  if (fr->col > c) {
@@ -5850,11 +5922,12 @@ static int engine_run_impl(yep_engine* e, const char* buf, size_t len, const yep
5850
5922
  int continues;
5851
5923
  if (fr->kind == YEP_FRAME_SEQ) {
5852
5924
  continues = dash_blank;
5853
- } else if (fr->inline_doc) {
5854
- continues = 0; /* "--- k: v" maps take deeper keys only */
5855
5925
  } else {
5856
5926
  continues = yep_scan_is_key_start(li.first) || li.first == ':' ||
5857
5927
  li.first == '?' || (li.first == '-' && !dash_blank);
5928
+ if (!continues && dash_blank && e->depth == 1) {
5929
+ map_closed_on_dash = 1;
5930
+ }
5858
5931
  }
5859
5932
  if (!continues) {
5860
5933
  int rc = e_close_to(e, e->depth - 1);
@@ -5868,10 +5941,19 @@ static int engine_run_impl(yep_engine* e, const char* buf, size_t len, const yep
5868
5941
  }
5869
5942
 
5870
5943
  if (e->depth == 0 && e->doc_content) {
5871
- /* a completed root scalar followed by more content: a second
5872
- * root node in the same document ("word1 # c\nword2") */
5873
- e_fail(e, YEP_ERR_UNEXPECTED, e->pos + li.indent);
5874
- goto fail;
5944
+ /* a completed root node followed by more content: a second
5945
+ * root in the same document ("word1 # c\nword2",
5946
+ * "---\n[1]\n[2]"). The oracle law is libyaml's parser
5947
+ * error — the stream wants a document marker and finds a
5948
+ * node ("did not find expected <document start>" at the
5949
+ * offending line's first byte; libyaml marks 2:1/3:1). A
5950
+ * seq entry where the just-closed root map wanted a key is
5951
+ * libyaml's "did not find expected key". Issue #900's
5952
+ * inline-map rows never reach this arm: they die at the
5953
+ * marker-line gate in e_open_map/e_open_seq. */
5954
+ return e_fail_msg(e, YEP_ERR_DOC_BOUNDARY, e->pos + li.indent,
5955
+ map_closed_on_dash ? "did not find expected key"
5956
+ : "did not find expected <document start>");
5875
5957
  }
5876
5958
  if (e->depth > 0 && e->frames[e->depth - 1].col < c) {
5877
5959
  /* deeper than any open container at main dispatch: nested
@@ -5953,8 +6035,7 @@ static int engine_run_impl(yep_engine* e, const char* buf, size_t len, const yep
5953
6035
  * the chain below unchanged. */
5954
6036
  if (e->depth > 0 && e->pend_anchor.p == NULL && e->pend_tag.p == NULL &&
5955
6037
  e->pend_anchor_id == 0 && !e->q_key_pending && !e->q_value_pending &&
5956
- ((e->frames[e->depth - 1].kind == YEP_FRAME_MAP && e->frames[e->depth - 1].col == c &&
5957
- !e->frames[e->depth - 1].inline_doc) ||
6038
+ ((e->frames[e->depth - 1].kind == YEP_FRAME_MAP && e->frames[e->depth - 1].col == c) ||
5958
6039
  (e->frames[e->depth - 1].kind == YEP_FRAME_SEQ && e->frames[e->depth - 1].col == c &&
5959
6040
  dash_blank))) {
5960
6041
  int frc = e_classified(e, e->frames[e->depth - 1].col);
@@ -0,0 +1,326 @@
1
+ /* stream_iter.c — tolerant per-document stream iteration (#921).
2
+ *
3
+ * The load_stream contract parses the whole stream and returns nothing
4
+ * when any document fails; consumers that must keep the valid
5
+ * documents of a stream cannot use the engine for recovery. This
6
+ * iterator frames documents ENGINE-SIDE (the abort-at-DOCUMENT_END
7
+ * run iterparse uses — scanner-accurate: a quoted scalar spanning a
8
+ * line that looks like a marker never splits) and reports failures
9
+ * per document: on a parse-class error the iterator records that
10
+ * document's verdict (the engine's line/col, which ARE the failed
11
+ * document's own — each run starts at the frame) and resyncs to the
12
+ * next line-start marker, so the host keeps the valid documents.
13
+ *
14
+ * The resync after a FAILURE is a raw marker scan: the failed
15
+ * document's tail is ill-formed by definition, so the scanner-grade
16
+ * framing above no longer has a well-defined subject; the raw scan is
17
+ * the libyaml-familiar best effort and only ever runs on the recovery
18
+ * path (valid documents never hit it).
19
+ *
20
+ * Cost note: each document parses twice (the boundary run + the slice
21
+ * parse). The tolerance contract is the point; a single-pass upgrade
22
+ * rides on the same sink seam when a workload demands it. */
23
+
24
+ #include <stdlib.h>
25
+ #include <string.h>
26
+
27
+ #include "../../include/yeptris/dom.h"
28
+ #include "../../include/yeptris/parse.h"
29
+ #include "../common/error.h"
30
+ #include "../common/simd_text.h"
31
+ #include "../encoding/encoding.h"
32
+ #include "../memory/allocator.h"
33
+ #include "../resolve/resolver.h"
34
+ #include "doc.h"
35
+ #include "engine.h"
36
+
37
+ /* The boundary finder: a sink that aborts the run at the first
38
+ * DOCUMENT_END (iterparse's law — the marker line stays unconsumed
39
+ * and doubles as the next document's start). Events are discarded;
40
+ * the run's pos IS the document's end offset. */
41
+ typedef struct {
42
+ int doc_ends;
43
+ } frame_sink;
44
+
45
+ static int frame_on_event(void* ctx, const yep_event* ev) {
46
+ frame_sink* s = (frame_sink*)ctx;
47
+ if (ev->type == YEP_EV_DOCUMENT_END) {
48
+ s->doc_ends++;
49
+ return 1; /* abort: the document is complete */
50
+ }
51
+ return 0;
52
+ }
53
+
54
+ struct yeptris_stream_iter {
55
+ const char* buf;
56
+ size_t len;
57
+ size_t cursor;
58
+ int done;
59
+ int gated; /* the encoding front-end ran once (iterparse's
60
+ * law: after it, cursor and engine pos live in
61
+ * the same buffer space) */
62
+ unsigned char* owned; /* UTF-16/32 transcode output */
63
+ char* norm; /* NEL/LS/PS-normalized copy */
64
+ const YeptrisParseOptions* opts; /* borrowed */
65
+ size_t index; /* 0-based stream document index (errors consume
66
+ * one too — the host's yield order is the stream's) */
67
+ YeptrisStatus last_status; /* the LAST next()'s per-document verdict */
68
+ uint32_t last_line;
69
+ uint32_t last_col;
70
+ };
71
+
72
+ yeptris_stream_iter* yeptris_stream_iter_new(const char* buf, size_t len,
73
+ const YeptrisParseOptions* opts) {
74
+ if (buf == NULL && len != 0) {
75
+ return NULL;
76
+ }
77
+ struct yeptris_stream_iter* it = calloc(1, sizeof(*it));
78
+ if (it == NULL) {
79
+ return NULL;
80
+ }
81
+ it->buf = buf;
82
+ it->len = len;
83
+ it->opts = opts;
84
+ return it;
85
+ }
86
+
87
+ /* The resync: the next line-start marker (--- / ...) followed by
88
+ * space, break, or EOF — the c-forbidden lookahead. Raw bytes: the
89
+ * recovery path's best effort (see the file comment). The scan starts
90
+ * AFTER the failed frame's first line: the frame opened at `from`'s
91
+ * line, and the engine's failure pos can sit at EOF (the run scans to
92
+ * the end before reporting), so anchoring on `from` itself is the only
93
+ * stable reference. */
94
+ static size_t next_marker(const char* p, size_t len, size_t from) {
95
+ size_t i = from;
96
+ while (i < len && p[i] != '\n' && p[i] != '\r') {
97
+ i++;
98
+ }
99
+ if (i < len) {
100
+ i++;
101
+ }
102
+ int line_start = 1;
103
+ while (i < len) {
104
+ char ch = p[i];
105
+ if (ch == '\n' || ch == '\r') {
106
+ line_start = 1;
107
+ i++;
108
+ continue;
109
+ }
110
+ if (line_start && ch == '-' && i + 2 < len && p[i + 1] == '-' && p[i + 2] == '-' &&
111
+ (i + 3 == len || p[i + 3] == ' ' || p[i + 3] == '\n' || p[i + 3] == '\r' ||
112
+ p[i + 3] == '\t')) {
113
+ return i;
114
+ }
115
+ if (line_start && ch == '.' && i + 2 < len && p[i + 1] == '.' && p[i + 2] == '.' &&
116
+ (i + 3 == len || p[i + 3] == ' ' || p[i + 3] == '\n' || p[i + 3] == '\r' ||
117
+ p[i + 3] == '\t')) {
118
+ return i;
119
+ }
120
+ line_start = 0;
121
+ i++;
122
+ }
123
+ return len; /* no marker: the tail is unrecoverable */
124
+ }
125
+
126
+ static void it_fail(struct yeptris_stream_iter* it, YeptrisStatus st, uint32_t line, uint32_t col) {
127
+ it->last_status = st;
128
+ it->last_line = line;
129
+ it->last_col = col;
130
+ }
131
+
132
+ static int engine_opts(yep_engine* eng, const YeptrisParseOptions* opts) {
133
+ /* the typing SSOT: one switch (parse.c owns it) */
134
+ YeptrisSchema schema = opts != NULL ? opts->schema : YEPTRIS_SCHEMA_12_CORE;
135
+ yep_engine_set_resolver(eng, resolver_for_schema(schema));
136
+ if (opts != NULL && opts->schema == YEPTRIS_SCHEMA_11_COMPAT) {
137
+ /* the compat grammar is the psych/libyaml surface (parse_impl's
138
+ * law) */
139
+ yep_engine_set_compat_grammar(eng, 1);
140
+ }
141
+ if (opts != NULL && opts->max_depth > 0) {
142
+ yep_engine_set_max_depth(eng, opts->max_depth);
143
+ }
144
+ return 0;
145
+ }
146
+
147
+ YeptrisStatus yeptris_stream_iter_next(struct yeptris_stream_iter* it, YeptrisDocument* out,
148
+ size_t* index) {
149
+ if (it == NULL || out == NULL) {
150
+ return YEPTRIS_ERROR_ARG;
151
+ }
152
+ *out = NULL;
153
+ if (index != NULL) {
154
+ *index = it->index;
155
+ }
156
+ for (;;) {
157
+ /* the encoding front-end, ONCE (iterparse's gate law: BOM
158
+ * strip, UTF-16/32 transcode, NEL/LS/PS normalization — cursor
159
+ * and engine pos must live in one buffer space or the resume
160
+ * arithmetic corrupts slices) */
161
+ if (!it->gated) {
162
+ it->gated = 1;
163
+ const yep_allocator* sys = yep_system_allocator();
164
+ yep_encoding enc = YEP_ENC_UNKNOWN;
165
+ size_t bom = yep_bom_sniff((const unsigned char*)it->buf, it->len, &enc);
166
+ if (enc == YEP_ENC_UTF16LE || enc == YEP_ENC_UTF16BE || enc == YEP_ENC_UTF32LE ||
167
+ enc == YEP_ENC_UTF32BE) {
168
+ size_t tlen = 0;
169
+ size_t terr = 0;
170
+ unsigned char* owned = NULL;
171
+ int rc = yep_transcode_to_utf8(sys, enc, (const unsigned char*)it->buf + bom,
172
+ it->len - bom, &owned, &tlen, &terr);
173
+ if (rc != 0) {
174
+ it->done = 1;
175
+ it_fail(it, YEPTRIS_ERROR_ENCODING, 1, 1);
176
+ return YEPTRIS_ERROR_ENCODING;
177
+ }
178
+ it->owned = owned;
179
+ it->buf = (const char*)owned;
180
+ it->len = tlen;
181
+ it->cursor = 0;
182
+ } else {
183
+ it->cursor = bom;
184
+ }
185
+ size_t nlen = 0;
186
+ int kept_raw = 0;
187
+ char* norm = yep_engine_normalize_breaks(sys, it->buf + it->cursor,
188
+ it->len - it->cursor, &nlen, &kept_raw);
189
+ if (norm != NULL) {
190
+ it->norm = norm;
191
+ it->buf = norm;
192
+ it->len = nlen;
193
+ it->cursor = 0;
194
+ }
195
+ }
196
+ if (it->done) {
197
+ it_fail(it, YEPTRIS_OK, 0, 0);
198
+ return YEPTRIS_OK; /* *out stays NULL: the stream is over */
199
+ }
200
+
201
+ /* one boundary run: abort at DOCUMENT_END, learn the extent */
202
+ frame_sink fs = {0};
203
+ yep_engine* eng = yep_engine_create(yep_system_allocator());
204
+ if (eng == NULL) {
205
+ it->done = 1;
206
+ it_fail(it, YEPTRIS_ERROR_MEMORY, 0, 0);
207
+ return YEPTRIS_ERROR_MEMORY;
208
+ }
209
+ engine_opts(eng, it->opts);
210
+ yep_sink sink = {.on_event = frame_on_event,
211
+ .ctx = &fs,
212
+ .on_flow_build = NULL,
213
+ .on_flow_commit = NULL,
214
+ .on_flow_rollback = NULL,
215
+ .on_block_pair = NULL,
216
+ .on_block_open = NULL,
217
+ .on_block_item = NULL};
218
+ yep_text_stats pst;
219
+ yep_text_active()->scan_stats(it->buf + it->cursor, it->len - it->cursor, &pst);
220
+ yep_engine_prepare(eng, &pst);
221
+ int rc = yep_engine_run(eng, it->buf + it->cursor, it->len - it->cursor, &sink);
222
+ size_t pos = yep_engine_pos(eng);
223
+ yep_err_code err_code = YEP_ERR_NONE;
224
+ uint32_t err_line = 0, err_col = 0;
225
+ {
226
+ const yep_error* err = yep_engine_error(eng);
227
+ if (err != NULL) {
228
+ err_code = err->code;
229
+ err_line = err->line;
230
+ err_col = err->col;
231
+ }
232
+ }
233
+ yep_engine_destroy(eng);
234
+
235
+ if (rc == -1 && (err_code == YEP_ERR_MEMORY || err_code == YEP_ERR_ENCODING)) {
236
+ /* the stream-fatal classes: the arena state is unknown
237
+ * (memory) or the front-end's space is broken (encoding —
238
+ * it gated once, so this cannot be a per-document miss) */
239
+ it->done = 1;
240
+ it_fail(it, err_code == YEP_ERR_MEMORY ? YEPTRIS_ERROR_MEMORY : YEPTRIS_ERROR_ENCODING,
241
+ err_line, err_col);
242
+ return it->last_status;
243
+ }
244
+ if (rc == -1 || (rc != 0 && fs.doc_ends == 0)) {
245
+ /* THIS document failed (parse-class or the run died before
246
+ * any document completed): report the per-document verdict
247
+ * — the run started at the frame, so the engine's line/col
248
+ * ARE the failed document's own — then resync to the next
249
+ * marker and keep going */
250
+ it_fail(it, err_code == YEP_ERR_DEPTH ? YEPTRIS_ERROR_DEPTH : YEPTRIS_ERROR_PARSE,
251
+ err_line, err_col);
252
+ size_t at = next_marker(it->buf, it->len, it->cursor);
253
+ if (at >= it->len) {
254
+ it->done = 1; /* the tail is unrecoverable */
255
+ } else {
256
+ it->cursor = at;
257
+ }
258
+ it->index++;
259
+ if (index != NULL) {
260
+ *index = it->index - 1;
261
+ }
262
+ return it->last_status; /* *out stays NULL: the failed slot */
263
+ }
264
+ if (fs.doc_ends == 0) {
265
+ /* natural end or a blank tail: the stream is over */
266
+ it->done = 1;
267
+ it_fail(it, YEPTRIS_OK, 0, 0);
268
+ return YEPTRIS_OK;
269
+ }
270
+
271
+ /* the frame is [cursor, cursor+pos): parse it as ONE document
272
+ * (the slice's own marker or bare content rides parse_ex's
273
+ * contract; the default tape route is what load uses) */
274
+ YeptrisStatus st = YEPTRIS_OK;
275
+ YeptrisDocument doc = yeptris_parse_ex(it->buf + it->cursor, pos, it->opts, &st);
276
+ if (doc == NULL || st != YEPTRIS_OK) {
277
+ /* the boundary run accepted what the slice parse refused:
278
+ * report as a failed document and resync the same way */
279
+ it_fail(it, st == YEPTRIS_OK ? YEPTRIS_ERROR_PARSE : st, 0, 0);
280
+ yeptris_document_free(doc);
281
+ size_t at = next_marker(it->buf, it->len, it->cursor);
282
+ if (at >= it->len) {
283
+ it->done = 1;
284
+ } else {
285
+ it->cursor = at;
286
+ }
287
+ it->index++;
288
+ if (index != NULL) {
289
+ *index = it->index - 1;
290
+ }
291
+ return it->last_status;
292
+ }
293
+ it->cursor += pos;
294
+ it->index++;
295
+ if (index != NULL) {
296
+ *index = it->index - 1;
297
+ }
298
+ it_fail(it, YEPTRIS_OK, 0, 0);
299
+ *out = doc;
300
+ return YEPTRIS_OK;
301
+ }
302
+ }
303
+
304
+ YeptrisStatus yeptris_stream_iter_status(const struct yeptris_stream_iter* it, uint32_t* line,
305
+ uint32_t* col) {
306
+ if (it == NULL) {
307
+ return YEPTRIS_ERROR_ARG;
308
+ }
309
+ if (line != NULL) {
310
+ *line = it->last_line;
311
+ }
312
+ if (col != NULL) {
313
+ *col = it->last_col;
314
+ }
315
+ return it->last_status;
316
+ }
317
+
318
+ void yeptris_stream_iter_free(struct yeptris_stream_iter* it) {
319
+ if (it == NULL) {
320
+ return;
321
+ }
322
+ const yep_allocator* sys = yep_system_allocator();
323
+ yep_free(sys, it->owned);
324
+ yep_free(sys, it->norm);
325
+ free(it);
326
+ }