yeptris 0.6.51.0-arm-linux-eabihf → 0.6.53.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.
@@ -49,7 +49,7 @@ typedef enum {
49
49
 
50
50
  /* One open collection frame (the engine's indentation stack). */
51
51
  typedef struct {
52
- uint16_t col;
52
+ uint32_t col;
53
53
  uint8_t kind;
54
54
  uint8_t inline_doc; /* opened on the --- line: deeper keys only */
55
55
  } yep_frame;
@@ -163,7 +163,7 @@ struct yep_engine {
163
163
  int last_root_flow; /* a root-level flow node just completed */
164
164
  int tag_undef; /* an unresolved !handle! was seen (QLJ7) */
165
165
  int doc_inline; /* content node opened on the --- line */
166
- uint16_t flow_floor; /* parent column of the current block-level flow */
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 —
169
169
  * the flow indent floor (9C9N/VJP3) is a
@@ -176,8 +176,8 @@ struct yep_engine {
176
176
 
177
177
  /* ------------------------------------------------------------ forwards */
178
178
 
179
- static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col);
180
- static int e_parse_value(yep_engine* e, yep_ctx ctx, uint16_t floor_col);
179
+ static int e_node(yep_engine* e, yep_ctx ctx, uint32_t floor_col);
180
+ static int e_parse_value(yep_engine* e, yep_ctx ctx, uint32_t floor_col);
181
181
 
182
182
  /* ---------------------------------------------------------------- util */
183
183
 
@@ -379,7 +379,8 @@ static int e_comment_at(const yep_engine* e) {
379
379
  }
380
380
 
381
381
  static int e_at_eol(yep_engine* e) {
382
- return e->pos >= e->len || e->p[e->pos] == '\n' || e->p[e->pos] == '\r' || e_comment_at(e);
382
+ return e->pos >= e->len || e->p[e->pos] == '\n' || e->p[e->pos] == '\r' || e_comment_at(e) ||
383
+ yep_scan_mb_break_len(e->p, e->len, e->pos) > 0;
383
384
  }
384
385
 
385
386
  static int e_skip_to_eol(yep_engine* e) {
@@ -461,7 +462,7 @@ static uint32_t anchor_define(yep_engine* e, yep_view name) {
461
462
  /* Opens a collection frame unless the top frame already continues at
462
463
  * exactly this column (sibling entries reuse it). anchor/tag ride the
463
464
  * START event. Returns 0 on success. */
464
- static int e_open_seq(yep_engine* e, uint16_t col, uint32_t line, uint32_t coln, yep_view anchor,
465
+ static int e_open_seq(yep_engine* e, uint32_t col, uint32_t line, uint32_t coln, yep_view anchor,
465
466
  yep_view tag, uint32_t anchor_id) {
466
467
  if (e->depth > 0 && e->frames[e->depth - 1].kind == YEP_FRAME_SEQ &&
467
468
  e->frames[e->depth - 1].col == col) {
@@ -499,7 +500,7 @@ static int e_open_seq(yep_engine* e, uint16_t col, uint32_t line, uint32_t coln,
499
500
 
500
501
  /* silent: the sink's on_block_open built the node — push the frame
501
502
  * without emitting (TODO.restructure/57). */
502
- static int e_open_map(yep_engine* e, uint16_t col, uint32_t line, uint32_t coln, yep_view anchor,
503
+ static int e_open_map(yep_engine* e, uint32_t col, uint32_t line, uint32_t coln, yep_view anchor,
503
504
  yep_view tag, uint32_t anchor_id, int silent) {
504
505
  if (e->depth > 0 && e->frames[e->depth - 1].kind == YEP_FRAME_MAP &&
505
506
  e->frames[e->depth - 1].col == col) {
@@ -554,7 +555,7 @@ static int e_flush_q_value(yep_engine* e) {
554
555
  * value arrived inline) the innermost '?' pending resolves — without
555
556
  * this pop the stack keeps a stale entry and the outer pair's null
556
557
  * flushes inside the wrong map (the nightly's run 38387614146). */
557
- static int e_pair_value_done(yep_engine* e, yep_ctx ctx, uint16_t key_col) {
558
+ static int e_pair_value_done(yep_engine* e, yep_ctx ctx, uint32_t key_col) {
558
559
  /* the pair's own level, BEFORE its value parse: the value may open
559
560
  * deeper frames ("? a:\n ? b<EOF>") and a post-parse depth read
560
561
  * then matches the DEEPER pair's pending slot, popping the wrong
@@ -603,7 +604,7 @@ static int e_close_to(yep_engine* e, int to_depth) {
603
604
  /* ------------------------------------------------------------- scalars */
604
605
 
605
606
  /* Quoted scalar at the opening quote; spans lines when needed. */
606
- static int e_quoted_floor(yep_engine* e, yep_event* ev, uint16_t min_indent, int enforce) {
607
+ static int e_quoted_floor(yep_engine* e, yep_event* ev, uint32_t min_indent, int enforce) {
607
608
  unsigned char q = (unsigned char)e->p[e->pos];
608
609
  size_t content_start = e->pos + 1;
609
610
  int has_esc = 0;
@@ -884,12 +885,12 @@ static int e_block_scalar(yep_engine* e, yep_event* ev, int parent_col) {
884
885
  if (content_indent <= parent_col) {
885
886
  break; /* no block content */
886
887
  }
887
- } else if (li.indent <= parent_col) {
888
+ } else if ((int)li.indent <= parent_col) {
888
889
  break; /* dedent ends the block */
889
- } else if (li.indent < content_indent && li.indent <= parent_col) {
890
+ } else if ((int)li.indent < content_indent && (int)li.indent <= parent_col) {
890
891
  break;
891
892
  }
892
- if (li.indent < content_indent) {
893
+ if ((int)li.indent < content_indent) {
893
894
  break;
894
895
  }
895
896
  }
@@ -901,10 +902,10 @@ static int e_block_scalar(yep_engine* e, yep_event* ev, int parent_col) {
901
902
  if (content_indent <= parent_col) {
902
903
  break;
903
904
  }
904
- } else if (li.indent < content_indent) {
905
+ } else if ((int)li.indent < content_indent) {
905
906
  break;
906
907
  }
907
- if (li.indent >= content_indent) {
908
+ if ((int)li.indent >= content_indent) {
908
909
  uint32_t from2 = li.offset + (uint32_t)content_indent;
909
910
  if (e->fold_n >= YEP_MAX_FOLD_LINES) {
910
911
  return e_fail(e, YEP_ERR_MEMORY, e->pos);
@@ -944,7 +945,7 @@ static int e_block_scalar(yep_engine* e, yep_event* ev, int parent_col) {
944
945
  }
945
946
  /* a blank line whose spaces extend past the content indent is
946
947
  * CONTENT (kept verbatim); otherwise it is a pure break */
947
- if (content_indent >= 0 && li.indent > content_indent) {
948
+ if (content_indent >= 0 && (int)li.indent > content_indent) {
948
949
  if (e->fold_n >= YEP_MAX_FOLD_LINES) {
949
950
  return e_fail(e, YEP_ERR_MEMORY, e->pos);
950
951
  }
@@ -995,7 +996,7 @@ static int e_block_scalar(yep_engine* e, yep_event* ev, int parent_col) {
995
996
  /* leading blank lines belong to the block content */
996
997
  e->fold[e->fold_n].breaks_before = saw_content ? pending_breaks + 1 : pending_breaks;
997
998
  e->fold[e->fold_n].more_indented =
998
- li.indent > content_indent ||
999
+ (int)li.indent > content_indent ||
999
1000
  (from < li.end && (e->p[from] == ' ' || e->p[from] == '\t'));
1000
1001
  e->fold_n++;
1001
1002
  saw_content = 1;
@@ -1892,7 +1893,7 @@ static int e_flow_key_after(const char* p, size_t len, size_t close) {
1892
1893
  * rejects it, and the fast path must agree (the nightly's run
1893
1894
  * 38280372447: whole vs chunked split across the fast/slow paths and
1894
1895
  * produced two different verdicts for the same bytes). */
1895
- static int e_flow_floor_ok(const char* p, size_t open, size_t close, uint16_t floor) {
1896
+ static int e_flow_floor_ok(const char* p, size_t open, size_t close, uint32_t floor) {
1896
1897
  size_t j = open;
1897
1898
  while (j < close) {
1898
1899
  /* the break rule is the scan's: \n, or a \r the next byte does
@@ -3035,7 +3036,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
3035
3036
 
3036
3037
  /* Plain (optionally anchored) value: emit, fold continuations, land at
3037
3038
  * the line end. root_ctx is 0 — every arm has pushed a frame. */
3038
- static int e_shape_plain_value(yep_engine* e, const yep_line_shape* sh, uint16_t floor,
3039
+ static int e_shape_plain_value(yep_engine* e, const yep_line_shape* sh, uint32_t floor,
3039
3040
  yep_view anchor, uint32_t anchor_id) {
3040
3041
  yep_event ev;
3041
3042
  e_event_init(&ev, YEP_EV_SCALAR);
@@ -3077,7 +3078,7 @@ static int e_shape_plain_value(yep_engine* e, const yep_line_shape* sh, uint16_t
3077
3078
 
3078
3079
  /* Flow value: the JSON fast path first; anything else restores the
3079
3080
  * cursor and runs the value chain (which re-derives the flow arms). */
3080
- static int e_shape_flow_value(yep_engine* e, const yep_line_shape* sh, uint16_t floor,
3081
+ static int e_shape_flow_value(yep_engine* e, const yep_line_shape* sh, uint32_t floor,
3081
3082
  yep_ctx next) {
3082
3083
  yep_view none = {NULL, 0};
3083
3084
  e->pos = sh->val_start;
@@ -3118,7 +3119,7 @@ static int e_shape_alias_value(yep_engine* e, const yep_line_shape* sh, int unde
3118
3119
  if (e_simple_key_ok(e, sh->val_start, e->pos) != 0) {
3119
3120
  return -1;
3120
3121
  }
3121
- uint16_t key_col = (uint16_t)e_col(e, sh->val_start);
3122
+ uint32_t key_col = e_col(e, sh->val_start);
3122
3123
  int rc = e_open_map(e, key_col, e->line, key_col + 1, none, none, 0, 0);
3123
3124
  if (rc != 0) {
3124
3125
  return rc;
@@ -3140,7 +3141,7 @@ static int e_shape_alias_value(yep_engine* e, const yep_line_shape* sh, int unde
3140
3141
  * emit it: the pair path takes the whole line without events, and
3141
3142
  * building kv up front discarded ~48 stores per classified line
3142
3143
  * (TODO.restructure/63 phase 1). */
3143
- static void e_key_event(const yep_engine* e, const yep_line_shape* sh, uint16_t key_col,
3144
+ static void e_key_event(const yep_engine* e, const yep_line_shape* sh, uint32_t key_col,
3144
3145
  uint32_t line, yep_event* kv) {
3145
3146
  e_event_init(kv, YEP_EV_SCALAR);
3146
3147
  kv->style = YEP_STYLE_PLAIN;
@@ -3155,7 +3156,7 @@ static void e_key_event(const yep_engine* e, const yep_line_shape* sh, uint16_t
3155
3156
  e_pos_mark(e, sh->key_end, &kv->end_line, &kv->end_col);
3156
3157
  }
3157
3158
 
3158
- static int e_classified(yep_engine* e, uint16_t floor_col) {
3159
+ static int e_classified(yep_engine* e, uint32_t floor_col) {
3159
3160
  yep_line_info li = e_line_info_here(e);
3160
3161
  if (e->pos != e->line_start + li.indent) {
3161
3162
  return 0; /* mid-line entry: the chain keeps its dispatch */
@@ -3176,7 +3177,7 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
3176
3177
  default:
3177
3178
  return 0;
3178
3179
  }
3179
- uint16_t col = (uint16_t)e_col(e, e->pos);
3180
+ uint32_t col = e_col(e, e->pos);
3180
3181
  int rc = e_open_seq(e, col, e->line, col + 1, none, none, 0);
3181
3182
  if (rc != 0) {
3182
3183
  return rc;
@@ -3202,7 +3203,7 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
3202
3203
  if (e_simple_key_ok(e, sh->key_start, sh->key_end) != 0) {
3203
3204
  return -1;
3204
3205
  }
3205
- uint16_t key_col = (uint16_t)e_col(e, e->pos);
3206
+ uint32_t key_col = e_col(e, e->pos);
3206
3207
  /* `key:` with the value on following lines, opening a FRESH map:
3207
3208
  * the sink may build map+key in one call (TODO.restructure/57) —
3208
3209
  * the engine then opens its frame silently. Sibling keys (the map
@@ -3240,7 +3241,7 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
3240
3241
  /* the pair facts are captured BEFORE the fold: e_plain_multiline
3241
3242
  * advances line/pos across continuations */
3242
3243
  uint32_t pair_line = e->line;
3243
- uint16_t pair_val_col = (uint16_t)e_col(e, sh->val_start);
3244
+ uint32_t pair_val_col = e_col(e, sh->val_start);
3244
3245
  yep_block_value v;
3245
3246
  memset(&v, 0, sizeof(v));
3246
3247
  v.cls = (uint8_t)sh->val;
@@ -3374,7 +3375,7 @@ static int e_classified(yep_engine* e, uint16_t floor_col) {
3374
3375
  * pend props (parsed at the parent's value position on an earlier line)
3375
3376
  * belong to the VALUE node as a whole — a collection START event when
3376
3377
  * the value is a collection. */
3377
- static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3378
+ static int e_node(yep_engine* e, yep_ctx ctx, uint32_t floor_col) {
3378
3379
  size_t node_at = e->pos;
3379
3380
  yep_view pend_a = e->pend_anchor, pend_t = e->pend_tag;
3380
3381
  uint32_t pend_aid = e->pend_anchor_id;
@@ -3481,7 +3482,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3481
3482
  if (!yep_view_is_empty(anchor) || !yep_view_is_empty(tag)) {
3482
3483
  return e_fail(e, YEP_ERR_UNEXPECTED, e->pos); /* "&a - x" */
3483
3484
  }
3484
- uint16_t col = (uint16_t)e_col(e, e->pos);
3485
+ uint32_t col = e_col(e, e->pos);
3485
3486
  int rc = e_open_seq(e, col, e->line, col + 1, node_a, node_t, node_aid);
3486
3487
  if (rc != 0) {
3487
3488
  return rc;
@@ -3514,7 +3515,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3514
3515
  if (e->pos > 0 && e->p[e->pos - 1] == '\t') {
3515
3516
  return e_fail(e, YEP_ERR_UNEXPECTED, e->pos); /* "?\t-" (Y79Y) */
3516
3517
  }
3517
- uint16_t col = (uint16_t)e_col(e, e->pos);
3518
+ uint32_t col = e_col(e, e->pos);
3518
3519
  int rc = e_open_map(e, col, e->line, col + 1, pend_a, pend_t, pend_aid, 0);
3519
3520
  if (rc != 0) {
3520
3521
  return rc;
@@ -3638,7 +3639,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3638
3639
  if (e_simple_key_ok(e, node_at, e->pos) != 0) {
3639
3640
  return -1;
3640
3641
  }
3641
- uint16_t key_col = (uint16_t)e_col(e, node_at);
3642
+ uint32_t key_col = e_col(e, node_at);
3642
3643
  int rc = e_open_map(e, key_col, e->line, key_col + 1, pend_a, pend_t, pend_aid, 0);
3643
3644
  if (rc != 0) {
3644
3645
  return rc;
@@ -3682,7 +3683,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3682
3683
  if (e_simple_key_ok(e, node_at, e->pos) != 0) {
3683
3684
  return -1;
3684
3685
  }
3685
- uint16_t key_col = (uint16_t)e_col(e, node_at);
3686
+ uint32_t key_col = e_col(e, node_at);
3686
3687
  int rc = e_open_map(e, key_col, e->line, key_col + 1, pend_a, pend_t, pend_aid, 0);
3687
3688
  if (rc != 0) {
3688
3689
  return rc;
@@ -3721,7 +3722,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3721
3722
  if (e_simple_key_ok(e, node_at, e->pos) != 0) {
3722
3723
  return -1;
3723
3724
  }
3724
- uint16_t key_col = (uint16_t)e_col(e, node_at);
3725
+ uint32_t key_col = e_col(e, node_at);
3725
3726
  int rc = e_open_map(e, key_col, e->line, key_col + 1, pend_a, pend_t, pend_aid, 0);
3726
3727
  if (rc != 0) {
3727
3728
  return rc;
@@ -3776,7 +3777,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3776
3777
  if (e->doc_inline && (!yep_view_is_empty(anchor) || !yep_view_is_empty(tag))) {
3777
3778
  return e_fail(e, YEP_ERR_UNEXPECTED, e->pos); /* "--- &a k: v" */
3778
3779
  }
3779
- uint16_t key_col = (uint16_t)e_col(e, node_at);
3780
+ uint32_t key_col = e_col(e, node_at);
3780
3781
  int rc = e_open_map(e, key_col, e->line, key_col + 1, pend_a, pend_t, pend_aid, 0);
3781
3782
  if (rc != 0) {
3782
3783
  return rc;
@@ -3836,7 +3837,7 @@ static int e_node(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3836
3837
  }
3837
3838
 
3838
3839
  /* Value after "key:" / "-" / "?": on this line, or on following lines. */
3839
- static int e_parse_value(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3840
+ static int e_parse_value(yep_engine* e, yep_ctx ctx, uint32_t floor_col) {
3840
3841
  e_skip_inline_space(e);
3841
3842
  if (!e_at_eol(e)) {
3842
3843
  if (ctx == YEP_CTX_AFTER_COLON && e->p[e->pos] == '-' &&
@@ -3848,7 +3849,7 @@ static int e_parse_value(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3848
3849
  (e->pos + 1 >= e->len || e->p[e->pos + 1] == ' ' || e->p[e->pos + 1] == '\t' ||
3849
3850
  e->p[e->pos + 1] == '\n' || e->p[e->pos + 1] == '\r')) {
3850
3851
  /* "- :" — a compact pair with an empty key */
3851
- uint16_t col = (uint16_t)e_col(e, e->pos);
3852
+ uint32_t col = e_col(e, e->pos);
3852
3853
  int rc = e_open_map(e, col, e->line, col + 1, e->pend_anchor, e->pend_tag,
3853
3854
  e->pend_anchor_id, 0);
3854
3855
  if (rc != 0) {
@@ -3890,6 +3891,16 @@ static int e_parse_value(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3890
3891
  if (e->pos >= e->len) {
3891
3892
  goto empty_value;
3892
3893
  }
3894
+ /* a raw NEL/LS/PS here is this line's break: consume it exactly
3895
+ * as the BLANK branch below consumes an LF-ended empty line —
3896
+ * yep_scan_line's line model does not treat a break byte at the
3897
+ * loop head as a line end, so without this the props-only line
3898
+ * re-entered e_node on the break byte (rows 1-2) and the marker
3899
+ * became the scalar's value (row 5's consume-first form) */
3900
+ if (yep_scan_mb_break_len(e->p, e->len, e->pos) > 0) {
3901
+ e_line_done(e, e->pos);
3902
+ continue;
3903
+ }
3893
3904
  yep_line_info li;
3894
3905
  if (e->pos == e->line_start) {
3895
3906
  /* seed the memo: the fast arms re-read this line's facts —
@@ -4190,15 +4201,10 @@ yep_engine* yep_engine_create(const yep_allocator* sys) {
4190
4201
  * length is not strlen()'s: the INPUT may carry NUL bytes of its own,
4191
4202
  * and a strlen() of the copy truncates the run at the first one (the
4192
4203
  * nightly's run 38524928004: "\0\n---\r<U+2028>" whole-parsed as an
4193
- * empty stream while the chunked cut skipped the copy). */
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, NULL);
4196
- if (out == NULL) {
4197
- return p;
4198
- }
4199
- e->norm_buf = out;
4200
- return out;
4201
- }
4204
+ * empty stream while the chunked cut skipped the copy). The context
4205
+ * walk behind the copy lives below (issue #638); since #839 the
4206
+ * chunked route threads it across --- cuts so every document
4207
+ * normalizes to the same bytes the one-buffer walk produces. */
4202
4208
 
4203
4209
  /* The NEL/LS/PS -> '\n' copy (the one-shot path rides this BEFORE the
4204
4210
  * engine so the normalized bytes own the document's `transcoded` slot;
@@ -4247,6 +4253,13 @@ typedef struct yep_norm_ctx {
4247
4253
  int bs_content;
4248
4254
  uint32_t bs_indent; /* the header line's indent; (uint32_t)-1 = the
4249
4255
  * doc-inline root rule (markers only) */
4256
+ uint8_t started; /* at least one byte consumed: the walk's i == 0
4257
+ * clauses are stream-start, not buffer-start —
4258
+ * the chunked route resumes mid-stream at a ---
4259
+ * cut, and the carried context must say so */
4260
+ unsigned char prev; /* the last consumed byte (valid when started):
4261
+ * the '#' and line-start tests peek p[i - 1],
4262
+ * which at a resumed buffer is carried state */
4250
4263
  } yep_norm_ctx;
4251
4264
 
4252
4265
  typedef struct yep_norm_tok {
@@ -4404,8 +4417,12 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
4404
4417
  norm_break_arms(c);
4405
4418
  return t;
4406
4419
  }
4407
- if (ch == '#' && (i == 0 || c->at_break || p[i - 1] == '\n' || p[i - 1] == '\r' ||
4408
- p[i - 1] == ' ' || p[i - 1] == '\t')) {
4420
+ /* the i == 0 clauses below are STREAM start: `started`/`prev` carry
4421
+ * them across the chunked route's per-document resumes (issue #839)
4422
+ * — a fresh context at a --- cut would otherwise read the resumed
4423
+ * buffer's first byte as a line start the whole walk never saw */
4424
+ if (ch == '#' && (!c->started || c->at_break || c->prev == '\n' || c->prev == '\r' ||
4425
+ c->prev == ' ' || c->prev == '\t')) {
4409
4426
  c->in_comment = 1;
4410
4427
  c->at_break = 0;
4411
4428
  return t;
@@ -4418,7 +4435,7 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
4418
4435
  return t;
4419
4436
  }
4420
4437
 
4421
- int line_start = (i == 0 || c->at_break || p[i - 1] == '\n' || p[i - 1] == '\r');
4438
+ int line_start = (!c->started || c->at_break || c->prev == '\n' || c->prev == '\r');
4422
4439
 
4423
4440
  /* block scalars: inside the fold no quote opens and every break
4424
4441
  * keeps its fold; the region ends at a col-0 marker, a %-line
@@ -4508,7 +4525,7 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
4508
4525
  if (!c->bs_open && ch == '-' && i + 2 < len && p[i + 1] == '-' && p[i + 2] == '-' &&
4509
4526
  (i + 3 >= len || p[i + 3] == ' ' || p[i + 3] == '\t' || p[i + 3] == '\n' ||
4510
4527
  p[i + 3] == '\r') &&
4511
- (c->at_break || i == 0)) {
4528
+ (c->at_break || !c->started)) {
4512
4529
  c->in_flow = 0; /* the marker bounds the document even when the
4513
4530
  * walk still believes a flow is open (the same
4514
4531
  * round-35 law as the in-quote rescue) */
@@ -4552,22 +4569,80 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
4552
4569
  }
4553
4570
  c->tok_start = 0;
4554
4571
  }
4555
- if (ch == ',' || ch == ':' || ch == '[' || ch == '{' || ch == ']' || ch == '}') {
4556
- c->tok_start = 1; /* flow separators: the next byte may begin a
4557
- * token with no space ("{\"a\":...") */
4558
- }
4559
- if (ch == '[' || ch == '{') {
4572
+ if ((ch == '[' || ch == '{') && (c->tok_start || c->prev == ':')) {
4573
+ /* a flow opener begins a token slot — at a token position, or
4574
+ * directly after the value ':' ("k:{a: 1}" opens; the blank
4575
+ * twin "k: {" rides tok_start already). Unconditioned, a
4576
+ * plain's interior '[' ("ya200%[") opened a PHANTOM flow whose
4577
+ * ',' armed the next token slot and a following "!<" opened a
4578
+ * phantom verbatim — breaks inside verbatim ride unnormalized,
4579
+ * the raw NELs defeated the line-facts kernel, and the plain
4580
+ * fold rendered one fold-SPACE per break instead of the n-1
4581
+ * newline tally (the r98 nightly — the #836 phantom's flow
4582
+ * half; the comma half landed in #836 itself). */
4583
+ c->tok_start = 1;
4560
4584
  c->in_flow++;
4561
- } else if (ch == ']' || ch == '}') {
4585
+ } else if (ch == ',') {
4586
+ /* flow separators glue the next token — THE COMMA IN FLOW
4587
+ * ONLY. The unconditioned arm let a BLOCK plain's interior
4588
+ * comma arm tok_start, a following "!<" opened a phantom
4589
+ * verbatim tag that swallowed the rest of the input (breaks
4590
+ * inside ride unnormalized), and the raw NEL/LS/PS bytes then
4591
+ * defeated the line-facts kernel's \n/\r law — the plain
4592
+ * fold's blank-line tally lost them (the r96 nightly,
4593
+ * yeptris#836, "b,,,!<!<"+NEL^3). ':' stays unconditioned:
4594
+ * the BLOCK value position rides it ("k: 'x'" opens the
4595
+ * quote, the NormLsPs pins). */
4562
4596
  if (c->in_flow > 0) {
4563
- c->in_flow--;
4597
+ c->tok_start = 1;
4598
+ }
4599
+ } else if (ch == ':') {
4600
+ /* the separator law (#833's own predicate): a ':' arms the
4601
+ * next token slot only before a blank/EOL/EOF — "k: 'x'"
4602
+ * opens the value quote (the NormLsPs pins) while an
4603
+ * interior "x:!<" of a block plain does NOT (the phantom
4604
+ * verbatim again, the #836 colon-led twin) */
4605
+ if (i + 1 >= len || p[i + 1] == ' ' || p[i + 1] == '\t' || p[i + 1] == '\n' ||
4606
+ p[i + 1] == '\r') {
4607
+ c->tok_start = 1;
4564
4608
  }
4609
+ } else if (ch == ']' || ch == '}') {
4610
+ c->tok_start = 1;
4565
4611
  }
4566
4612
  return t;
4567
4613
  }
4568
4614
 
4615
+ char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_t len,
4616
+ size_t* out_len, int* kept_raw);
4617
+
4618
+ static char* norm_breaks_run(const yep_allocator* sys, const char* p, size_t len, size_t* out_len,
4619
+ int* kept_raw, yep_norm_ctx* ctx);
4620
+
4569
4621
  char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_t len,
4570
4622
  size_t* out_len, int* kept_raw) {
4623
+ return norm_breaks_run(sys, p, len, out_len, kept_raw, NULL);
4624
+ }
4625
+
4626
+ /* The context-threaded core (#839). ctx == NULL is the one-shot form:
4627
+ * fresh state, and the !exotic early exit stands (no walk, no state).
4628
+ * A non-NULL ctx rides in the step state: the chunked route parses
4629
+ * whole documents, so the walk never sees a mid-construct cut — the
4630
+ * divergence was the RESUME state. A --- cut lands exactly where the
4631
+ * whole walk holds some carried state (an open quote it has not been
4632
+ * rescued from, at_break, the raw prev byte); resuming fresh re-lexes
4633
+ * the marker line differently ("--- 'tail": whole's rescue leaves
4634
+ * tok_start 0 so the apostrophe is content, a fresh context's
4635
+ * tok_start 1 opens a quote). Carrying, never resetting: doc N's walk
4636
+ * sees the same bytes with the same state whole holds at the cut, so
4637
+ * doc N + 1's start state equals whole's there — the induction makes
4638
+ * the concatenated documents byte-identical to the one-buffer walk.
4639
+ * The probe ALWAYS runs in this form (even without exotic leads): the
4640
+ * carried state must advance over every byte, or the next resume
4641
+ * starts stale. Both outcomes keep zero-copy semantics: a document
4642
+ * whose exotic bytes all ride raw is used as-is, and those raw bytes
4643
+ * are exactly what the one-buffer walk's copy carries for them. */
4644
+ static char* norm_breaks_run(const yep_allocator* sys, const char* p, size_t len, size_t* out_len,
4645
+ int* kept_raw, yep_norm_ctx* ctx) {
4571
4646
  if (kept_raw != NULL) {
4572
4647
  *kept_raw = 0;
4573
4648
  }
@@ -4584,15 +4659,21 @@ char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_
4584
4659
  exotic = 1; /* any NEL/LS/PS lead byte needs the context walk */
4585
4660
  break;
4586
4661
  }
4587
- if (!exotic) {
4588
- return NULL;
4589
- }
4590
4662
  yep_norm_ctx probe;
4591
- memset(&probe, 0, sizeof(probe));
4592
- probe.tok_start = 1;
4663
+ if (ctx != NULL) {
4664
+ probe = *ctx;
4665
+ } else {
4666
+ if (!exotic) {
4667
+ return NULL;
4668
+ }
4669
+ memset(&probe, 0, sizeof(probe));
4670
+ probe.tok_start = 1;
4671
+ }
4593
4672
  int found = 0;
4594
4673
  for (size_t j = 0; j < len;) {
4595
4674
  yep_norm_tok t = norm_step(&probe, p, len, j);
4675
+ probe.started = 1;
4676
+ probe.prev = (unsigned char)p[j + t.n - 1];
4596
4677
  if (t.fold_off >= 0) {
4597
4678
  found = 1;
4598
4679
  break;
@@ -4600,22 +4681,34 @@ char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_
4600
4681
  j += t.n;
4601
4682
  }
4602
4683
  if (!found) {
4603
- if (kept_raw != NULL) {
4604
- *kept_raw = 1; /* every exotic byte rides raw */
4684
+ if (ctx != NULL) {
4685
+ *ctx = probe; /* the carried state advanced even with no fold */
4686
+ } else {
4687
+ if (kept_raw != NULL) {
4688
+ *kept_raw = 1; /* every exotic byte rides raw */
4689
+ }
4690
+ return NULL; /* the exotic bytes all ride inside quoted regions:
4691
+ * the zero-copy path keeps them raw (the fix) */
4605
4692
  }
4606
- return NULL; /* the exotic bytes all ride inside quoted regions:
4607
- * the zero-copy path keeps them raw (the fix) */
4693
+ return NULL;
4608
4694
  }
4609
4695
  char* out = yep_alloc(sys, len + 1);
4610
4696
  if (out == NULL) {
4611
4697
  return NULL;
4612
4698
  }
4613
4699
  yep_norm_ctx cp;
4614
- memset(&cp, 0, sizeof(cp));
4615
- cp.tok_start = 1;
4700
+ if (ctx != NULL) {
4701
+ cp = *ctx; /* restart from the same carried state: the probe's
4702
+ * partial walk (it broke at the fold) is discarded */
4703
+ } else {
4704
+ memset(&cp, 0, sizeof(cp));
4705
+ cp.tok_start = 1;
4706
+ }
4616
4707
  size_t o = 0;
4617
4708
  for (size_t j = 0; j < len;) {
4618
4709
  yep_norm_tok t = norm_step(&cp, p, len, j);
4710
+ cp.started = 1;
4711
+ cp.prev = (unsigned char)p[j + t.n - 1];
4619
4712
  if (t.fold_off < 0) {
4620
4713
  if (kept_raw != NULL && *kept_raw == 0) {
4621
4714
  for (size_t m = 0; m + 1 < t.n; m++) {
@@ -4641,6 +4734,19 @@ char* yep_engine_normalize_breaks(const yep_allocator* sys, const char* p, size_
4641
4734
  if (out_len != NULL) {
4642
4735
  *out_len = o;
4643
4736
  }
4737
+ if (ctx != NULL) {
4738
+ *ctx = cp;
4739
+ }
4740
+ return out;
4741
+ }
4742
+
4743
+ static const char* e_normalize_breaks(yep_engine* e, const char* p, size_t len, size_t* out_len,
4744
+ yep_norm_ctx* ctx) {
4745
+ char* out = norm_breaks_run(e->sys, p, len, out_len, NULL, ctx);
4746
+ if (out == NULL) {
4747
+ return p;
4748
+ }
4749
+ e->norm_buf = out;
4644
4750
  return out;
4645
4751
  }
4646
4752
 
@@ -4667,6 +4773,11 @@ typedef struct yep_stepstate {
4667
4773
  int at_break; /* the previous position was a line break
4668
4774
  * (multibyte breaks leave a non-break byte as
4669
4775
  * p[i-1], so the '#' prev-byte test misses them) */
4776
+ yep_norm_ctx nctx; /* the break normalizer's carried context (#839):
4777
+ * each document's walk resumes with exactly the
4778
+ * state the whole-stream walk holds at the ---
4779
+ * cut, so per-document normalization is
4780
+ * byte-identical to the one-buffer walk */
4670
4781
  } yep_stepstate;
4671
4782
 
4672
4783
  static int engine_run_impl(yep_engine* e, const char* buf, size_t len, const yep_sink* sink,
@@ -4823,6 +4934,19 @@ static size_t step_scan(yep_stepstate* st) {
4823
4934
  st->tok_start = 1;
4824
4935
  st->word_led = 0;
4825
4936
  st->at_break = 1;
4937
+ } else if (c == '\r' && (i + 1 >= st->len || (unsigned char)p[i + 1] != '\n')) {
4938
+ /* a lone CR is a line break too (CRLF rides the \n arm
4939
+ * next iteration; the state is identical either way).
4940
+ * Missing this arm kept the scan inside a comment whose
4941
+ * line ended CR, the tab-led `{ {?` flow opener on the
4942
+ * next line went untracked, and the cut landed at the
4943
+ * following --- INSIDE the engine's open flow (the r97
4944
+ * nightly, yeptris#839: whole errored at the flow's ---
4945
+ * while chunked cut and errored at the cut edge) */
4946
+ st->in_comment = 0;
4947
+ st->tok_start = 1;
4948
+ st->word_led = 0;
4949
+ st->at_break = 1;
4826
4950
  } else if (c == 0xC2 && i + 1 < st->len && (unsigned char)p[i + 1] == 0x85) {
4827
4951
  i++;
4828
4952
  st->in_comment = 0;
@@ -5180,6 +5304,10 @@ int yep_engine_step(yep_engine* e, const char* chunk, size_t len, int final, con
5180
5304
  }
5181
5305
  yep_stepstate* st = e->step;
5182
5306
  st->tok_start = 1; /* the buffer starts at a line start */
5307
+ if (st->nctx.started == 0) {
5308
+ st->nctx.tok_start = 1; /* the stream's first walk: the same
5309
+ * fresh state the one-shot starts */
5310
+ }
5183
5311
  if (len > 0) {
5184
5312
  if (st->len + len > st->cap) {
5185
5313
  size_t cap = st->cap ? st->cap : 4096;
@@ -5245,7 +5373,8 @@ static int engine_run_impl(yep_engine* e, const char* buf, size_t len, const yep
5245
5373
  * it, and the next transcode replaces it. */
5246
5374
  e->parsing_final = emit_end;
5247
5375
  size_t norm_len = len;
5248
- e->p = e_normalize_breaks(e, buf, len, &norm_len);
5376
+ yep_norm_ctx* nctx = e->step != NULL ? &((yep_stepstate*)e->step)->nctx : NULL;
5377
+ e->p = e_normalize_breaks(e, buf, len, &norm_len, nctx);
5249
5378
  e->len = norm_len;
5250
5379
  e->pos = 0;
5251
5380
  e->line = 1 + e->line_base;
@@ -5303,7 +5432,7 @@ static int engine_run_impl(yep_engine* e, const char* buf, size_t len, const yep
5303
5432
  e_fail(e, YEP_ERR_TAB_IN_INDENT, e->pos + li.indent);
5304
5433
  goto fail;
5305
5434
  }
5306
- li.indent = (uint16_t)(t - li.offset);
5435
+ li.indent = (uint32_t)(t - li.offset);
5307
5436
  }
5308
5437
  if (li.flags & YEP_LF_DIRECTIVE) {
5309
5438
  if (doc_open) {
@@ -5623,7 +5752,7 @@ static int engine_run_impl(yep_engine* e, const char* buf, size_t len, const yep
5623
5752
  * dash-blank test is computed once per line (TODO.restructure/75
5624
5753
  * — it was spelled out twice below); the top frame loads once
5625
5754
  * per iteration. */
5626
- uint16_t c = li.indent;
5755
+ uint32_t c = li.indent;
5627
5756
  const int dash_blank =
5628
5757
  li.first == '-' &&
5629
5758
  (li.offset + li.indent + 1 >= e->len || e->p[li.offset + li.indent + 1] == ' ' ||
@@ -90,13 +90,13 @@ typedef struct yep_sink {
90
90
  * placed, 0 = not handled (the engine emits the two events
91
91
  * exactly as before), <0 = abort. */
92
92
  int (*on_block_pair)(void* ctx, const yep_view* key, const struct yep_block_value* v,
93
- uint32_t line, uint16_t key_col, uint16_t val_col);
93
+ uint32_t line, uint32_t key_col, uint32_t val_col);
94
94
  /* Optional block-open fast path (TODO.restructure/57): a `key:`
95
95
  * line whose value lives on FOLLOWING lines, when the engine is
96
96
  * about to open a FRESH mapping. 1 = the sink built the map node
97
97
  * (placed, stack pushed) and the key node — the engine opens its
98
98
  * frame silently and skips both events; 0 = events as before. */
99
- int (*on_block_open)(void* ctx, const yep_view* key, uint32_t line, uint16_t key_col);
99
+ int (*on_block_open)(void* ctx, const yep_view* key, uint32_t line, uint32_t key_col);
100
100
  /* Optional dash-item fast path (TODO.restructure/78): a classified
101
101
  * `- value` plain item after the engine opened (or continued) the
102
102
  * block sequence. 1 = the sink built and placed the item node,
@@ -248,7 +248,8 @@ static yep_tag_id compat11(void* ctx, const char* p, uint32_t n) {
248
248
  if (n - i > 2 && p[i] == '0' && (p[i + 1] == 'b' || p[i + 1] == 'B')) {
249
249
  return is_digits_us(p + i + 2, n - i - 2) ? 1 : 0;
250
250
  }
251
- if (n - i > 2 && p[i] == '0' && (p[i + 1] == 'x' || p[i + 1] == 'X')) {
251
+ if (n - i > 2 && p[i] == '0' &&
252
+ p[i + 1] == 'x') { /* lowercase only — psych keeps "0X1A" a String */
252
253
  uint32_t k = i + 2;
253
254
  if (p[k] == '_') {
254
255
  k++;
@@ -78,7 +78,9 @@ static yep_tag_id core12(void* ctx, const char* p, uint32_t n) {
78
78
  }
79
79
  }
80
80
  }
81
- if (n - i > 2 && p[i] == '0' && (p[i + 1] == 'x' || p[i + 1] == 'X')) {
81
+ if (n - i > 2 && p[i] == '0' &&
82
+ p[i + 1] == 'x') { /* lowercase only: the core schema's int regexp; "0X1A" rides a string
83
+ (psych agrees) */
82
84
  for (uint32_t k = i + 2; k < n; k++) {
83
85
  char c = p[k];
84
86
  if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {