Skip to main content

rlsp_yaml_parser/
flow.rs

1// SPDX-License-Identifier: MIT
2
3//! YAML 1.2 §7 flow style productions [104]–[161].
4//!
5//! Covers alias nodes, empty nodes, double-quoted scalars, single-quoted
6//! scalars, plain scalars, flow sequences, flow mappings, and flow nodes.
7//! Each function is named after the spec production and cross-referenced by
8//! its production number in a `// [N]` comment.
9
10use crate::chars::{
11    b_non_content, decode_escape, ns_anchor_char, ns_plain_char, ns_plain_first, s_white,
12};
13use crate::combinator::neg_lookahead;
14use crate::combinator::{
15    Context, Parser, Reply, State, char_parser, many0, many1, seq, token, wrap_tokens,
16};
17use crate::structure::{
18    c_forbidden, c_ns_properties, l_empty, s_flow_folded, s_flow_line_prefix_ge, s_separate,
19    s_separate_ge,
20};
21use crate::token::{Code, Token};
22
23// ---------------------------------------------------------------------------
24// §7.1 – Alias nodes [104]
25// ---------------------------------------------------------------------------
26
27/// [104] c-ns-alias-node — `*` followed by anchor name.
28///
29/// Emits `BeginAlias` / `Indicator` / `Meta` / `EndAlias`.
30#[must_use]
31pub fn c_ns_alias_node<'i>() -> Parser<'i> {
32    wrap_tokens(
33        Code::BeginAlias,
34        Code::EndAlias,
35        seq(
36            token(Code::Indicator, char_parser('*')),
37            token(Code::Meta, many1(ns_anchor_char())),
38        ),
39    )
40}
41
42// ---------------------------------------------------------------------------
43// §7.2 – Empty nodes [105]–[106]
44// ---------------------------------------------------------------------------
45
46/// [105] e-scalar — empty scalar: zero consumption, always succeeds.
47#[must_use]
48pub fn e_scalar<'i>() -> Parser<'i> {
49    Box::new(|state| Reply::Success {
50        tokens: Vec::new(),
51        state,
52    })
53}
54
55/// [106] e-node — empty node: zero consumption, always succeeds.
56#[must_use]
57pub fn e_node<'i>() -> Parser<'i> {
58    Box::new(|state| Reply::Success {
59        tokens: Vec::new(),
60        state,
61    })
62}
63
64// ---------------------------------------------------------------------------
65// §7.3.1 – Double-quoted scalars [107]–[113]
66// ---------------------------------------------------------------------------
67
68/// [107] nb-double-char — a character allowed in a double-quoted scalar body.
69///
70/// Either an escape sequence (`\` followed by a valid escape code) or any
71/// non-break character that is not `"` or `\`.
72#[must_use]
73pub fn nb_double_char<'i>() -> Parser<'i> {
74    Box::new(|state| {
75        let Some(ch) = state.peek() else {
76            return Reply::Failure;
77        };
78        match ch {
79            '"' => Reply::Failure,
80            '\\' => {
81                // Record start before consuming the backslash.
82                let start_pos = state.pos;
83                let start_input = state.input;
84                let after_backslash = state.advance('\\');
85                let rest = after_backslash.input;
86                match decode_escape(rest) {
87                    None => Reply::Failure,
88                    Some((_decoded, bytes_consumed)) => {
89                        // Advance through the escape code characters.
90                        let mut s = after_backslash;
91                        let mut consumed = 0;
92                        for ec in rest.chars() {
93                            if consumed >= bytes_consumed {
94                                break;
95                            }
96                            s = s.advance(ec);
97                            consumed += ec.len_utf8();
98                        }
99                        let total_bytes = s.pos.byte_offset - start_pos.byte_offset;
100                        let text = &start_input[..total_bytes];
101                        // Emit escape as a Text token (there is no Escape code variant).
102                        Reply::Success {
103                            tokens: vec![Token {
104                                code: Code::Text,
105                                pos: start_pos,
106                                text,
107                            }],
108                            state: s,
109                        }
110                    }
111                }
112            }
113            // Any nb-char (not a line break, not BOM) that is not '"' or '\'
114            _ => {
115                // Reject line break characters.
116                if matches!(ch, '\n' | '\r') {
117                    return Reply::Failure;
118                }
119                // Reject BOM.
120                if ch == '\u{FEFF}' {
121                    return Reply::Failure;
122                }
123                let start_pos = state.pos;
124                let start_input = state.input;
125                let new_state = state.advance(ch);
126                let byte_len = ch.len_utf8();
127                Reply::Success {
128                    tokens: vec![Token {
129                        code: Code::Text,
130                        pos: start_pos,
131                        text: &start_input[..byte_len],
132                    }],
133                    state: new_state,
134                }
135            }
136        }
137    })
138}
139
140/// [108] ns-double-char — `nb-double-char` that is not whitespace.
141#[must_use]
142pub fn ns_double_char<'i>() -> Parser<'i> {
143    Box::new(|state| {
144        let Some(ch) = state.peek() else {
145            return Reply::Failure;
146        };
147        if matches!(ch, ' ' | '\t') {
148            return Reply::Failure;
149        }
150        nb_double_char()(state)
151    })
152}
153
154/// [111] nb-double-one-line — text content of one line in a double-quoted scalar.
155fn nb_double_one_line<'i>() -> Parser<'i> {
156    many0(nb_double_char())
157}
158
159/// [114] nb-ns-double-in-line — interleaved spaces and non-space chars on one line.
160///
161/// Matches zero or more groups of (optional whitespace + one ns-double-char).
162/// This is `(s-white* ns-double-char)*`.
163fn nb_ns_double_in_line<'i>() -> Parser<'i> {
164    many0(Box::new(|state: State<'_>| {
165        let (ws_tokens, after_ws) = match many0(s_white())(state) {
166            Reply::Success { tokens, state } => (tokens, state),
167            other @ (Reply::Failure | Reply::Error(_)) => return other,
168        };
169        match ns_double_char()(after_ws) {
170            Reply::Success {
171                tokens: ns_tokens,
172                state: final_state,
173            } => {
174                let mut all = ws_tokens;
175                all.extend(ns_tokens);
176                Reply::Success {
177                    tokens: all,
178                    state: final_state,
179                }
180            }
181            // No ns-double-char after whitespace — backtrack: do not consume the whitespace.
182            Reply::Failure => Reply::Failure,
183            other @ Reply::Error(_) => other,
184        }
185    }))
186}
187
188/// [112] s-double-escaped(n) — backslash-escaped line break in double-quoted scalars.
189///
190/// Handles `\` at end of line: `s-white* '\' b-non-content l-empty(n,flow-in)* s-flow-line-prefix(n)`.
191fn s_double_escaped(n: i32) -> Parser<'static> {
192    Box::new(move |state| {
193        // Optional leading whitespace before `\`.
194        let (ws_tokens, after_ws) = match many0(s_white())(state) {
195            Reply::Success { tokens, state } => (tokens, state),
196            other @ (Reply::Failure | Reply::Error(_)) => return other,
197        };
198        // Must be followed by `\`.
199        let Some('\\') = after_ws.peek() else {
200            return Reply::Failure;
201        };
202        let backslash_pos = after_ws.pos;
203        let after_backslash = after_ws.advance('\\');
204        let backslash_token = crate::token::Token {
205            code: Code::Text,
206            pos: backslash_pos,
207            text: "\\",
208        };
209        // Non-content break.
210        let (break_tokens, after_break) = match b_non_content()(after_backslash) {
211            Reply::Success { tokens, state } => (tokens, state),
212            Reply::Failure => return Reply::Failure,
213            other @ Reply::Error(_) => return other,
214        };
215        // Zero or more empty lines.
216        let (empty_tokens, after_empty) = match many0(l_empty(n, Context::FlowIn))(after_break) {
217            Reply::Success { tokens, state } => (tokens, state),
218            other @ (Reply::Failure | Reply::Error(_)) => return other,
219        };
220        // Flow line prefix for the continuation — use _ge to accept deeper indent.
221        let (prefix_tokens, final_state) = match s_flow_line_prefix_ge(n)(after_empty) {
222            Reply::Success { tokens, state } => (tokens, state),
223            Reply::Failure => return Reply::Failure,
224            other @ Reply::Error(_) => return other,
225        };
226        let mut all = ws_tokens;
227        all.push(backslash_token);
228        all.extend(break_tokens);
229        all.extend(empty_tokens);
230        all.extend(prefix_tokens);
231        Reply::Success {
232            tokens: all,
233            state: final_state,
234        }
235    })
236}
237
238/// [113] s-double-break(n) — break transition in double-quoted scalars.
239///
240/// Either an escaped break (`s-double-escaped`) or a flow-folded break.
241fn s_double_break(n: i32) -> Parser<'static> {
242    // s-double-escaped takes priority over s-flow-folded.
243    Box::new(move |state| match s_double_escaped(n)(state.clone()) {
244        reply @ Reply::Success { .. } => reply,
245        Reply::Failure | Reply::Error(_) => s_flow_folded(n)(state),
246    })
247}
248
249/// [115] s-double-next-line(n) — continuation line inside double-quoted scalars.
250///
251/// Per spec [115]: `s-double-break(n) (ns-double-char nb-ns-double-in-line
252/// (s-double-next-line(n) | s-white*) | "")`.
253fn s_double_next_line(n: i32) -> Parser<'static> {
254    Box::new(move |state| {
255        // Consume the break transition.
256        let (break_tokens, after_break) = match s_double_break(n)(state) {
257            Reply::Success { tokens, state } => (tokens, state),
258            other @ (Reply::Failure | Reply::Error(_)) => return other,
259        };
260        // Optional content on the continuation line.
261        // Try ns-double-char first; if absent, the break alone is valid ("").
262        let Some(ch) = after_break.peek() else {
263            // EOF after break — no more content.
264            return Reply::Success {
265                tokens: break_tokens,
266                state: after_break,
267            };
268        };
269        // Check if first char is ns-double-char (not space/tab and not `"` or `\n`/`\r`).
270        if matches!(ch, ' ' | '\t' | '"') {
271            // Not starting with ns-double-char — empty continuation.
272            return Reply::Success {
273                tokens: break_tokens,
274                state: after_break,
275            };
276        }
277        let (first_tokens, after_first) = match ns_double_char()(after_break.clone()) {
278            Reply::Success { tokens, state } => (tokens, state),
279            // Not an ns-double-char — empty continuation after break.
280            Reply::Failure | Reply::Error(_) => {
281                return Reply::Success {
282                    tokens: break_tokens,
283                    state: after_break,
284                };
285            }
286        };
287        // Consume the rest of the line: (s-white* ns-double-char)*.
288        let (inline_tokens, after_inline) = match nb_ns_double_in_line()(after_first) {
289            Reply::Success { tokens, state } => (tokens, state),
290            other @ (Reply::Failure | Reply::Error(_)) => return other,
291        };
292        // Try another continuation or trailing whitespace.
293        let (tail_tokens, final_state) = match s_double_next_line(n)(after_inline.clone()) {
294            Reply::Success { tokens, state } => (tokens, state),
295            Reply::Failure | Reply::Error(_) => {
296                // Trailing s-white* at end of scalar.
297                match many0(s_white())(after_inline.clone()) {
298                    Reply::Success { tokens, state } => (tokens, state),
299                    Reply::Failure | Reply::Error(_) => (Vec::new(), after_inline),
300                }
301            }
302        };
303        let mut all = break_tokens;
304        all.extend(first_tokens);
305        all.extend(inline_tokens);
306        all.extend(tail_tokens);
307        Reply::Success {
308            tokens: all,
309            state: final_state,
310        }
311    })
312}
313
314/// [116] nb-double-multi-line(n) — multi-line body of double-quoted scalar.
315///
316/// Per spec [116]: `nb-ns-double-in-line (s-double-next-line(n) | s-white*)`.
317fn nb_double_multi_line(n: i32) -> Parser<'static> {
318    Box::new(move |state| {
319        // Consume interleaved non-space content (may include spaces between ns chars).
320        let (inline_tokens, after_inline) = match nb_ns_double_in_line()(state) {
321            Reply::Success { tokens, state } => (tokens, state),
322            other @ (Reply::Failure | Reply::Error(_)) => return other,
323        };
324        // Try a continuation line, or accept trailing whitespace for last line.
325        let (tail_tokens, final_state) = match s_double_next_line(n)(after_inline.clone()) {
326            Reply::Success { tokens, state } => (tokens, state),
327            Reply::Failure | Reply::Error(_) => match many0(s_white())(after_inline.clone()) {
328                Reply::Success { tokens, state } => (tokens, state),
329                Reply::Failure | Reply::Error(_) => (Vec::new(), after_inline),
330            },
331        };
332        let mut all = inline_tokens;
333        all.extend(tail_tokens);
334        Reply::Success {
335            tokens: all,
336            state: final_state,
337        }
338    })
339}
340
341/// [110] nb-double-text(n,c) — full body of a double-quoted scalar.
342fn nb_double_text(n: i32, c: Context) -> Parser<'static> {
343    // Multi-line is permitted in FlowOut and FlowIn contexts per spec [110].
344    match c {
345        Context::FlowOut | Context::FlowIn => nb_double_multi_line(n),
346        Context::BlockKey | Context::FlowKey | Context::BlockOut | Context::BlockIn => {
347            nb_double_one_line()
348        }
349    }
350}
351
352/// [109] c-double-quoted(n,c) — `"` body `"` wrapped in `BeginScalar`/`EndScalar`.
353#[must_use]
354pub fn c_double_quoted(n: i32, c: Context) -> Parser<'static> {
355    wrap_tokens(
356        Code::BeginScalar,
357        Code::EndScalar,
358        Box::new(move |state| {
359            // Opening quote.
360            let (open_tokens, after_open) = match token(Code::Indicator, char_parser('"'))(state) {
361                Reply::Success { tokens, state } => (tokens, state),
362                other @ (Reply::Failure | Reply::Error(_)) => return other,
363            };
364            // Body — collect all chars as a single Text token.
365            let body_parser = nb_double_text(n, c);
366            match body_parser(after_open.clone()) {
367                Reply::Success {
368                    tokens,
369                    state: after_body,
370                } => {
371                    // Closing quote.
372                    match token(Code::Indicator, char_parser('"'))(after_body) {
373                        Reply::Success {
374                            tokens: close_tokens,
375                            state: final_state,
376                        } => {
377                            let mut all = open_tokens;
378                            all.extend(tokens);
379                            all.extend(close_tokens);
380                            Reply::Success {
381                                tokens: all,
382                                state: final_state,
383                            }
384                        }
385                        Reply::Failure | Reply::Error(_) => Reply::Failure,
386                    }
387                }
388                other @ (Reply::Failure | Reply::Error(_)) => other,
389            }
390        }),
391    )
392}
393
394// ---------------------------------------------------------------------------
395// §7.3.3 – Single-quoted scalars [114]–[121]
396// ---------------------------------------------------------------------------
397
398/// [114] c-quoted-quote — two adjacent single quotes representing one `'`.
399#[must_use]
400pub fn c_quoted_quote<'i>() -> Parser<'i> {
401    seq(char_parser('\''), char_parser('\''))
402}
403
404/// [115] nb-single-char — a character allowed in a single-quoted scalar body.
405///
406/// Either `''` (escaped single quote) or any non-break, non-`'` character.
407#[must_use]
408pub fn nb_single_char<'i>() -> Parser<'i> {
409    Box::new(|state| {
410        let Some(ch) = state.peek() else {
411            return Reply::Failure;
412        };
413        if ch == '\'' {
414            // Attempt `''` (escaped); lone `'` is the scalar end.
415            return c_quoted_quote()(state);
416        }
417        if matches!(ch, '\n' | '\r') {
418            return Reply::Failure;
419        }
420        if ch == '\u{FEFF}' {
421            return Reply::Failure;
422        }
423        let start_pos = state.pos;
424        let start_input = state.input;
425        let new_state = state.advance(ch);
426        let byte_len = ch.len_utf8();
427        Reply::Success {
428            tokens: vec![Token {
429                code: Code::Text,
430                pos: start_pos,
431                text: &start_input[..byte_len],
432            }],
433            state: new_state,
434        }
435    })
436}
437
438/// [116] ns-single-char — `nb-single-char` that is not whitespace.
439#[must_use]
440pub fn ns_single_char<'i>() -> Parser<'i> {
441    Box::new(|state| {
442        let Some(ch) = state.peek() else {
443            return Reply::Failure;
444        };
445        if matches!(ch, ' ' | '\t') {
446            return Reply::Failure;
447        }
448        nb_single_char()(state)
449    })
450}
451
452/// [119] nb-single-one-line — one line of single-quoted text.
453fn nb_single_one_line<'i>() -> Parser<'i> {
454    many0(nb_single_char())
455}
456
457/// nb-ns-single-in-line — interleaved whitespace and non-space chars on one line.
458///
459/// Matches `( s-white* ns-single-char )*` — the single-quoted analogue of
460/// `nb-ns-double-in-line` [114].
461fn nb_ns_single_in_line<'i>() -> Parser<'i> {
462    many0(Box::new(|state: State<'_>| {
463        let (ws_tokens, after_ws) = match many0(s_white())(state) {
464            Reply::Success { tokens, state } => (tokens, state),
465            other @ (Reply::Failure | Reply::Error(_)) => return other,
466        };
467        match ns_single_char()(after_ws) {
468            Reply::Success {
469                tokens: ns_tokens,
470                state: final_state,
471            } => {
472                let mut all = ws_tokens;
473                all.extend(ns_tokens);
474                Reply::Success {
475                    tokens: all,
476                    state: final_state,
477                }
478            }
479            Reply::Failure => Reply::Failure,
480            other @ Reply::Error(_) => other,
481        }
482    }))
483}
484
485/// [124] s-single-next-line(n) — line folding inside single-quoted scalars.
486///
487/// Per spec [124]:
488///   `s-flow-folded(n)
489///    ( ns-single-char nb-ns-single-in-line
490///      ( s-single-next-line(n) | s-white* ) )?`
491///
492/// The continuation part after `s-flow-folded` is optional.
493fn s_single_next_line(n: i32) -> Parser<'static> {
494    Box::new(move |state| {
495        let (fold_tokens, after_fold) = match s_flow_folded(n)(state) {
496            Reply::Success { tokens, state } => (tokens, state),
497            other @ (Reply::Failure | Reply::Error(_)) => return other,
498        };
499
500        // Optional continuation: ns-single-char nb-ns-single-in-line ( recurse | s-white* )
501        let (first_tokens, after_first) = match ns_single_char()(after_fold.clone()) {
502            Reply::Success { tokens, state } => (tokens, state),
503            Reply::Failure | Reply::Error(_) => {
504                return Reply::Success {
505                    tokens: fold_tokens,
506                    state: after_fold,
507                };
508            }
509        };
510        let (inline_tokens, after_inline) = match nb_ns_single_in_line()(after_first) {
511            Reply::Success { tokens, state } => (tokens, state),
512            other @ (Reply::Failure | Reply::Error(_)) => return other,
513        };
514        let (tail_tokens, final_state) = match s_single_next_line(n)(after_inline.clone()) {
515            Reply::Success { tokens, state } => (tokens, state),
516            Reply::Failure | Reply::Error(_) => match many0(s_white())(after_inline.clone()) {
517                Reply::Success { tokens, state } => (tokens, state),
518                Reply::Failure | Reply::Error(_) => (Vec::new(), after_inline),
519            },
520        };
521        let mut all = fold_tokens;
522        all.extend(first_tokens);
523        all.extend(inline_tokens);
524        all.extend(tail_tokens);
525        Reply::Success {
526            tokens: all,
527            state: final_state,
528        }
529    })
530}
531
532/// [125] nb-single-multi-line(n) — multi-line body of single-quoted scalar.
533///
534/// Per spec [125]: `nb-ns-single-in-line ( s-single-next-line(n) | s-white* )`.
535fn nb_single_multi_line(n: i32) -> Parser<'static> {
536    Box::new(move |state| {
537        let (inline_tokens, after_inline) = match nb_ns_single_in_line()(state) {
538            Reply::Success { tokens, state } => (tokens, state),
539            other @ (Reply::Failure | Reply::Error(_)) => return other,
540        };
541        let (tail_tokens, final_state) = match s_single_next_line(n)(after_inline.clone()) {
542            Reply::Success { tokens, state } => (tokens, state),
543            Reply::Failure | Reply::Error(_) => match many0(s_white())(after_inline.clone()) {
544                Reply::Success { tokens, state } => (tokens, state),
545                Reply::Failure | Reply::Error(_) => (Vec::new(), after_inline),
546            },
547        };
548        let mut all = inline_tokens;
549        all.extend(tail_tokens);
550        Reply::Success {
551            tokens: all,
552            state: final_state,
553        }
554    })
555}
556
557/// [118] nb-single-text(n,c) — full body of a single-quoted scalar.
558fn nb_single_text(n: i32, c: Context) -> Parser<'static> {
559    // Multi-line is permitted in FlowOut and FlowIn contexts per spec [118].
560    match c {
561        Context::FlowOut | Context::FlowIn => nb_single_multi_line(n),
562        Context::BlockKey | Context::FlowKey | Context::BlockOut | Context::BlockIn => {
563            nb_single_one_line()
564        }
565    }
566}
567
568/// [117] c-single-quoted(n,c) — `'` body `'` wrapped in `BeginScalar`/`EndScalar`.
569#[must_use]
570pub fn c_single_quoted(n: i32, c: Context) -> Parser<'static> {
571    wrap_tokens(
572        Code::BeginScalar,
573        Code::EndScalar,
574        Box::new(move |state| {
575            let (open_tokens, after_open) = match token(Code::Indicator, char_parser('\''))(state) {
576                Reply::Success { tokens, state } => (tokens, state),
577                other @ (Reply::Failure | Reply::Error(_)) => return other,
578            };
579            let body_parser = nb_single_text(n, c);
580            match body_parser(after_open.clone()) {
581                Reply::Success {
582                    tokens: body_tokens,
583                    state: after_body,
584                } => match token(Code::Indicator, char_parser('\''))(after_body) {
585                    Reply::Success {
586                        tokens: close_tokens,
587                        state: final_state,
588                    } => {
589                        let mut all = open_tokens;
590                        all.extend(body_tokens);
591                        all.extend(close_tokens);
592                        Reply::Success {
593                            tokens: all,
594                            state: final_state,
595                        }
596                    }
597                    Reply::Failure | Reply::Error(_) => Reply::Failure,
598                },
599                other @ (Reply::Failure | Reply::Error(_)) => other,
600            }
601        }),
602    )
603}
604
605// ---------------------------------------------------------------------------
606// §7.3.3 – Plain scalars [122]–[135]
607// ---------------------------------------------------------------------------
608
609// ns_plain_first(c) — defined in chars.rs
610// ns_plain_safe(c)  — defined in chars.rs
611// ns_plain_char(c)  — defined in chars.rs
612
613/// [132] nb-ns-plain-in-line(c) — whitespace + ns-plain-char sequences after first char.
614fn nb_ns_plain_in_line(c: Context) -> Parser<'static> {
615    use crate::chars::s_white;
616    many0(Box::new(move |state: State<'_>| {
617        let before_ws = state.pos.byte_offset;
618        let (ws_tokens, after_ws) = match many0(s_white())(state) {
619            Reply::Success { tokens, state } => (tokens, state),
620            Reply::Failure | Reply::Error(_) => unreachable!("many0 always succeeds"),
621        };
622        let had_ws = after_ws.pos.byte_offset > before_ws;
623        // Per spec [130]: `#` requires preceding ns-char (non-whitespace).
624        if had_ws && after_ws.peek() == Some('#') {
625            return Reply::Failure;
626        }
627        match ns_plain_char(c)(after_ws) {
628            Reply::Success {
629                tokens: char_tokens,
630                state: final_state,
631            } => {
632                let mut all = ws_tokens;
633                all.extend(char_tokens);
634                Reply::Success {
635                    tokens: all,
636                    state: final_state,
637                }
638            }
639            other @ (Reply::Failure | Reply::Error(_)) => other,
640        }
641    }))
642}
643
644/// [134] ns-plain-one-line(c) — a plain scalar that fits on one line.
645fn ns_plain_one_line(c: Context) -> Parser<'static> {
646    seq(ns_plain_first(c), nb_ns_plain_in_line(c))
647}
648
649/// [133] s-ns-plain-next-line(n,c) — continuation line of a plain scalar.
650///
651/// A continuation line must not start at a document boundary (`c-forbidden`):
652/// `---` or `...` at column 0 followed by a safe terminator.
653fn s_ns_plain_next_line(n: i32, c: Context) -> Parser<'static> {
654    Box::new(move |state| {
655        let (fold_tokens, after_fold) = match s_flow_folded(n)(state) {
656            Reply::Success { tokens, state } => (tokens, state),
657            other @ (Reply::Failure | Reply::Error(_)) => return other,
658        };
659        // `#` after fold is a comment (preceded by whitespace from fold prefix).
660        if after_fold.peek() == Some('#') {
661            return Reply::Failure;
662        }
663        let rest = seq(
664            neg_lookahead(c_forbidden()),
665            seq(ns_plain_char(c), nb_ns_plain_in_line(c)),
666        );
667        match rest(after_fold) {
668            Reply::Success {
669                tokens: rest_tokens,
670                state: final_state,
671            } => {
672                let mut all = fold_tokens;
673                all.extend(rest_tokens);
674                Reply::Success {
675                    tokens: all,
676                    state: final_state,
677                }
678            }
679            other @ (Reply::Failure | Reply::Error(_)) => other,
680        }
681    })
682}
683
684/// [135] ns-plain-multi-line(n,c) — a plain scalar spanning multiple lines.
685fn ns_plain_multi_line(n: i32, c: Context) -> Parser<'static> {
686    seq(ns_plain_one_line(c), many0(s_ns_plain_next_line(n, c)))
687}
688
689/// [131] ns-plain(n,c) — a plain scalar, single- or multi-line.
690///
691/// Per spec [131], block-key and flow-key contexts use only the one-line form.
692/// All other contexts allow continuation lines.
693///
694/// Emits `BeginScalar` / `Text` / `EndScalar`.
695#[must_use]
696pub fn ns_plain(n: i32, c: Context) -> Parser<'static> {
697    let inner: Parser<'static> = match c {
698        Context::BlockKey | Context::FlowKey => token(Code::Text, ns_plain_one_line(c)),
699        Context::BlockOut | Context::BlockIn | Context::FlowOut | Context::FlowIn => {
700            token(Code::Text, ns_plain_multi_line(n, c))
701        }
702    };
703    wrap_tokens(Code::BeginScalar, Code::EndScalar, inner)
704}
705
706// ---------------------------------------------------------------------------
707// §7.4 – Flow sequences [136]–[140]
708// ---------------------------------------------------------------------------
709
710/// [136] c-flow-sequence(n,c) — `[` entries `]`.
711///
712/// Emits `BeginSequence` / entries / `EndSequence`.
713#[must_use]
714pub fn c_flow_sequence(n: i32, c: Context) -> Parser<'static> {
715    wrap_tokens(
716        Code::BeginSequence,
717        Code::EndSequence,
718        Box::new(move |state| {
719            let outer_c = state.c;
720            // Opening `[`.
721            let (open_tokens, after_open) = match token(Code::Indicator, char_parser('['))(state) {
722                Reply::Success { tokens, state } => (tokens, state),
723                other @ (Reply::Failure | Reply::Error(_)) => return other,
724            };
725            // Optional separation, then inner context (FlowIn).
726            let after_sep = skip_flow_sep(n, after_open);
727            let inner_c = inner_flow_context(c);
728            let inner_state = State {
729                c: inner_c,
730                ..after_sep
731            };
732            // Optional entries per spec [136].
733            let (entries_tokens, after_entries) =
734                match ns_s_flow_seq_entries(n, inner_c)(inner_state.clone()) {
735                    Reply::Success { tokens, state } => (tokens, state),
736                    Reply::Failure | Reply::Error(_) => (Vec::new(), inner_state),
737                };
738            // Optional trailing separation.
739            let after_sep2 = skip_flow_sep(n, after_entries);
740            // Closing `]`.
741            match token(Code::Indicator, char_parser(']'))(after_sep2) {
742                Reply::Success {
743                    tokens: close_tokens,
744                    state: final_state,
745                } => {
746                    let mut all = open_tokens;
747                    all.extend(entries_tokens);
748                    all.extend(close_tokens);
749                    Reply::Success {
750                        tokens: all,
751                        state: State {
752                            c: outer_c,
753                            ..final_state
754                        },
755                    }
756                }
757                Reply::Failure | Reply::Error(_) => Reply::Failure,
758            }
759        }),
760    )
761}
762
763/// [137] ns-s-flow-seq-entries(n,c) — comma-separated sequence entries (zero or more).
764///
765/// Entries may be empty (null nodes). A leading or trailing comma results in
766/// an implicit empty entry, which is represented by emitting no tokens.
767fn ns_s_flow_seq_entries(n: i32, c: Context) -> Parser<'static> {
768    Box::new(move |state| {
769        // First entry is required per spec [137].
770        let (first_tokens, after_first) = match ns_flow_seq_entry(n, c)(state) {
771            Reply::Success { tokens, state } => (tokens, state),
772            Reply::Error(e) => return Reply::Error(e),
773            Reply::Failure => return Reply::Failure,
774        };
775        // Zero or more `, entry` repetitions.
776        let mut all_tokens = first_tokens;
777        let mut current = after_first;
778        loop {
779            let after_sep = skip_flow_sep(n, current.clone());
780            match token(Code::Indicator, char_parser(','))(after_sep) {
781                Reply::Error(e) => return Reply::Error(e),
782                Reply::Failure => break,
783                Reply::Success {
784                    tokens: comma_tokens,
785                    state: after_comma,
786                } => {
787                    let after_sep2 = skip_flow_sep(n, after_comma.clone());
788                    let (entry_tokens, after_entry) =
789                        match ns_flow_seq_entry(n, c)(after_sep2.clone()) {
790                            Reply::Success { tokens, state } => (tokens, state),
791                            Reply::Failure | Reply::Error(_) => {
792                                // Double comma = invalid.
793                                if after_sep2.peek() == Some(',') {
794                                    return Reply::Failure;
795                                }
796                                (Vec::new(), after_sep2)
797                            }
798                        };
799                    all_tokens.extend(comma_tokens);
800                    all_tokens.extend(entry_tokens);
801                    current = after_entry;
802                }
803            }
804        }
805        Reply::Success {
806            tokens: all_tokens,
807            state: current,
808        }
809    })
810}
811
812/// [138] ns-flow-seq-entry(n,c) — a single entry in a flow sequence.
813///
814/// Per spec: `ns-flow-pair(n,c) | ns-flow-node(n,c)`. When `ns-flow-pair`
815/// succeeds but the remaining input doesn't start with a flow terminator
816/// (`,`, `]`, `}`, whitespace, or line break), the pair may have consumed
817/// too little — try `ns-flow-node` and take the longer match.
818fn ns_flow_seq_entry(n: i32, c: Context) -> Parser<'static> {
819    Box::new(move |state| {
820        let pair = c_ns_flow_pair(n, c)(state.clone());
821        match &pair {
822            Reply::Success {
823                state: pair_state, ..
824            } => {
825                // Check if the pair result looks complete: remaining input
826                // starts with a flow terminator or whitespace/break.
827                let next = pair_state.peek();
828                let looks_complete = matches!(next, None | Some(',' | ']' | '}' | ' ' | '\t'));
829                if looks_complete {
830                    return pair;
831                }
832                // Pair consumed too little — try ns-flow-node for a longer match.
833                let node = ns_flow_node(n, c)(state);
834                let node_end = match &node {
835                    Reply::Success { state, .. } => state.pos.byte_offset,
836                    Reply::Failure | Reply::Error(_) => 0,
837                };
838                if node_end > pair_state.pos.byte_offset {
839                    node
840                } else {
841                    pair
842                }
843            }
844            Reply::Failure | Reply::Error(_) => ns_flow_node(n, c)(state),
845        }
846    })
847}
848
849// ---------------------------------------------------------------------------
850// §7.5 – Flow mappings [141]–[153]
851// ---------------------------------------------------------------------------
852
853/// [141] c-flow-mapping(n,c) — `{` entries `}`.
854///
855/// Emits `BeginMapping` / entries / `EndMapping`.
856#[must_use]
857pub fn c_flow_mapping(n: i32, c: Context) -> Parser<'static> {
858    wrap_tokens(
859        Code::BeginMapping,
860        Code::EndMapping,
861        Box::new(move |state| {
862            let outer_c = state.c;
863            // Opening `{`.
864            let (open_tokens, after_open) = match token(Code::Indicator, char_parser('{'))(state) {
865                Reply::Success { tokens, state } => (tokens, state),
866                other @ (Reply::Failure | Reply::Error(_)) => return other,
867            };
868            let after_sep = skip_flow_sep(n, after_open);
869            let inner_c = inner_flow_context(c);
870            let inner_state = State {
871                c: inner_c,
872                ..after_sep
873            };
874            let (entries_tokens, after_entries) =
875                match ns_s_flow_map_entries(n, inner_c)(inner_state) {
876                    Reply::Success { tokens, state } => (tokens, state),
877                    Reply::Failure => return Reply::Failure,
878                    Reply::Error(e) => return Reply::Error(e),
879                };
880            let after_sep2 = skip_flow_sep(n, after_entries);
881            // Closing `}`.
882            match token(Code::Indicator, char_parser('}'))(after_sep2) {
883                Reply::Success {
884                    tokens: close_tokens,
885                    state: final_state,
886                } => {
887                    let mut all = open_tokens;
888                    all.extend(entries_tokens);
889                    all.extend(close_tokens);
890                    Reply::Success {
891                        tokens: all,
892                        state: State {
893                            c: outer_c,
894                            ..final_state
895                        },
896                    }
897                }
898                Reply::Failure | Reply::Error(_) => Reply::Failure,
899            }
900        }),
901    )
902}
903
904/// [142] ns-s-flow-map-entries(n,c) — comma-separated mapping entries (zero or more).
905fn ns_s_flow_map_entries(n: i32, c: Context) -> Parser<'static> {
906    Box::new(move |state| {
907        let (first_tokens, after_first) = match ns_flow_map_entry(n, c)(state.clone()) {
908            Reply::Success { tokens, state } => (tokens, state),
909            Reply::Error(e) => return Reply::Error(e),
910            Reply::Failure => {
911                return Reply::Success {
912                    tokens: Vec::new(),
913                    state,
914                };
915            }
916        };
917        let mut all_tokens = first_tokens;
918        let mut current = after_first;
919        loop {
920            let after_sep = skip_flow_sep(n, current.clone());
921            match token(Code::Indicator, char_parser(','))(after_sep) {
922                Reply::Error(e) => return Reply::Error(e),
923                Reply::Failure => break,
924                Reply::Success {
925                    tokens: comma_tokens,
926                    state: after_comma,
927                } => {
928                    let after_sep2 = skip_flow_sep(n, after_comma);
929                    let (entry_tokens, after_entry) =
930                        match ns_flow_map_entry(n, c)(after_sep2.clone()) {
931                            Reply::Success { tokens, state } => (tokens, state),
932                            Reply::Failure | Reply::Error(_) => (Vec::new(), after_sep2),
933                        };
934                    all_tokens.extend(comma_tokens);
935                    all_tokens.extend(entry_tokens);
936                    current = after_entry;
937                }
938            }
939        }
940        Reply::Success {
941            tokens: all_tokens,
942            state: current,
943        }
944    })
945}
946
947/// [143] ns-flow-map-entry(n,c) — explicit key (`?`) or implicit key entry.
948fn ns_flow_map_entry(n: i32, c: Context) -> Parser<'static> {
949    Box::new(move |state| {
950        let explicit = ns_flow_map_explicit_entry(n, c)(state.clone());
951        match explicit {
952            Reply::Success { .. } | Reply::Error(_) => explicit,
953            Reply::Failure => ns_flow_map_implicit_entry(n, c)(state),
954        }
955    })
956}
957
958/// [144] ns-flow-map-explicit-entry(n,c) — `? sep key (: sep value | empty)`.
959fn ns_flow_map_explicit_entry(n: i32, c: Context) -> Parser<'static> {
960    wrap_tokens(
961        Code::BeginPair,
962        Code::EndPair,
963        Box::new(move |state| {
964            let (q_tokens, after_q) = match token(Code::Indicator, char_parser('?'))(state) {
965                Reply::Success { tokens, state } => (tokens, state),
966                other @ (Reply::Failure | Reply::Error(_)) => return other,
967            };
968            let after_sep = skip_flow_sep(n, after_q);
969            // Key (optional — may be empty).
970            let (key_tokens, after_key) = match ns_flow_yaml_node(n, c)(after_sep.clone()) {
971                Reply::Success { tokens, state } => (tokens, state),
972                Reply::Failure | Reply::Error(_) => (Vec::new(), after_sep),
973            };
974            // Optional value.
975            let (value_tokens, final_state) = match c_ns_flow_map_value(n, c)(after_key.clone()) {
976                Reply::Success { tokens, state } => (tokens, state),
977                Reply::Failure | Reply::Error(_) => (Vec::new(), after_key),
978            };
979            let mut all = q_tokens;
980            all.extend(key_tokens);
981            all.extend(value_tokens);
982            Reply::Success {
983                tokens: all,
984                state: final_state,
985            }
986        }),
987    )
988}
989
990/// [145] ns-flow-map-implicit-entry(n,c) — YAML key entry or empty-key entry.
991fn ns_flow_map_implicit_entry(n: i32, c: Context) -> Parser<'static> {
992    Box::new(move |state| {
993        let yaml = ns_flow_map_yaml_key_entry(n, c)(state.clone());
994        match yaml {
995            Reply::Success { .. } | Reply::Error(_) => return yaml,
996            Reply::Failure => {}
997        }
998        let json = c_ns_flow_map_json_key_entry(n, c)(state.clone());
999        match json {
1000            Reply::Success { .. } | Reply::Error(_) => return json,
1001            Reply::Failure => {}
1002        }
1003        c_ns_flow_map_empty_key_entry(n, c)(state)
1004    })
1005}
1006
1007/// [147] c-ns-flow-map-json-key-entry(n,c) — JSON node key then optional value.
1008///
1009/// Per spec, the key uses the parent context `c` (not `FlowKey`), which
1010/// allows multiline quoted scalars as keys in flow collections.
1011fn c_ns_flow_map_json_key_entry(n: i32, c: Context) -> Parser<'static> {
1012    wrap_tokens(
1013        Code::BeginPair,
1014        Code::EndPair,
1015        Box::new(move |state| {
1016            // Key uses the parent context c per spec [147].
1017            let (key_tokens, after_key) = match c_flow_json_node(n, c)(state.clone()) {
1018                Reply::Success { tokens, state } => (tokens, state),
1019                other @ (Reply::Failure | Reply::Error(_)) => return other,
1020            };
1021            // Per spec [147]: optional `s-separate(n,c)?` then adjacent value,
1022            // or e-node. Skip optional separation (including whitespace/newlines)
1023            // before checking for `:`.
1024            let after_sep = skip_flow_sep(n, after_key.clone());
1025            let (value_tokens, final_state) =
1026                match c_ns_flow_map_adjacent_value(n, c)(after_sep.clone()) {
1027                    Reply::Success { tokens, state } => (tokens, state),
1028                    Reply::Failure | Reply::Error(_) => {
1029                        match c_ns_flow_map_separate_value(n, c)(after_sep.clone()) {
1030                            Reply::Success { tokens, state } => (tokens, state),
1031                            Reply::Failure | Reply::Error(_) => (Vec::new(), after_key),
1032                        }
1033                    }
1034                };
1035            let mut all = key_tokens;
1036            all.extend(value_tokens);
1037            Reply::Success {
1038                tokens: all,
1039                state: final_state,
1040            }
1041        }),
1042    )
1043}
1044
1045/// [146] ns-flow-map-yaml-key-entry(n,c) — YAML key then optional value.
1046///
1047/// The key uses the parent context `c` per spec [146], allowing multiline
1048/// plain scalars as mapping keys in flow contexts.
1049fn ns_flow_map_yaml_key_entry(n: i32, c: Context) -> Parser<'static> {
1050    wrap_tokens(
1051        Code::BeginPair,
1052        Code::EndPair,
1053        Box::new(move |state| {
1054            // Key uses parent context c for multiline support.
1055            let (key_tokens, after_key) = match ns_flow_yaml_node(n, c)(state.clone()) {
1056                Reply::Success { tokens, state } => (tokens, state),
1057                other @ (Reply::Failure | Reply::Error(_)) => return other,
1058            };
1059            // Optional separation before the value (cross-line allowed).
1060            let after_sep = skip_flow_sep(n, after_key.clone());
1061            let (value_tokens, final_state) =
1062                match c_ns_flow_map_separate_value(n, c)(after_sep.clone()) {
1063                    Reply::Success { tokens, state } => (tokens, state),
1064                    Reply::Failure | Reply::Error(_) => {
1065                        match c_ns_flow_map_adjacent_value(n, c)(after_key.clone()) {
1066                            Reply::Success { tokens, state } => (tokens, state),
1067                            Reply::Failure | Reply::Error(_) => (Vec::new(), after_key),
1068                        }
1069                    }
1070                };
1071            let mut all = key_tokens;
1072            all.extend(value_tokens);
1073            Reply::Success {
1074                tokens: all,
1075                state: final_state,
1076            }
1077        }),
1078    )
1079}
1080
1081/// [148] c-ns-flow-map-empty-key-entry(n,c) — empty key with `: value`.
1082fn c_ns_flow_map_empty_key_entry(n: i32, c: Context) -> Parser<'static> {
1083    wrap_tokens(
1084        Code::BeginPair,
1085        Code::EndPair,
1086        Box::new(move |state| {
1087            // Must begin with `:`.
1088            let Some(':') = state.peek() else {
1089                return Reply::Failure;
1090            };
1091            let colon_input = state.input;
1092            let colon_pos = state.pos;
1093            let after_colon = state.advance(':');
1094            let colon_token = Token {
1095                code: Code::Indicator,
1096                pos: colon_pos,
1097                text: &colon_input[..1],
1098            };
1099            let after_sep = skip_flow_sep(n, after_colon.clone());
1100            let (value_tokens, final_state) = match ns_flow_node(n, c)(after_sep.clone()) {
1101                Reply::Success { tokens, state } => (tokens, state),
1102                Reply::Failure | Reply::Error(_) => (Vec::new(), after_colon),
1103            };
1104            let mut all = vec![colon_token];
1105            all.extend(value_tokens);
1106            Reply::Success {
1107                tokens: all,
1108                state: final_state,
1109            }
1110        }),
1111    )
1112}
1113
1114/// [147] c-ns-flow-map-separate-value(n,c) — `: ` (colon + whitespace) then value.
1115///
1116/// Only matches when `:` is followed by whitespace or end-of-input — not
1117/// when it is immediately adjacent to the next token.
1118fn c_ns_flow_map_separate_value(n: i32, c: Context) -> Parser<'static> {
1119    Box::new(move |state| {
1120        let Some(':') = state.peek() else {
1121            return Reply::Failure;
1122        };
1123        let colon_input = state.input;
1124        let colon_pos = state.pos;
1125        let after_colon = state.advance(':');
1126        // Adjacent value: next char is a non-space ns-char → fall back to adjacent.
1127        match after_colon.peek() {
1128            None => {
1129                // Colon at EOF — emit indicator, no value.
1130                Reply::Success {
1131                    tokens: vec![Token {
1132                        code: Code::Indicator,
1133                        pos: colon_pos,
1134                        text: &colon_input[..1],
1135                    }],
1136                    state: after_colon,
1137                }
1138            }
1139            Some(',' | '}' | ']') => {
1140                // Flow terminator after `:` — emit indicator with empty (e-node) value.
1141                Reply::Success {
1142                    tokens: vec![Token {
1143                        code: Code::Indicator,
1144                        pos: colon_pos,
1145                        text: &colon_input[..1],
1146                    }],
1147                    state: after_colon,
1148                }
1149            }
1150            Some(nc) if !matches!(nc, ' ' | '\t' | '\n' | '\r') => {
1151                // Next char is not whitespace — this is adjacent syntax; reject here.
1152                Reply::Failure
1153            }
1154            _ => {
1155                let colon_token = Token {
1156                    code: Code::Indicator,
1157                    pos: colon_pos,
1158                    text: &colon_input[..1],
1159                };
1160                let after_sep = skip_flow_sep(n, after_colon.clone());
1161                let (value_tokens, final_state) = match ns_flow_node(n, c)(after_sep.clone()) {
1162                    Reply::Success { tokens, state } => (tokens, state),
1163                    Reply::Failure | Reply::Error(_) => (Vec::new(), after_colon),
1164                };
1165                let mut all = vec![colon_token];
1166                all.extend(value_tokens);
1167                Reply::Success {
1168                    tokens: all,
1169                    state: final_state,
1170                }
1171            }
1172        }
1173    })
1174}
1175
1176/// [150] c-ns-flow-map-adjacent-value(n,c) — `:`value with no space.
1177fn c_ns_flow_map_adjacent_value(n: i32, c: Context) -> Parser<'static> {
1178    Box::new(move |state| {
1179        let Some(':') = state.peek() else {
1180            return Reply::Failure;
1181        };
1182        let colon_input = state.input;
1183        let colon_pos = state.pos;
1184        let after_colon = state.advance(':');
1185        let colon_token = Token {
1186            code: Code::Indicator,
1187            pos: colon_pos,
1188            text: &colon_input[..1],
1189        };
1190        // Optional separation then value, or empty node.
1191        let after_sep = skip_flow_sep(n, after_colon.clone());
1192        let (value_tokens, final_state) = match ns_flow_node(n, c)(after_sep.clone()) {
1193            Reply::Success { tokens, state } => (tokens, state),
1194            Reply::Failure | Reply::Error(_) => (Vec::new(), after_colon),
1195        };
1196        let mut all = vec![colon_token];
1197        all.extend(value_tokens);
1198        Reply::Success {
1199            tokens: all,
1200            state: final_state,
1201        }
1202    })
1203}
1204
1205/// [149] c-ns-flow-map-value(n,c) — optional sep then `: value`.
1206fn c_ns_flow_map_value(n: i32, c: Context) -> Parser<'static> {
1207    Box::new(move |state| {
1208        let after_sep = skip_flow_sep(n, state);
1209        c_ns_flow_map_separate_value(n, c)(after_sep)
1210    })
1211}
1212
1213// ---------------------------------------------------------------------------
1214// §7.6 – Flow nodes [154]–[161]
1215// ---------------------------------------------------------------------------
1216
1217/// [154] ns-flow-yaml-content(n,c) — plain scalar as YAML flow content.
1218fn ns_flow_yaml_content(n: i32, c: Context) -> Parser<'static> {
1219    ns_plain(n, c)
1220}
1221
1222/// [155] c-flow-json-content(n,c) — quoted scalar or flow collection as JSON content.
1223fn c_flow_json_content(n: i32, c: Context) -> Parser<'static> {
1224    Box::new(move |state| {
1225        let dq = c_double_quoted(n, c)(state.clone());
1226        if matches!(dq, Reply::Success { .. } | Reply::Error(_)) {
1227            return dq;
1228        }
1229        let sq = c_single_quoted(n, c)(state.clone());
1230        if matches!(sq, Reply::Success { .. } | Reply::Error(_)) {
1231            return sq;
1232        }
1233        let seq_ = c_flow_sequence(n, c)(state.clone());
1234        if matches!(seq_, Reply::Success { .. } | Reply::Error(_)) {
1235            return seq_;
1236        }
1237        c_flow_mapping(n, c)(state)
1238    })
1239}
1240
1241/// [156] ns-flow-content(n,c) — YAML or JSON content.
1242fn ns_flow_content(n: i32, c: Context) -> Parser<'static> {
1243    Box::new(move |state| {
1244        let yaml = ns_flow_yaml_content(n, c)(state.clone());
1245        match yaml {
1246            Reply::Success { .. } | Reply::Error(_) => yaml,
1247            Reply::Failure => c_flow_json_content(n, c)(state),
1248        }
1249    })
1250}
1251
1252/// [157] ns-flow-yaml-node(n,c) — alias, or properties + optional content.
1253#[must_use]
1254pub fn ns_flow_yaml_node(n: i32, c: Context) -> Parser<'static> {
1255    Box::new(move |state| {
1256        // Alias takes priority.
1257        let alias = c_ns_alias_node()(state.clone());
1258        if let Reply::Success { .. } = alias {
1259            return alias;
1260        }
1261        // Properties then optional content.
1262        match c_ns_properties(n, c)(state.clone()) {
1263            Reply::Success {
1264                tokens: props_tokens,
1265                state: after_props,
1266            } => {
1267                // Optional sep + content.
1268                let content_state = match s_separate(n, c)(after_props.clone()) {
1269                    Reply::Success { state, .. } => state,
1270                    Reply::Failure | Reply::Error(_) => after_props.clone(),
1271                };
1272                let (content_tokens, final_state) =
1273                    match ns_flow_content(n, c)(content_state.clone()) {
1274                        Reply::Success { tokens, state } => (tokens, state),
1275                        Reply::Failure | Reply::Error(_) => (Vec::new(), after_props),
1276                    };
1277                let mut all = props_tokens;
1278                all.extend(content_tokens);
1279                Reply::Success {
1280                    tokens: all,
1281                    state: final_state,
1282                }
1283            }
1284            Reply::Failure | Reply::Error(_) => ns_flow_yaml_content(n, c)(state),
1285        }
1286    })
1287}
1288
1289/// [158] c-flow-json-node(n,c) — optional properties then JSON content.
1290#[must_use]
1291pub fn c_flow_json_node(n: i32, c: Context) -> Parser<'static> {
1292    Box::new(move |state| {
1293        // Try properties + sep first, then content.
1294        let (props_tokens, content_state) = match c_ns_properties(n, c)(state.clone()) {
1295            Reply::Success {
1296                tokens,
1297                state: after_props,
1298            } => {
1299                let sep_state = match s_separate(n, c)(after_props.clone()) {
1300                    Reply::Success { state, .. } => state,
1301                    Reply::Failure | Reply::Error(_) => after_props,
1302                };
1303                (tokens, sep_state)
1304            }
1305            Reply::Failure | Reply::Error(_) => (Vec::new(), state),
1306        };
1307        match c_flow_json_content(n, c)(content_state) {
1308            Reply::Success {
1309                tokens: content_tokens,
1310                state: final_state,
1311            } => {
1312                let mut all = props_tokens;
1313                all.extend(content_tokens);
1314                Reply::Success {
1315                    tokens: all,
1316                    state: final_state,
1317                }
1318            }
1319            other @ (Reply::Failure | Reply::Error(_)) => other,
1320        }
1321    })
1322}
1323
1324/// [159] ns-flow-node(n,c) — YAML node or JSON node.
1325#[must_use]
1326pub fn ns_flow_node(n: i32, c: Context) -> Parser<'static> {
1327    Box::new(move |state| {
1328        let yaml = ns_flow_yaml_node(n, c)(state.clone());
1329        match yaml {
1330            Reply::Success { .. } | Reply::Error(_) => yaml,
1331            Reply::Failure => c_flow_json_node(n, c)(state),
1332        }
1333    })
1334}
1335
1336/// [160] c-ns-flow-pair(n,c) — a key:value pair in a flow context.
1337///
1338/// Emits `BeginPair` / key / `:` / value / `EndPair`.
1339#[must_use]
1340pub fn c_ns_flow_pair(n: i32, c: Context) -> Parser<'static> {
1341    Box::new(move |state| {
1342        let explicit = ns_flow_map_explicit_entry(n, c)(state.clone());
1343        match explicit {
1344            Reply::Success { .. } | Reply::Error(_) => return explicit,
1345            Reply::Failure => {}
1346        }
1347        // Per spec [160]: implicit pairs in sequences use ns-s-implicit-yaml-key
1348        // [154] which restricts YAML keys to FlowKey context (single-line).
1349        // Flow mapping entries use parent context via ns_flow_map_implicit_entry.
1350        // Here we use FlowKey for the YAML key to prevent `key\n:value` in sequences.
1351        let yaml_key = wrap_tokens(
1352            Code::BeginPair,
1353            Code::EndPair,
1354            Box::new(move |s: State<'static>| {
1355                let (key_tokens, after_key) =
1356                    match ns_flow_yaml_node(n, Context::FlowKey)(s.clone()) {
1357                        Reply::Success { tokens, state } => (tokens, state),
1358                        Reply::Failure | Reply::Error(_) => return Reply::Failure,
1359                    };
1360                // Per spec [154]: s-separate-in-line? after key.
1361                let after_sep = match crate::structure::s_separate_in_line()(after_key.clone()) {
1362                    Reply::Success { state, .. } => state,
1363                    Reply::Failure | Reply::Error(_) => after_key.clone(),
1364                };
1365                let (value_tokens, final_state) =
1366                    match c_ns_flow_map_separate_value(n, c)(after_sep.clone()) {
1367                        Reply::Success { tokens, state } => (tokens, state),
1368                        Reply::Failure | Reply::Error(_) => {
1369                            match c_ns_flow_map_adjacent_value(n, c)(after_sep.clone()) {
1370                                Reply::Success { tokens, state } => (tokens, state),
1371                                Reply::Failure | Reply::Error(_) => (Vec::new(), after_key),
1372                            }
1373                        }
1374                    };
1375                let mut all = key_tokens;
1376                all.extend(value_tokens);
1377                Reply::Success {
1378                    tokens: all,
1379                    state: final_state,
1380                }
1381            }),
1382        )(state.clone());
1383        match yaml_key {
1384            Reply::Success { .. } | Reply::Error(_) => return yaml_key,
1385            Reply::Failure => {}
1386        }
1387        // JSON key and empty key entries.
1388        let json = c_ns_flow_map_json_key_entry(n, c)(state.clone());
1389        match json {
1390            Reply::Success { .. } | Reply::Error(_) => return json,
1391            Reply::Failure => {}
1392        }
1393        c_ns_flow_map_empty_key_entry(n, c)(state)
1394    })
1395}
1396
1397// ---------------------------------------------------------------------------
1398// Helpers
1399// ---------------------------------------------------------------------------
1400
1401/// Skip optional separation whitespace (including newlines) in flow context.
1402///
1403/// Uses `s_separate_ge` which allows continuation lines with >= n spaces
1404/// of indentation, matching the flow context requirement that deeper
1405/// indentation is permitted inside flow collections.
1406fn skip_flow_sep(n: i32, state: State<'static>) -> State<'static> {
1407    match s_separate_ge(n, state.c)(state.clone()) {
1408        Reply::Success { state, .. } => state,
1409        Reply::Failure | Reply::Error(_) => state,
1410    }
1411}
1412
1413/// Determine the inner context for a flow collection.
1414const fn inner_flow_context(c: Context) -> Context {
1415    match c {
1416        Context::FlowOut | Context::FlowIn | Context::BlockOut | Context::BlockIn => {
1417            Context::FlowIn
1418        }
1419        Context::BlockKey | Context::FlowKey => Context::FlowKey,
1420    }
1421}
1422
1423// ---------------------------------------------------------------------------
1424// Tests
1425// ---------------------------------------------------------------------------
1426
1427#[cfg(test)]
1428#[allow(
1429    clippy::indexing_slicing,
1430    clippy::expect_used,
1431    clippy::unwrap_used,
1432    unused_imports
1433)]
1434mod tests {
1435    use super::*;
1436    use crate::combinator::{Reply, State};
1437    use crate::token::Code;
1438
1439    fn state(input: &str) -> State<'_> {
1440        State::new(input)
1441    }
1442
1443    fn is_success(reply: &Reply<'_>) -> bool {
1444        matches!(reply, Reply::Success { .. })
1445    }
1446
1447    fn is_failure(reply: &Reply<'_>) -> bool {
1448        matches!(reply, Reply::Failure)
1449    }
1450
1451    fn remaining<'a>(reply: &'a Reply<'a>) -> &'a str {
1452        match reply {
1453            Reply::Success { state, .. } => state.input,
1454            Reply::Failure | Reply::Error(_) => panic!("expected success"),
1455        }
1456    }
1457
1458    fn codes(reply: Reply<'_>) -> Vec<Code> {
1459        match reply {
1460            Reply::Success { tokens, .. } => tokens.into_iter().map(|t| t.code).collect(),
1461            Reply::Failure | Reply::Error(_) => panic!("expected success"),
1462        }
1463    }
1464
1465    // -----------------------------------------------------------------------
1466    // Group 1: Alias nodes [104] (8 tests)
1467    // -----------------------------------------------------------------------
1468
1469    #[test]
1470    fn c_ns_alias_node_emits_begin_alias() {
1471        let c = codes(c_ns_alias_node()(state("*myanchor rest")));
1472        assert_eq!(c.first().copied(), Some(Code::BeginAlias));
1473    }
1474
1475    #[test]
1476    fn c_ns_alias_node_emits_end_alias() {
1477        let c = codes(c_ns_alias_node()(state("*myanchor rest")));
1478        assert_eq!(c.last().copied(), Some(Code::EndAlias));
1479    }
1480
1481    #[test]
1482    fn c_ns_alias_node_emits_indicator_for_asterisk() {
1483        let c = codes(c_ns_alias_node()(state("*myanchor rest")));
1484        assert!(c.contains(&Code::Indicator));
1485    }
1486
1487    #[test]
1488    fn c_ns_alias_node_emits_meta_for_name() {
1489        let c = codes(c_ns_alias_node()(state("*myanchor rest")));
1490        assert!(c.contains(&Code::Meta));
1491    }
1492
1493    #[test]
1494    fn c_ns_alias_node_stops_before_space() {
1495        let reply = c_ns_alias_node()(state("*myanchor rest"));
1496        assert_eq!(remaining(&reply), " rest");
1497    }
1498
1499    #[test]
1500    fn c_ns_alias_node_stops_at_flow_indicator() {
1501        let reply = c_ns_alias_node()(state("*name]rest"));
1502        assert!(is_success(&reply));
1503        assert_eq!(remaining(&reply), "]rest");
1504    }
1505
1506    #[test]
1507    fn c_ns_alias_node_fails_without_asterisk() {
1508        let reply = c_ns_alias_node()(state("myanchor"));
1509        assert!(is_failure(&reply));
1510    }
1511
1512    #[test]
1513    fn c_ns_alias_node_fails_with_empty_name() {
1514        let reply = c_ns_alias_node()(state("* rest"));
1515        assert!(is_failure(&reply));
1516    }
1517
1518    // -----------------------------------------------------------------------
1519    // Group 2: Empty nodes [105]–[106] (4 tests)
1520    // -----------------------------------------------------------------------
1521
1522    #[test]
1523    fn e_scalar_succeeds_with_zero_consumption() {
1524        let reply = e_scalar()(state("rest"));
1525        assert!(is_success(&reply));
1526        assert_eq!(remaining(&reply), "rest");
1527    }
1528
1529    #[test]
1530    fn e_scalar_succeeds_on_empty_input() {
1531        let reply = e_scalar()(state(""));
1532        assert!(is_success(&reply));
1533    }
1534
1535    #[test]
1536    fn e_node_succeeds_with_zero_consumption() {
1537        let reply = e_node()(state("rest"));
1538        assert!(is_success(&reply));
1539        assert_eq!(remaining(&reply), "rest");
1540    }
1541
1542    #[test]
1543    fn e_node_succeeds_on_empty_input() {
1544        let reply = e_node()(state(""));
1545        assert!(is_success(&reply));
1546    }
1547
1548    // -----------------------------------------------------------------------
1549    // Group 3: Double-quoted scalars [107]–[113] (22 tests)
1550    // -----------------------------------------------------------------------
1551
1552    #[test]
1553    fn c_double_quoted_emits_begin_scalar() {
1554        let c = codes(c_double_quoted(0, Context::FlowOut)(state(
1555            "\"hello\" rest",
1556        )));
1557        assert_eq!(c.first().copied(), Some(Code::BeginScalar));
1558    }
1559
1560    #[test]
1561    fn c_double_quoted_emits_end_scalar() {
1562        let c = codes(c_double_quoted(0, Context::FlowOut)(state(
1563            "\"hello\" rest",
1564        )));
1565        assert_eq!(c.last().copied(), Some(Code::EndScalar));
1566    }
1567
1568    #[test]
1569    fn c_double_quoted_emits_text_for_content() {
1570        let c = codes(c_double_quoted(0, Context::FlowOut)(state(
1571            "\"hello\" rest",
1572        )));
1573        assert!(c.contains(&Code::Text));
1574    }
1575
1576    #[test]
1577    fn c_double_quoted_consumes_through_closing_quote() {
1578        let reply = c_double_quoted(0, Context::FlowOut)(state("\"hello\" rest"));
1579        assert_eq!(remaining(&reply), " rest");
1580    }
1581
1582    #[test]
1583    fn c_double_quoted_accepts_empty_string() {
1584        let reply = c_double_quoted(0, Context::FlowOut)(state("\"\" rest"));
1585        assert!(is_success(&reply));
1586        let c = codes(c_double_quoted(0, Context::FlowOut)(state("\"\" rest")));
1587        assert!(c.contains(&Code::BeginScalar));
1588        assert!(c.contains(&Code::EndScalar));
1589    }
1590
1591    #[test]
1592    fn c_double_quoted_fails_without_opening_quote() {
1593        let reply = c_double_quoted(0, Context::FlowOut)(state("hello\""));
1594        assert!(is_failure(&reply));
1595    }
1596
1597    #[test]
1598    fn c_double_quoted_fails_without_closing_quote() {
1599        let reply = c_double_quoted(0, Context::FlowOut)(state("\"hello"));
1600        assert!(is_failure(&reply));
1601    }
1602
1603    #[test]
1604    fn c_double_quoted_handles_escape_newline() {
1605        let reply = c_double_quoted(0, Context::FlowOut)(state("\"hello\\nworld\""));
1606        assert!(is_success(&reply));
1607        // The escape produces a Text token covering the `\n` bytes.
1608        let c = codes(c_double_quoted(0, Context::FlowOut)(state(
1609            "\"hello\\nworld\"",
1610        )));
1611        assert!(c.contains(&Code::Text));
1612    }
1613
1614    #[test]
1615    fn c_double_quoted_handles_escape_tab() {
1616        let reply = c_double_quoted(0, Context::FlowOut)(state("\"\\t\""));
1617        assert!(is_success(&reply));
1618        let c = codes(c_double_quoted(0, Context::FlowOut)(state("\"\\t\"")));
1619        // nb_double_char emits a Text token for the escape sequence.
1620        assert!(c.contains(&Code::Text));
1621    }
1622
1623    #[test]
1624    fn c_double_quoted_handles_escape_backslash() {
1625        let reply = c_double_quoted(0, Context::FlowOut)(state("\"\\\\ \""));
1626        assert!(is_success(&reply));
1627        let c = codes(c_double_quoted(0, Context::FlowOut)(state("\"\\\\ \"")));
1628        assert!(c.contains(&Code::Text));
1629    }
1630
1631    #[test]
1632    fn c_double_quoted_handles_escape_double_quote() {
1633        let reply = c_double_quoted(0, Context::FlowOut)(state("\"\\\"\""));
1634        assert!(is_success(&reply));
1635    }
1636
1637    #[test]
1638    fn c_double_quoted_handles_unicode_escape() {
1639        let reply = c_double_quoted(0, Context::FlowOut)(state("\"\\u0041\""));
1640        assert!(is_success(&reply));
1641        let c = codes(c_double_quoted(0, Context::FlowOut)(state("\"\\u0041\"")));
1642        assert!(c.contains(&Code::Text));
1643    }
1644
1645    #[test]
1646    fn c_double_quoted_rejects_invalid_escape() {
1647        let reply = c_double_quoted(0, Context::FlowOut)(state("\"\\z\""));
1648        assert!(is_failure(&reply));
1649    }
1650
1651    #[test]
1652    fn c_double_quoted_accepts_multiline() {
1653        let reply = c_double_quoted(0, Context::FlowOut)(state("\"line1\nline2\""));
1654        assert!(is_success(&reply));
1655        assert_eq!(remaining(&reply), "");
1656    }
1657
1658    #[test]
1659    fn nb_double_char_accepts_regular_char() {
1660        let reply = nb_double_char()(state("a"));
1661        assert!(is_success(&reply));
1662    }
1663
1664    #[test]
1665    fn nb_double_char_accepts_escape_sequence() {
1666        let reply = nb_double_char()(state("\\n"));
1667        assert!(is_success(&reply));
1668        // Emits a Text token for the escape.
1669        let c = codes(nb_double_char()(state("\\n")));
1670        assert!(c.contains(&Code::Text));
1671    }
1672
1673    #[test]
1674    fn nb_double_char_fails_on_bare_double_quote() {
1675        let reply = nb_double_char()(state("\""));
1676        assert!(is_failure(&reply));
1677    }
1678
1679    #[test]
1680    fn ns_double_char_fails_on_space() {
1681        let reply = ns_double_char()(state(" a"));
1682        assert!(is_failure(&reply));
1683    }
1684
1685    #[test]
1686    fn ns_double_char_accepts_regular_non_space_char() {
1687        let reply = ns_double_char()(state("a"));
1688        assert!(is_success(&reply));
1689    }
1690
1691    #[test]
1692    fn nb_double_char_fails_on_backslash_alone() {
1693        let reply = nb_double_char()(state("\\"));
1694        assert!(is_failure(&reply));
1695    }
1696
1697    #[test]
1698    fn c_double_quoted_handles_folded_line() {
1699        let reply = c_double_quoted(0, Context::FlowOut)(state("\"word1\n  word2\""));
1700        assert!(is_success(&reply));
1701    }
1702
1703    #[test]
1704    fn c_double_quoted_handles_trimmed_blank_lines() {
1705        let reply = c_double_quoted(0, Context::FlowOut)(state("\"word1\n\n  word2\""));
1706        assert!(is_success(&reply));
1707    }
1708
1709    // -----------------------------------------------------------------------
1710    // Group 4: Single-quoted scalars [114]–[121] (14 tests)
1711    // -----------------------------------------------------------------------
1712
1713    #[test]
1714    fn c_single_quoted_emits_begin_scalar() {
1715        let c = codes(c_single_quoted(0, Context::FlowOut)(state("'hello' rest")));
1716        assert_eq!(c.first().copied(), Some(Code::BeginScalar));
1717    }
1718
1719    #[test]
1720    fn c_single_quoted_emits_end_scalar() {
1721        let c = codes(c_single_quoted(0, Context::FlowOut)(state("'hello' rest")));
1722        assert_eq!(c.last().copied(), Some(Code::EndScalar));
1723    }
1724
1725    #[test]
1726    fn c_single_quoted_emits_text_for_content() {
1727        let c = codes(c_single_quoted(0, Context::FlowOut)(state("'hello' rest")));
1728        assert!(c.contains(&Code::Text));
1729    }
1730
1731    #[test]
1732    fn c_single_quoted_consumes_through_closing_quote() {
1733        let reply = c_single_quoted(0, Context::FlowOut)(state("'hello' rest"));
1734        assert_eq!(remaining(&reply), " rest");
1735    }
1736
1737    #[test]
1738    fn c_single_quoted_accepts_empty_string() {
1739        let reply = c_single_quoted(0, Context::FlowOut)(state("'' rest"));
1740        assert!(is_success(&reply));
1741        let c = codes(c_single_quoted(0, Context::FlowOut)(state("'' rest")));
1742        assert!(c.contains(&Code::BeginScalar));
1743        assert!(c.contains(&Code::EndScalar));
1744    }
1745
1746    #[test]
1747    fn c_single_quoted_fails_without_opening_quote() {
1748        let reply = c_single_quoted(0, Context::FlowOut)(state("hello'"));
1749        assert!(is_failure(&reply));
1750    }
1751
1752    #[test]
1753    fn c_single_quoted_fails_without_closing_quote() {
1754        let reply = c_single_quoted(0, Context::FlowOut)(state("'hello"));
1755        assert!(is_failure(&reply));
1756    }
1757
1758    #[test]
1759    fn c_single_quoted_handles_escaped_single_quote() {
1760        let reply = c_single_quoted(0, Context::FlowOut)(state("'it''s fine'"));
1761        assert!(is_success(&reply));
1762        assert_eq!(remaining(&reply), "");
1763    }
1764
1765    #[test]
1766    fn c_quoted_quote_accepts_doubled_single_quote() {
1767        let reply = c_quoted_quote()(state("''rest"));
1768        assert!(is_success(&reply));
1769        assert_eq!(remaining(&reply), "rest");
1770    }
1771
1772    #[test]
1773    fn c_quoted_quote_fails_on_single_lone_quote() {
1774        let reply = c_quoted_quote()(state("'rest"));
1775        assert!(is_failure(&reply));
1776    }
1777
1778    #[test]
1779    fn nb_single_char_accepts_regular_char() {
1780        let reply = nb_single_char()(state("a"));
1781        assert!(is_success(&reply));
1782    }
1783
1784    #[test]
1785    fn nb_single_char_fails_on_bare_single_quote() {
1786        let reply = nb_single_char()(state("'rest"));
1787        assert!(is_failure(&reply));
1788    }
1789
1790    #[test]
1791    fn ns_single_char_fails_on_space() {
1792        let reply = ns_single_char()(state(" a"));
1793        assert!(is_failure(&reply));
1794    }
1795
1796    #[test]
1797    fn c_single_quoted_accepts_multiline() {
1798        let reply = c_single_quoted(0, Context::FlowOut)(state("'line1\nline2'"));
1799        assert!(is_success(&reply));
1800    }
1801
1802    // -----------------------------------------------------------------------
1803    // Group 5: Plain scalars [122]–[135] (22 tests)
1804    // -----------------------------------------------------------------------
1805
1806    #[test]
1807    fn ns_plain_first_accepts_regular_char_in_block_context() {
1808        let reply = crate::chars::ns_plain_first(Context::BlockOut)(state("hello"));
1809        assert!(is_success(&reply));
1810    }
1811
1812    #[test]
1813    fn ns_plain_first_accepts_question_when_followed_by_safe_char() {
1814        let reply = crate::chars::ns_plain_first(Context::FlowOut)(state("?value"));
1815        assert!(is_success(&reply));
1816    }
1817
1818    #[test]
1819    fn ns_plain_first_accepts_colon_when_followed_by_safe_char() {
1820        let reply = crate::chars::ns_plain_first(Context::FlowOut)(state(":value"));
1821        assert!(is_success(&reply));
1822    }
1823
1824    #[test]
1825    fn ns_plain_first_accepts_hyphen_when_followed_by_safe_char() {
1826        let reply = crate::chars::ns_plain_first(Context::FlowOut)(state("-value"));
1827        assert!(is_success(&reply));
1828    }
1829
1830    #[test]
1831    fn ns_plain_first_rejects_indicator_not_followed_by_safe_char() {
1832        let reply = crate::chars::ns_plain_first(Context::FlowOut)(state(": "));
1833        assert!(is_failure(&reply));
1834    }
1835
1836    #[test]
1837    fn ns_plain_char_accepts_hash_inside_scalar() {
1838        let reply = crate::chars::ns_plain_char(Context::BlockOut)(state("#rest"));
1839        assert!(is_success(&reply));
1840    }
1841
1842    #[test]
1843    fn ns_plain_char_accepts_colon_when_followed_by_safe_char() {
1844        let reply = crate::chars::ns_plain_char(Context::BlockOut)(state(":x"));
1845        assert!(is_success(&reply));
1846    }
1847
1848    #[test]
1849    fn ns_plain_char_rejects_colon_at_end_of_input() {
1850        let reply = crate::chars::ns_plain_char(Context::BlockOut)(state(":"));
1851        assert!(is_failure(&reply));
1852    }
1853
1854    #[test]
1855    fn ns_plain_accepts_simple_word_in_block_context() {
1856        // nb-ns-plain-in-line [132] allows s-white* before each ns-plain-char,
1857        // so "hello rest" is one scalar — interior spaces are consumed.
1858        let reply = ns_plain(0, Context::BlockOut)(state("hello"));
1859        assert!(is_success(&reply));
1860        assert_eq!(remaining(&reply), "");
1861    }
1862
1863    #[test]
1864    fn ns_plain_accepts_simple_word_in_flow_context() {
1865        let reply = ns_plain(0, Context::FlowOut)(state("hello"));
1866        assert!(is_success(&reply));
1867        assert_eq!(remaining(&reply), "");
1868    }
1869
1870    #[test]
1871    fn ns_plain_stops_at_newline_in_single_line_context() {
1872        // nb-ns-plain-in-line [132] uses nb- chars — no line break — so a
1873        // newline terminates the inline portion.  With n=0 the continuation
1874        // line is also valid, so the full "hello\nrest" is consumed and
1875        // success is returned with empty remaining.
1876        let reply = ns_plain(0, Context::BlockOut)(state("hello\nrest"));
1877        assert!(is_success(&reply));
1878        assert_eq!(remaining(&reply), "");
1879    }
1880
1881    #[test]
1882    fn ns_plain_stops_at_flow_indicator_in_flow_context() {
1883        let reply = ns_plain(0, Context::FlowIn)(state("hello]rest"));
1884        assert!(is_success(&reply));
1885        assert_eq!(remaining(&reply), "]rest");
1886    }
1887
1888    #[test]
1889    fn ns_plain_allows_flow_indicator_in_block_context() {
1890        let reply = ns_plain(0, Context::BlockOut)(state("hello]rest"));
1891        assert!(is_success(&reply));
1892        assert_eq!(remaining(&reply), "");
1893    }
1894
1895    #[test]
1896    fn ns_plain_stops_at_colon_space_boundary() {
1897        let reply = ns_plain(0, Context::FlowOut)(state("key: value"));
1898        assert!(is_success(&reply));
1899        assert_eq!(remaining(&reply), ": value");
1900    }
1901
1902    #[test]
1903    fn ns_plain_stops_at_comma_in_flow_context() {
1904        let reply = ns_plain(0, Context::FlowIn)(state("hello,world"));
1905        assert!(is_success(&reply));
1906        assert_eq!(remaining(&reply), ",world");
1907    }
1908
1909    #[test]
1910    fn ns_plain_fails_when_starting_with_indicator() {
1911        let reply = ns_plain(0, Context::FlowOut)(state(",value"));
1912        assert!(is_failure(&reply));
1913    }
1914
1915    #[test]
1916    fn ns_plain_fails_when_starting_with_hash() {
1917        let reply = ns_plain(0, Context::BlockOut)(state("#comment"));
1918        assert!(is_failure(&reply));
1919    }
1920
1921    #[test]
1922    fn ns_plain_multi_line_continues_after_newline() {
1923        let reply = ns_plain(0, Context::BlockOut)(state("word1\n  word2 rest"));
1924        assert!(is_success(&reply));
1925    }
1926
1927    #[test]
1928    fn ns_plain_multi_line_stops_when_continuation_indent_too_low() {
1929        // n=2, word2 is at column 0 — continuation should not be consumed.
1930        let reply = ns_plain(2, Context::BlockOut)(state("word1\nword2 rest"));
1931        assert!(is_success(&reply));
1932        let rem = remaining(&reply);
1933        assert!(rem.contains("word2"));
1934    }
1935
1936    #[test]
1937    fn ns_plain_emits_begin_scalar() {
1938        let c = codes(ns_plain(0, Context::BlockOut)(state("hello")));
1939        assert_eq!(c.first().copied(), Some(Code::BeginScalar));
1940    }
1941
1942    #[test]
1943    fn ns_plain_emits_end_scalar() {
1944        let c = codes(ns_plain(0, Context::BlockOut)(state("hello")));
1945        assert_eq!(c.last().copied(), Some(Code::EndScalar));
1946    }
1947
1948    #[test]
1949    fn ns_plain_emits_text_for_content() {
1950        let c = codes(ns_plain(0, Context::BlockOut)(state("hello world")));
1951        assert!(c.contains(&Code::Text));
1952    }
1953
1954    #[test]
1955    fn ns_plain_safe_in_flow_rejects_flow_indicators() {
1956        let reply = crate::chars::ns_plain_safe(Context::FlowIn)(state(","));
1957        assert!(is_failure(&reply));
1958    }
1959
1960    // -----------------------------------------------------------------------
1961    // Group 6: Flow sequences [136]–[140] (22 tests)
1962    // -----------------------------------------------------------------------
1963
1964    // Spike test: validates BeginSequence/EndSequence wrapping.
1965    #[test]
1966    fn c_flow_sequence_emits_begin_sequence() {
1967        let c = codes(c_flow_sequence(0, Context::FlowOut)(state("[a, b] rest")));
1968        assert_eq!(c.first().copied(), Some(Code::BeginSequence));
1969    }
1970
1971    #[test]
1972    fn c_flow_sequence_emits_end_sequence() {
1973        let c = codes(c_flow_sequence(0, Context::FlowOut)(state("[a, b] rest")));
1974        assert_eq!(c.last().copied(), Some(Code::EndSequence));
1975    }
1976
1977    #[test]
1978    fn c_flow_sequence_accepts_empty_sequence() {
1979        let reply = c_flow_sequence(0, Context::FlowOut)(state("[] rest"));
1980        assert!(is_success(&reply));
1981        let c = codes(c_flow_sequence(0, Context::FlowOut)(state("[] rest")));
1982        assert!(c.contains(&Code::BeginSequence));
1983        assert!(c.contains(&Code::EndSequence));
1984        assert_eq!(remaining(&reply), " rest");
1985    }
1986
1987    #[test]
1988    fn c_flow_sequence_accepts_single_entry() {
1989        let reply = c_flow_sequence(0, Context::FlowOut)(state("[hello] rest"));
1990        assert!(is_success(&reply));
1991        let c = codes(c_flow_sequence(0, Context::FlowOut)(state("[hello] rest")));
1992        assert!(c.contains(&Code::BeginScalar));
1993    }
1994
1995    #[test]
1996    fn c_flow_sequence_accepts_two_entries_with_comma() {
1997        let reply = c_flow_sequence(0, Context::FlowOut)(state("[a, b] rest"));
1998        assert!(is_success(&reply));
1999        assert_eq!(remaining(&reply), " rest");
2000    }
2001
2002    #[test]
2003    fn c_flow_sequence_accepts_trailing_comma() {
2004        let reply = c_flow_sequence(0, Context::FlowOut)(state("[a,] rest"));
2005        assert!(is_success(&reply));
2006    }
2007
2008    #[test]
2009    fn c_flow_sequence_fails_without_opening_bracket() {
2010        let reply = c_flow_sequence(0, Context::FlowOut)(state("a, b]"));
2011        assert!(is_failure(&reply));
2012    }
2013
2014    #[test]
2015    fn c_flow_sequence_fails_without_closing_bracket() {
2016        let reply = c_flow_sequence(0, Context::FlowOut)(state("[a, b"));
2017        assert!(is_failure(&reply));
2018    }
2019
2020    #[test]
2021    fn c_flow_sequence_accepts_nested_sequence() {
2022        let reply = c_flow_sequence(0, Context::FlowOut)(state("[[1,2],[3,4]] rest"));
2023        assert!(is_success(&reply));
2024        assert_eq!(remaining(&reply), " rest");
2025    }
2026
2027    #[test]
2028    fn c_flow_sequence_accepts_nested_mapping() {
2029        let reply = c_flow_sequence(0, Context::FlowOut)(state("[{a: b}] rest"));
2030        assert!(is_success(&reply));
2031    }
2032
2033    #[test]
2034    fn c_flow_sequence_accepts_double_quoted_entry() {
2035        let reply = c_flow_sequence(0, Context::FlowOut)(state("[\"hello\"] rest"));
2036        assert!(is_success(&reply));
2037        let c = codes(c_flow_sequence(0, Context::FlowOut)(state(
2038            "[\"hello\"] rest",
2039        )));
2040        assert!(c.contains(&Code::BeginScalar));
2041    }
2042
2043    #[test]
2044    fn c_flow_sequence_accepts_single_quoted_entry() {
2045        let reply = c_flow_sequence(0, Context::FlowOut)(state("['hello'] rest"));
2046        assert!(is_success(&reply));
2047    }
2048
2049    #[test]
2050    fn c_flow_sequence_entry_emits_begin_pair_for_explicit_key() {
2051        let reply = c_flow_sequence(0, Context::FlowOut)(state("[? key: val] rest"));
2052        assert!(is_success(&reply));
2053        let c = codes(c_flow_sequence(0, Context::FlowOut)(state(
2054            "[? key: val] rest",
2055        )));
2056        assert!(c.contains(&Code::BeginPair));
2057    }
2058
2059    #[test]
2060    fn c_flow_sequence_accepts_alias_node_entry() {
2061        let reply = c_flow_sequence(0, Context::FlowOut)(state("[*anchor] rest"));
2062        assert!(is_success(&reply));
2063        let c = codes(c_flow_sequence(0, Context::FlowOut)(state(
2064            "[*anchor] rest",
2065        )));
2066        assert!(c.contains(&Code::BeginAlias));
2067    }
2068
2069    #[test]
2070    fn c_flow_sequence_rejects_leading_comma() {
2071        let reply = c_flow_sequence(0, Context::FlowOut)(state("[ , ] rest"));
2072        assert!(is_failure(&reply));
2073    }
2074
2075    #[test]
2076    fn c_flow_sequence_inner_context_is_flow_in() {
2077        let reply = c_flow_sequence(0, Context::FlowOut)(state("[a,b,c]"));
2078        assert!(is_success(&reply));
2079        assert_eq!(remaining(&reply), "");
2080    }
2081
2082    #[test]
2083    fn c_flow_sequence_accepts_flow_pair_entry() {
2084        let reply = c_flow_sequence(0, Context::FlowOut)(state("[a: b] rest"));
2085        assert!(is_success(&reply));
2086        let c = codes(c_flow_sequence(0, Context::FlowOut)(state("[a: b] rest")));
2087        assert!(c.contains(&Code::BeginPair));
2088        assert!(c.contains(&Code::EndPair));
2089    }
2090
2091    #[test]
2092    fn c_flow_sequence_accepts_multiple_entries_no_spaces() {
2093        let reply = c_flow_sequence(0, Context::FlowOut)(state("[a,b,c] rest"));
2094        assert!(is_success(&reply));
2095        assert_eq!(remaining(&reply), " rest");
2096    }
2097
2098    #[test]
2099    fn c_flow_sequence_accepts_whitespace_around_entries() {
2100        let reply = c_flow_sequence(0, Context::FlowOut)(state("[ a , b ] rest"));
2101        assert!(is_success(&reply));
2102        assert_eq!(remaining(&reply), " rest");
2103    }
2104
2105    #[test]
2106    fn c_flow_sequence_accepts_multiline_entries() {
2107        let reply = c_flow_sequence(0, Context::FlowOut)(state("[\na,\nb\n] rest"));
2108        assert!(is_success(&reply));
2109        assert_eq!(remaining(&reply), " rest");
2110    }
2111
2112    #[test]
2113    fn c_flow_sequence_emits_indicator_for_brackets() {
2114        let c = codes(c_flow_sequence(0, Context::FlowOut)(state("[a]")));
2115        assert!(c.contains(&Code::Indicator));
2116    }
2117
2118    #[test]
2119    fn c_flow_sequence_emits_indicator_for_comma() {
2120        let c = codes(c_flow_sequence(0, Context::FlowOut)(state("[a,b]")));
2121        assert!(c.contains(&Code::Indicator));
2122    }
2123
2124    // -----------------------------------------------------------------------
2125    // Group 7: Flow mappings [141]–[153] (22 tests)
2126    // -----------------------------------------------------------------------
2127
2128    #[test]
2129    fn c_flow_mapping_emits_begin_mapping() {
2130        let c = codes(c_flow_mapping(0, Context::FlowOut)(state("{a: b} rest")));
2131        assert_eq!(c.first().copied(), Some(Code::BeginMapping));
2132    }
2133
2134    #[test]
2135    fn c_flow_mapping_emits_end_mapping() {
2136        let c = codes(c_flow_mapping(0, Context::FlowOut)(state("{a: b} rest")));
2137        assert_eq!(c.last().copied(), Some(Code::EndMapping));
2138    }
2139
2140    #[test]
2141    fn c_flow_mapping_accepts_empty_mapping() {
2142        let reply = c_flow_mapping(0, Context::FlowOut)(state("{} rest"));
2143        assert!(is_success(&reply));
2144        assert_eq!(remaining(&reply), " rest");
2145    }
2146
2147    #[test]
2148    fn c_flow_mapping_accepts_single_implicit_entry() {
2149        let reply = c_flow_mapping(0, Context::FlowOut)(state("{a: b} rest"));
2150        assert!(is_success(&reply));
2151        let c = codes(c_flow_mapping(0, Context::FlowOut)(state("{a: b} rest")));
2152        assert!(c.contains(&Code::BeginPair));
2153        assert!(c.contains(&Code::EndPair));
2154    }
2155
2156    #[test]
2157    fn c_flow_mapping_accepts_two_entries() {
2158        let reply = c_flow_mapping(0, Context::FlowOut)(state("{a: b, c: d} rest"));
2159        assert!(is_success(&reply));
2160        assert_eq!(remaining(&reply), " rest");
2161    }
2162
2163    #[test]
2164    fn c_flow_mapping_accepts_trailing_comma() {
2165        let reply = c_flow_mapping(0, Context::FlowOut)(state("{a: b,} rest"));
2166        assert!(is_success(&reply));
2167    }
2168
2169    #[test]
2170    fn c_flow_mapping_fails_without_opening_brace() {
2171        let reply = c_flow_mapping(0, Context::FlowOut)(state("a: b}"));
2172        assert!(is_failure(&reply));
2173    }
2174
2175    #[test]
2176    fn c_flow_mapping_fails_without_closing_brace() {
2177        let reply = c_flow_mapping(0, Context::FlowOut)(state("{a: b"));
2178        assert!(is_failure(&reply));
2179    }
2180
2181    #[test]
2182    fn c_flow_mapping_accepts_nested_mapping() {
2183        let reply = c_flow_mapping(0, Context::FlowOut)(state("{a: {b: c}} rest"));
2184        assert!(is_success(&reply));
2185        assert_eq!(remaining(&reply), " rest");
2186    }
2187
2188    #[test]
2189    fn c_flow_mapping_accepts_nested_sequence() {
2190        let reply = c_flow_mapping(0, Context::FlowOut)(state("{a: [1,2]} rest"));
2191        assert!(is_success(&reply));
2192    }
2193
2194    #[test]
2195    fn c_flow_mapping_accepts_colon_adjacent_value() {
2196        let reply = c_flow_mapping(0, Context::FlowOut)(state("{key:value} rest"));
2197        assert!(is_success(&reply));
2198    }
2199
2200    #[test]
2201    fn c_flow_mapping_accepts_explicit_key() {
2202        let reply = c_flow_mapping(0, Context::FlowOut)(state("{? key : value} rest"));
2203        assert!(is_success(&reply));
2204        let c = codes(c_flow_mapping(0, Context::FlowOut)(state(
2205            "{? key : value} rest",
2206        )));
2207        assert!(c.contains(&Code::BeginPair));
2208    }
2209
2210    #[test]
2211    fn c_flow_mapping_accepts_value_only_entry() {
2212        let reply = c_flow_mapping(0, Context::FlowOut)(state("{: value} rest"));
2213        assert!(is_success(&reply));
2214    }
2215
2216    #[test]
2217    fn c_flow_mapping_entry_emits_begin_pair() {
2218        let c = codes(c_flow_mapping(0, Context::FlowOut)(state("{a: b}")));
2219        assert!(c.contains(&Code::BeginPair));
2220    }
2221
2222    #[test]
2223    fn c_flow_mapping_entry_emits_end_pair() {
2224        let c = codes(c_flow_mapping(0, Context::FlowOut)(state("{a: b}")));
2225        assert!(c.contains(&Code::EndPair));
2226    }
2227
2228    #[test]
2229    fn c_flow_mapping_accepts_alias_as_value() {
2230        let reply = c_flow_mapping(0, Context::FlowOut)(state("{a: *anchor} rest"));
2231        assert!(is_success(&reply));
2232        let c = codes(c_flow_mapping(0, Context::FlowOut)(state(
2233            "{a: *anchor} rest",
2234        )));
2235        assert!(c.contains(&Code::BeginAlias));
2236    }
2237
2238    #[test]
2239    fn c_flow_mapping_accepts_double_quoted_key() {
2240        let reply = c_flow_mapping(0, Context::FlowOut)(state("{\"key\": value} rest"));
2241        assert!(is_success(&reply));
2242    }
2243
2244    #[test]
2245    fn c_flow_mapping_accepts_single_quoted_key() {
2246        let reply = c_flow_mapping(0, Context::FlowOut)(state("{'key': value} rest"));
2247        assert!(is_success(&reply));
2248    }
2249
2250    #[test]
2251    fn c_flow_mapping_accepts_multiline_entry() {
2252        let reply = c_flow_mapping(0, Context::FlowOut)(state("{\na: b\n} rest"));
2253        assert!(is_success(&reply));
2254        assert_eq!(remaining(&reply), " rest");
2255    }
2256
2257    #[test]
2258    fn c_flow_mapping_emits_indicator_for_braces() {
2259        let c = codes(c_flow_mapping(0, Context::FlowOut)(state("{a: b}")));
2260        assert!(c.contains(&Code::Indicator));
2261    }
2262
2263    #[test]
2264    fn c_flow_mapping_emits_indicator_for_colon() {
2265        let c = codes(c_flow_mapping(0, Context::FlowOut)(state("{a: b}")));
2266        assert!(c.contains(&Code::Indicator));
2267    }
2268
2269    #[test]
2270    fn c_flow_mapping_accepts_no_value_entry() {
2271        let reply = c_flow_mapping(0, Context::FlowOut)(state("{a} rest"));
2272        assert!(is_success(&reply));
2273    }
2274
2275    // -----------------------------------------------------------------------
2276    // Group 8: Flow nodes [154]–[161] (16 tests)
2277    // -----------------------------------------------------------------------
2278
2279    #[test]
2280    fn ns_flow_yaml_node_accepts_plain_scalar() {
2281        let reply = ns_flow_yaml_node(0, Context::FlowOut)(state("hello rest"));
2282        assert!(is_success(&reply));
2283        let c = codes(ns_flow_yaml_node(0, Context::FlowOut)(state("hello rest")));
2284        assert!(c.contains(&Code::BeginScalar));
2285    }
2286
2287    #[test]
2288    fn ns_flow_yaml_node_accepts_alias() {
2289        let reply = ns_flow_yaml_node(0, Context::FlowOut)(state("*anchor rest"));
2290        assert!(is_success(&reply));
2291        let c = codes(ns_flow_yaml_node(0, Context::FlowOut)(state(
2292            "*anchor rest",
2293        )));
2294        assert!(c.contains(&Code::BeginAlias));
2295    }
2296
2297    #[test]
2298    fn ns_flow_yaml_node_accepts_properties_then_scalar() {
2299        let reply = ns_flow_yaml_node(0, Context::FlowOut)(state("!!str hello rest"));
2300        assert!(is_success(&reply));
2301        let c = codes(ns_flow_yaml_node(0, Context::FlowOut)(state(
2302            "!!str hello rest",
2303        )));
2304        assert!(c.contains(&Code::BeginTag));
2305        assert!(c.contains(&Code::BeginScalar));
2306    }
2307
2308    #[test]
2309    fn ns_flow_yaml_node_accepts_properties_with_empty_node() {
2310        let reply = ns_flow_yaml_node(0, Context::FlowOut)(state("!!str "));
2311        assert!(is_success(&reply));
2312    }
2313
2314    #[test]
2315    fn c_flow_json_node_accepts_double_quoted_scalar() {
2316        let reply = c_flow_json_node(0, Context::FlowOut)(state("\"hello\" rest"));
2317        assert!(is_success(&reply));
2318        let c = codes(c_flow_json_node(0, Context::FlowOut)(state(
2319            "\"hello\" rest",
2320        )));
2321        assert!(c.contains(&Code::BeginScalar));
2322    }
2323
2324    #[test]
2325    fn c_flow_json_node_accepts_single_quoted_scalar() {
2326        let reply = c_flow_json_node(0, Context::FlowOut)(state("'hello' rest"));
2327        assert!(is_success(&reply));
2328        let c = codes(c_flow_json_node(0, Context::FlowOut)(state("'hello' rest")));
2329        assert!(c.contains(&Code::BeginScalar));
2330    }
2331
2332    #[test]
2333    fn c_flow_json_node_accepts_flow_sequence() {
2334        let reply = c_flow_json_node(0, Context::FlowOut)(state("[a, b] rest"));
2335        assert!(is_success(&reply));
2336        let c = codes(c_flow_json_node(0, Context::FlowOut)(state("[a, b] rest")));
2337        assert!(c.contains(&Code::BeginSequence));
2338    }
2339
2340    #[test]
2341    fn c_flow_json_node_accepts_flow_mapping() {
2342        let reply = c_flow_json_node(0, Context::FlowOut)(state("{a: b} rest"));
2343        assert!(is_success(&reply));
2344        let c = codes(c_flow_json_node(0, Context::FlowOut)(state("{a: b} rest")));
2345        assert!(c.contains(&Code::BeginMapping));
2346    }
2347
2348    #[test]
2349    fn c_flow_json_node_fails_on_plain_scalar() {
2350        let reply = c_flow_json_node(0, Context::FlowOut)(state("hello rest"));
2351        assert!(is_failure(&reply));
2352    }
2353
2354    #[test]
2355    fn ns_flow_node_accepts_yaml_node() {
2356        let reply = ns_flow_node(0, Context::FlowOut)(state("hello rest"));
2357        assert!(is_success(&reply));
2358    }
2359
2360    #[test]
2361    fn ns_flow_node_accepts_json_node() {
2362        let reply = ns_flow_node(0, Context::FlowOut)(state("\"hello\" rest"));
2363        assert!(is_success(&reply));
2364    }
2365
2366    #[test]
2367    fn c_ns_flow_pair_accepts_implicit_key_value() {
2368        let reply = c_ns_flow_pair(0, Context::FlowOut)(state("key: value rest"));
2369        assert!(is_success(&reply));
2370        let c = codes(c_ns_flow_pair(0, Context::FlowOut)(state(
2371            "key: value rest",
2372        )));
2373        assert!(c.contains(&Code::BeginPair));
2374        assert!(c.contains(&Code::EndPair));
2375    }
2376
2377    #[test]
2378    fn c_ns_flow_pair_accepts_explicit_key() {
2379        let reply = c_ns_flow_pair(0, Context::FlowOut)(state("? key : value rest"));
2380        assert!(is_success(&reply));
2381        let c = codes(c_ns_flow_pair(0, Context::FlowOut)(state(
2382            "? key : value rest",
2383        )));
2384        assert!(c.contains(&Code::BeginPair));
2385    }
2386
2387    #[test]
2388    fn c_ns_flow_pair_accepts_value_with_no_key() {
2389        let reply = c_ns_flow_pair(0, Context::FlowOut)(state(": value rest"));
2390        assert!(is_success(&reply));
2391    }
2392
2393    #[test]
2394    fn c_ns_flow_pair_emits_indicator_for_colon() {
2395        let c = codes(c_ns_flow_pair(0, Context::FlowOut)(state("key: value")));
2396        assert!(c.contains(&Code::Indicator));
2397    }
2398
2399    #[test]
2400    fn ns_flow_yaml_node_accepts_properties_then_flow_mapping() {
2401        let reply = ns_flow_yaml_node(0, Context::FlowOut)(state("!!map {a: b} rest"));
2402        assert!(is_success(&reply));
2403        let c = codes(ns_flow_yaml_node(0, Context::FlowOut)(state(
2404            "!!map {a: b} rest",
2405        )));
2406        assert!(c.contains(&Code::BeginTag));
2407        assert!(c.contains(&Code::BeginMapping));
2408    }
2409}