1use crate::chars::{b_break, nb_char, s_white};
11use crate::combinator::{
12 Context, Parser, Reply, State, alt, char_parser, many0, many1, neg_lookahead, opt, seq, token,
13 wrap_tokens,
14};
15use crate::flow::{e_node, ns_flow_node};
16use crate::structure::{
17 b_comment, c_forbidden, c_ns_properties, l_empty, s_b_comment, s_indent, s_indent_content,
18 s_indent_le, s_indent_lt, s_l_comments, s_separate, s_separate_ge, s_separate_in_line,
19};
20use crate::token::Code;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum Chomping {
29 Strip,
31 Clip,
33 Keep,
35}
36
37fn c_chomping_indicator(state: State<'_>) -> (Chomping, State<'_>) {
46 match state.peek() {
47 Some('-') => (Chomping::Strip, state.advance('-')),
48 Some('+') => (Chomping::Keep, state.advance('+')),
49 _ => (Chomping::Clip, state),
50 }
51}
52
53fn c_indentation_indicator(state: State<'_>) -> Reply<'_> {
58 match state.peek() {
59 Some('0') => Reply::Failure,
60 Some(ch @ '1'..='9') => {
61 let after = state.advance(ch);
62 let n = i32::from(ch as u8 - b'0');
63 Reply::Success {
64 tokens: vec![crate::token::Token {
65 code: Code::Meta,
66 pos: after.pos,
67 text: "",
68 }],
69 state: State {
70 input: after.input,
71 pos: after.pos,
72 n,
73 c: after.c,
74 },
75 }
76 }
77 _ => Reply::Success {
78 tokens: Vec::new(),
79 state: State {
80 input: state.input,
81 pos: state.pos,
82 n: 0, c: state.c,
84 },
85 },
86 }
87}
88
89fn try_indent_chomp(s: State<'_>) -> Option<(i32, Chomping, State<'_>)> {
91 match c_indentation_indicator(s) {
92 Reply::Success { state: s1, .. } => {
93 let m = s1.n;
94 let (chomp, s2) = c_chomping_indicator(s1);
95 Some((m, chomp, s2))
96 }
97 Reply::Failure | Reply::Error(_) => None,
98 }
99}
100
101fn try_chomp_indent(s: State<'_>) -> Option<(i32, Chomping, State<'_>)> {
103 let (chomp, s1) = c_chomping_indicator(s);
104 match c_indentation_indicator(s1) {
105 Reply::Success { state: s2, .. } => {
106 let m = s2.n;
107 Some((m, chomp, s2))
108 }
109 Reply::Failure | Reply::Error(_) => None,
110 }
111}
112
113fn parse_block_header(
117 state: State<'_>,
118) -> Option<(i32, Chomping, Option<&'static str>, State<'_>)> {
119 let r1 = try_indent_chomp(state.clone());
121 let r2 = try_chomp_indent(state);
122
123 let (m, chomp, after_indicators) = match (r1, r2) {
124 (Some((_, _, s1)), Some((m2, c2, s2))) if s2.pos.byte_offset > s1.pos.byte_offset => {
125 (m2, c2, s2)
126 }
127 (Some((m, c, s)), _) | (None, Some((m, c, s))) => (m, c, s),
128 (None, None) => return None,
129 };
130
131 let chomp_char = match chomp {
132 Chomping::Strip => Some("-"),
133 Chomping::Keep => Some("+"),
134 Chomping::Clip => None,
135 };
136
137 match s_b_comment()(after_indicators) {
139 Reply::Success { state: s_after, .. } => Some((m, chomp, chomp_char, s_after)),
140 Reply::Failure | Reply::Error(_) => None,
141 }
142}
143
144fn detect_scalar_indentation(input: &str, min_indent: i32) -> i32 {
157 let mut remaining = input;
158 loop {
159 let spaces = remaining.chars().take_while(|&ch| ch == ' ').count();
161 let after_spaces = &remaining[spaces..];
162 match after_spaces.chars().next() {
164 None => {
165 return min_indent;
167 }
168 Some('\n' | '\r') => {
169 let break_len = if after_spaces.starts_with("\r\n") {
171 2
172 } else {
173 1
174 };
175 remaining = &after_spaces[break_len..];
176 }
177 Some('\t') => {
178 let line_end = after_spaces.find('\n').unwrap_or(after_spaces.len());
181 remaining = &after_spaces[line_end..];
182 if remaining.starts_with('\n') {
183 remaining = &remaining[1..];
184 }
185 }
186 Some(_) => {
187 let indent = i32::try_from(spaces).unwrap_or(i32::MAX);
189 return indent;
190 }
191 }
192 }
193}
194
195fn b_chomped_last(t: Chomping) -> Parser<'static> {
204 Box::new(move |state| {
205 match b_break()(state.clone()) {
207 Reply::Failure => {
208 if state.input.is_empty() {
210 return Reply::Success {
211 tokens: Vec::new(),
212 state,
213 };
214 }
215 Reply::Failure
216 }
217 Reply::Error(e) => Reply::Error(e),
218 Reply::Success {
219 state: after_break, ..
220 } => match t {
221 Chomping::Strip => Reply::Success {
222 tokens: Vec::new(),
223 state: after_break,
224 },
225 Chomping::Clip | Chomping::Keep => Reply::Success {
226 tokens: vec![crate::token::Token {
227 code: Code::LineFeed,
228 pos: state.pos,
229 text: "",
230 }],
231 state: after_break,
232 },
233 },
234 }
235 })
236}
237
238fn l_chomped_blank(n: i32) -> Parser<'static> {
242 seq(s_indent_le(n), seq(many0(s_white()), b_break()))
243}
244
245fn l_strip_empty(n: i32) -> Parser<'static> {
249 seq(many0(l_chomped_blank(n)), opt(l_trail_comments(n)))
250}
251
252fn l_keep_empty(n: i32) -> Parser<'static> {
256 seq(
257 many0(token(Code::Break, l_chomped_blank(n))),
258 opt(l_trail_comments(n)),
259 )
260}
261
262fn l_trail_comments(n: i32) -> Parser<'static> {
267 use crate::structure::l_comment;
268 seq(
269 wrap_tokens(
270 Code::BeginComment,
271 Code::EndComment,
272 seq(
273 s_indent_lt(n),
274 seq(
275 token(Code::Indicator, char_parser('#')),
276 seq(token(Code::Text, many0(nb_char())), b_comment()),
277 ),
278 ),
279 ),
280 many0(l_comment()),
281 )
282}
283
284fn l_chomped_empty(n: i32, t: Chomping) -> Parser<'static> {
286 match t {
287 Chomping::Strip | Chomping::Clip => l_strip_empty(n),
288 Chomping::Keep => l_keep_empty(n),
289 }
290}
291
292fn l_nb_literal_text(n: i32) -> Parser<'static> {
305 seq(
306 many0(l_empty(n, Context::BlockIn)),
307 seq(s_indent_content(n), token(Code::Text, many1(nb_char()))),
308 )
309}
310
311fn b_nb_literal_next(n: i32) -> Parser<'static> {
313 seq(token(Code::LineFeed, b_break()), l_nb_literal_text(n))
314}
315
316fn l_literal_content(n: i32, t: Chomping) -> Parser<'static> {
318 Box::new(move |state| {
319 match l_nb_literal_text(n)(state.clone()) {
321 Reply::Failure => {
322 l_chomped_empty(n, t)(state)
324 }
325 Reply::Error(e) => Reply::Error(e),
326 Reply::Success {
327 tokens: first_tokens,
328 state: after_first,
329 } => {
330 let cont_result = many0(b_nb_literal_next(n))(after_first.clone());
332 let (cont_tokens, after_cont) = match cont_result {
333 Reply::Success { tokens, state } => (tokens, state),
334 Reply::Failure | Reply::Error(_) => (Vec::new(), after_first),
335 };
336
337 let last_result = b_chomped_last(t)(after_cont.clone());
339 let (last_tokens, after_last) = match last_result {
340 Reply::Success { tokens, state } => (tokens, state),
341 Reply::Failure | Reply::Error(_) => (Vec::new(), after_cont),
342 };
343
344 let tail_result = l_chomped_empty(n, t)(after_last.clone());
346 let (tail_tokens, final_state) = match tail_result {
347 Reply::Success { tokens, state } => (tokens, state),
348 Reply::Failure | Reply::Error(_) => (Vec::new(), after_last),
349 };
350
351 let mut all = first_tokens;
352 all.extend(cont_tokens);
353 all.extend(last_tokens);
354 all.extend(tail_tokens);
355 Reply::Success {
356 tokens: all,
357 state: final_state,
358 }
359 }
360 }
361 })
362}
363
364#[must_use]
366pub fn c_l_literal(n: i32) -> Parser<'static> {
367 Box::new(move |state| {
368 let Some('|') = state.peek() else {
370 return Reply::Failure;
371 };
372 let start_pos = state.pos;
373 let after_pipe = state.advance('|');
374
375 let Some((m_raw, chomp, chomp_char, after_header)) = parse_block_header(after_pipe.clone())
377 else {
378 return Reply::Failure;
379 };
380
381 let m = if m_raw == 0 {
383 detect_scalar_indentation(after_header.input, n + 1)
384 } else {
385 n + m_raw
386 };
387
388 let header_tokens: Vec<crate::token::Token<'static>> = {
389 let mut v = vec![crate::token::Token {
390 code: Code::Indicator,
391 pos: start_pos,
392 text: "|",
393 }];
394 if let Some(ch) = chomp_char {
395 v.push(crate::token::Token {
396 code: Code::Indicator,
397 pos: start_pos,
398 text: ch,
399 });
400 }
401 v
402 };
403
404 if m <= n {
405 let content_result = l_chomped_empty(m, chomp)(after_header.clone());
407 let (content_tokens, final_state) = match content_result {
408 Reply::Success { tokens, state } => (tokens, state),
409 Reply::Failure | Reply::Error(_) => (Vec::new(), after_header),
410 };
411 let mut all = vec![crate::token::Token {
412 code: Code::BeginScalar,
413 pos: start_pos,
414 text: "",
415 }];
416 all.extend(header_tokens);
417 all.extend(content_tokens);
418 all.push(crate::token::Token {
419 code: Code::EndScalar,
420 pos: final_state.pos,
421 text: "",
422 });
423 return Reply::Success {
424 tokens: all,
425 state: final_state,
426 };
427 }
428
429 let content_result = l_literal_content(m, chomp)(after_header);
431 let (content_tokens, final_state) = match content_result {
432 Reply::Success { tokens, state } => (tokens, state),
433 Reply::Failure => return Reply::Failure,
434 Reply::Error(e) => return Reply::Error(e),
435 };
436
437 let mut all = vec![crate::token::Token {
438 code: Code::BeginScalar,
439 pos: start_pos,
440 text: "",
441 }];
442 all.extend(header_tokens);
443 all.extend(content_tokens);
444 all.push(crate::token::Token {
445 code: Code::EndScalar,
446 pos: final_state.pos,
447 text: "",
448 });
449 Reply::Success {
450 tokens: all,
451 state: final_state,
452 }
453 })
454}
455
456fn s_nb_folded_text(n: i32) -> Parser<'static> {
466 seq(
467 s_indent_content(n),
468 seq(
469 neg_lookahead(s_white()),
470 token(Code::Text, many1(nb_char())),
471 ),
472 )
473}
474
475fn s_nb_spaced_text(n: i32) -> Parser<'static> {
483 Box::new(move |state| {
484 let after_indent = match s_indent_content(n)(state) {
486 Reply::Success { state, .. } => state,
487 Reply::Failure => return Reply::Failure,
488 Reply::Error(e) => return Reply::Error(e),
489 };
490 match after_indent.peek() {
492 Some(' ' | '\t') => {}
493 _ => return Reply::Failure,
494 }
495 token(Code::Text, seq(s_white(), many1(nb_char())))(after_indent)
499 })
500}
501
502fn s_nb_folded_lines(n: i32) -> Parser<'static> {
506 seq(
507 s_nb_folded_text(n),
508 many0(seq(
509 token(Code::LineFold, b_break()),
510 seq(many0(l_empty(n, Context::BlockIn)), s_nb_folded_text(n)),
511 )),
512 )
513}
514
515fn s_nb_spaced_lines(n: i32) -> Parser<'static> {
517 seq(
518 s_nb_spaced_text(n),
519 many0(seq(
520 token(Code::LineFeed, b_break()),
521 seq(many0(l_empty(n, Context::BlockIn)), s_nb_spaced_text(n)),
522 )),
523 )
524}
525
526fn l_nb_same_lines(n: i32) -> Parser<'static> {
528 seq(
529 many0(l_empty(n, Context::BlockIn)),
530 alt(s_nb_folded_lines(n), s_nb_spaced_lines(n)),
531 )
532}
533
534fn l_nb_diff_lines(n: i32) -> Parser<'static> {
536 seq(
537 l_nb_same_lines(n),
538 many0(seq(token(Code::LineFeed, b_break()), l_nb_same_lines(n))),
539 )
540}
541
542fn l_folded_content(n: i32, t: Chomping) -> Parser<'static> {
544 Box::new(move |state| {
545 match l_nb_diff_lines(n)(state.clone()) {
547 Reply::Failure => {
548 l_chomped_empty(n, t)(state)
550 }
551 Reply::Error(e) => Reply::Error(e),
552 Reply::Success {
553 tokens: content_tokens,
554 state: after_content,
555 } => {
556 let last_result = b_chomped_last(t)(after_content.clone());
557 let (last_tokens, after_last) = match last_result {
558 Reply::Success { tokens, state } => (tokens, state),
559 Reply::Failure | Reply::Error(_) => (Vec::new(), after_content),
560 };
561
562 let tail_result = l_chomped_empty(n, t)(after_last.clone());
563 let (tail_tokens, final_state) = match tail_result {
564 Reply::Success { tokens, state } => (tokens, state),
565 Reply::Failure | Reply::Error(_) => (Vec::new(), after_last),
566 };
567
568 let mut all = content_tokens;
569 all.extend(last_tokens);
570 all.extend(tail_tokens);
571 Reply::Success {
572 tokens: all,
573 state: final_state,
574 }
575 }
576 }
577 })
578}
579
580#[must_use]
582pub fn c_l_folded(n: i32) -> Parser<'static> {
583 Box::new(move |state| {
584 let Some('>') = state.peek() else {
586 return Reply::Failure;
587 };
588 let start_pos = state.pos;
589 let after_gt = state.advance('>');
590
591 let Some((m_raw, chomp, chomp_char, after_header)) = parse_block_header(after_gt) else {
592 return Reply::Failure;
593 };
594
595 let m = if m_raw == 0 {
596 detect_scalar_indentation(after_header.input, n + 1)
597 } else {
598 n + m_raw
599 };
600
601 let header_tokens: Vec<crate::token::Token<'static>> = {
602 let mut v = vec![crate::token::Token {
603 code: Code::Indicator,
604 pos: start_pos,
605 text: ">",
606 }];
607 if let Some(ch) = chomp_char {
608 v.push(crate::token::Token {
609 code: Code::Indicator,
610 pos: start_pos,
611 text: ch,
612 });
613 }
614 v
615 };
616
617 if m <= n {
618 let content_result = l_chomped_empty(m, chomp)(after_header.clone());
619 let (content_tokens, final_state) = match content_result {
620 Reply::Success { tokens, state } => (tokens, state),
621 Reply::Failure | Reply::Error(_) => (Vec::new(), after_header),
622 };
623 let mut all = vec![crate::token::Token {
624 code: Code::BeginScalar,
625 pos: start_pos,
626 text: "",
627 }];
628 all.extend(header_tokens);
629 all.extend(content_tokens);
630 all.push(crate::token::Token {
631 code: Code::EndScalar,
632 pos: final_state.pos,
633 text: "",
634 });
635 return Reply::Success {
636 tokens: all,
637 state: final_state,
638 };
639 }
640
641 let content_result = l_folded_content(m, chomp)(after_header);
642 let (content_tokens, final_state) = match content_result {
643 Reply::Success { tokens, state } => (tokens, state),
644 Reply::Failure => return Reply::Failure,
645 Reply::Error(e) => return Reply::Error(e),
646 };
647
648 let mut all = vec![crate::token::Token {
649 code: Code::BeginScalar,
650 pos: start_pos,
651 text: "",
652 }];
653 all.extend(header_tokens);
654 all.extend(content_tokens);
655 all.push(crate::token::Token {
656 code: Code::EndScalar,
657 pos: final_state.pos,
658 text: "",
659 });
660 Reply::Success {
661 tokens: all,
662 state: final_state,
663 }
664 })
665}
666
667#[must_use]
675pub const fn seq_spaces(n: i32, c: Context) -> i32 {
676 match c {
677 Context::BlockOut => n - 1,
678 Context::BlockIn
679 | Context::FlowOut
680 | Context::FlowIn
681 | Context::BlockKey
682 | Context::FlowKey => n,
683 }
684}
685
686#[must_use]
690pub fn l_block_sequence(n: i32) -> Parser<'static> {
691 Box::new(move |state| {
692 let detected = detect_scalar_indentation(state.input, n);
694 if detected < n {
695 return Reply::Failure;
696 }
697 wrap_tokens(
698 Code::BeginSequence,
699 Code::EndSequence,
700 many1(c_l_block_seq_entry(detected)),
701 )(state)
702 })
703}
704
705fn c_l_block_seq_entry(n: i32) -> Parser<'static> {
707 Box::new(move |state| {
708 let indent_result = s_indent(n)(state.clone());
710 let after_indent = match indent_result {
711 Reply::Success { state, .. } => state,
712 Reply::Failure => return Reply::Failure,
713 Reply::Error(e) => return Reply::Error(e),
714 };
715
716 let Some('-') = after_indent.peek() else {
718 return Reply::Failure;
719 };
720 let dash_pos = after_indent.pos;
721 let after_dash = after_indent.advance('-');
722
723 if let Some(ch) = after_dash.peek() {
726 if ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r' {
727 return Reply::Failure;
728 }
729 }
730
731 let dash_token = crate::token::Token {
732 code: Code::Indicator,
733 pos: dash_pos,
734 text: "-",
735 };
736
737 let value_result = s_b_block_indented(n, Context::BlockIn)(after_dash.clone());
739 let (value_tokens, final_state) = match value_result {
740 Reply::Success { tokens, state } => (tokens, state),
741 Reply::Failure => return Reply::Failure,
742 Reply::Error(e) => return Reply::Error(e),
743 };
744
745 let mut all = vec![dash_token];
746 all.extend(value_tokens);
747 Reply::Success {
748 tokens: all,
749 state: final_state,
750 }
751 })
752}
753
754fn s_b_block_indented(n: i32, c: Context) -> Parser<'static> {
763 Box::new(move |state| {
764 let m = i32::try_from(state.input.chars().take_while(|&ch| ch == ' ').count()).unwrap_or(0);
766
767 if m > 0 {
768 let mut after_indent = state.clone();
770 for _ in 0..m {
771 after_indent = after_indent.advance(' ');
772 }
773
774 let compact_n = n + 1 + m;
776 let compact = alt(
777 ns_l_compact_sequence(compact_n),
778 ns_l_compact_mapping(compact_n),
779 );
780
781 match compact(after_indent.clone()) {
782 Reply::Success { tokens, state } => {
783 return Reply::Success { tokens, state };
784 }
785 Reply::Failure | Reply::Error(_) => {}
786 }
787 }
788
789 let block_node = alt(s_l_block_node(n, c), seq(e_node(), s_l_comments()));
791 block_node(state)
792 })
793}
794
795fn c_l_block_seq_entry_no_indent(n: i32) -> Parser<'static> {
797 Box::new(move |state| {
798 let Some('-') = state.peek() else {
799 return Reply::Failure;
800 };
801 let dash_pos = state.pos;
802 let after_dash = state.advance('-');
803 if let Some(ch) = after_dash.peek() {
804 if ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r' {
805 return Reply::Failure;
806 }
807 }
808 let dash_token = crate::token::Token {
809 code: Code::Indicator,
810 pos: dash_pos,
811 text: "-",
812 };
813 let value_result = s_b_block_indented(n, Context::BlockIn)(after_dash.clone());
814 let (value_tokens, final_state) = match value_result {
815 Reply::Success { tokens, state } => (tokens, state),
816 Reply::Failure => return Reply::Failure,
817 Reply::Error(e) => return Reply::Error(e),
818 };
819 let mut all = vec![dash_token];
820 all.extend(value_tokens);
821 Reply::Success {
822 tokens: all,
823 state: final_state,
824 }
825 })
826}
827
828fn ns_l_compact_sequence(n: i32) -> Parser<'static> {
834 wrap_tokens(
835 Code::BeginSequence,
836 Code::EndSequence,
837 seq(
838 c_l_block_seq_entry_no_indent(n),
839 many0(c_l_block_seq_entry(n)),
840 ),
841 )
842}
843
844fn skip_blank_lines(state: State<'_>) -> State<'_> {
851 let mut s = state;
852 loop {
853 let remaining = s.input;
855 let ws_len = remaining
856 .chars()
857 .take_while(|&ch| ch == ' ' || ch == '\t')
858 .count();
859 let after_ws = &remaining[ws_len..];
860 if after_ws.starts_with('\n') {
861 let mut next = s;
863 for ch in remaining[..ws_len].chars() {
864 next = next.advance(ch);
865 }
866 next = next.advance('\n');
867 s = next;
868 } else if after_ws.starts_with("\r\n") {
869 let mut next = s;
870 for ch in remaining[..ws_len].chars() {
871 next = next.advance(ch);
872 }
873 next = next.advance('\r');
874 next = next.advance('\n');
875 s = next;
876 } else {
877 break;
878 }
879 }
880 s
881}
882
883#[must_use]
887pub fn l_block_mapping(n: i32) -> Parser<'static> {
888 Box::new(move |state| {
889 let detected = detect_scalar_indentation(state.input, n);
891 if detected < n {
892 return Reply::Failure;
893 }
894 wrap_tokens(
896 Code::BeginMapping,
897 Code::EndMapping,
898 Box::new(move |state| {
899 let (first_tokens, after_first) = match ns_l_block_map_entry(detected)(state) {
901 Reply::Success { tokens, state } => (tokens, state),
902 Reply::Failure => return Reply::Failure,
903 Reply::Error(e) => return Reply::Error(e),
904 };
905 let mut all_tokens = first_tokens;
906 let mut current = after_first;
907 loop {
909 let skipped = skip_blank_lines(current.clone());
910 match ns_l_block_map_entry(detected)(skipped.clone()) {
911 Reply::Success { tokens, state } => {
912 all_tokens.extend(tokens);
913 current = state;
914 }
915 Reply::Failure | Reply::Error(_) => break,
916 }
917 }
918 Reply::Success {
919 tokens: all_tokens,
920 state: current,
921 }
922 }),
923 )(state)
924 })
925}
926
927fn ns_l_block_map_entry(n: i32) -> Parser<'static> {
929 Box::new(move |state| {
930 let indent_result = s_indent(n)(state.clone());
932 let after_indent = match indent_result {
933 Reply::Success { state, .. } => state,
934 Reply::Failure => return Reply::Failure,
935 Reply::Error(e) => return Reply::Error(e),
936 };
937
938 alt(
939 c_l_block_map_explicit_entry(n),
940 ns_l_block_map_implicit_entry(n),
941 )(after_indent)
942 })
943}
944
945fn c_l_block_map_explicit_entry(n: i32) -> Parser<'static> {
947 wrap_tokens(
948 Code::BeginPair,
949 Code::EndPair,
950 seq(
951 c_l_block_map_explicit_key(n),
952 opt(l_block_map_explicit_value(n)),
953 ),
954 )
955}
956
957fn c_l_block_map_explicit_key(n: i32) -> Parser<'static> {
963 Box::new(move |state| {
964 let Some('?') = state.peek() else {
965 return Reply::Failure;
966 };
967 let q_pos = state.pos;
968 let after_q = state.advance('?');
969
970 match after_q.peek() {
972 None | Some(' ' | '\t' | '\n' | '\r') => {}
973 Some(_) => return Reply::Failure,
974 }
975
976 let q_token = crate::token::Token {
977 code: Code::Indicator,
978 pos: q_pos,
979 text: "?",
980 };
981
982 let value_result = s_b_block_indented(n, Context::BlockOut)(after_q.clone());
983 let (value_tokens, final_state) = match value_result {
984 Reply::Success { tokens, state } => (tokens, state),
985 Reply::Failure | Reply::Error(_) => (Vec::new(), after_q),
986 };
987
988 let mut all = vec![q_token];
989 all.extend(value_tokens);
990 Reply::Success {
991 tokens: all,
992 state: final_state,
993 }
994 })
995}
996
997fn l_block_map_explicit_value(n: i32) -> Parser<'static> {
999 Box::new(move |state| {
1000 let indent_result = s_indent(n)(state.clone());
1002 let after_indent = match indent_result {
1003 Reply::Success { state, .. } => state,
1004 Reply::Failure => return Reply::Failure,
1005 Reply::Error(e) => return Reply::Error(e),
1006 };
1007
1008 let Some(':') = after_indent.peek() else {
1009 return Reply::Failure;
1010 };
1011 let colon_pos = after_indent.pos;
1012 let after_colon = after_indent.advance(':');
1013
1014 let colon_token = crate::token::Token {
1015 code: Code::Indicator,
1016 pos: colon_pos,
1017 text: ":",
1018 };
1019
1020 let value_result = s_b_block_indented(n, Context::BlockOut)(after_colon.clone());
1021 let (value_tokens, final_state) = match value_result {
1022 Reply::Success { tokens, state } => (tokens, state),
1023 Reply::Failure | Reply::Error(_) => (Vec::new(), after_colon),
1024 };
1025
1026 let mut all = vec![colon_token];
1027 all.extend(value_tokens);
1028 Reply::Success {
1029 tokens: all,
1030 state: final_state,
1031 }
1032 })
1033}
1034
1035fn ns_l_block_map_implicit_entry(n: i32) -> Parser<'static> {
1040 wrap_tokens(
1041 Code::BeginPair,
1042 Code::EndPair,
1043 Box::new(move |state| {
1044 let (key_tokens, after_key) = match ns_s_block_map_implicit_key()(state.clone()) {
1045 Reply::Success { tokens, state } => (tokens, state),
1046 Reply::Failure | Reply::Error(_) => {
1047 let check = match s_separate_in_line()(state.clone()) {
1049 Reply::Success { state: s, .. } if is_value_indicator(s.input) => s,
1050 Reply::Success { .. } | Reply::Failure | Reply::Error(_) => state.clone(),
1051 };
1052 if is_value_indicator(check.input) {
1053 (Vec::new(), check)
1054 } else {
1055 return Reply::Failure;
1056 }
1057 }
1058 };
1059 match c_l_block_map_implicit_value(n)(after_key) {
1060 Reply::Success {
1061 mut tokens,
1062 state: final_state,
1063 } => {
1064 let mut all = key_tokens;
1065 all.append(&mut tokens);
1066 Reply::Success {
1067 tokens: all,
1068 state: final_state,
1069 }
1070 }
1071 Reply::Failure => Reply::Failure,
1072 Reply::Error(e) => Reply::Error(e),
1073 }
1074 }),
1075 )
1076}
1077
1078fn is_value_indicator(input: &str) -> bool {
1082 if !input.starts_with(':') {
1083 return false;
1084 }
1085 match input[1..].chars().next() {
1086 None | Some(' ' | '\t' | '\n' | '\r') => true,
1087 Some(_) => false,
1088 }
1089}
1090
1091#[must_use]
1098pub fn ns_s_block_map_implicit_key() -> Parser<'static> {
1099 Box::new(|state| {
1100 if let Reply::Success {
1102 tokens,
1103 state: after_alias,
1104 } = crate::flow::c_ns_alias_node()(state.clone())
1105 {
1106 let final_state = match s_separate_in_line()(after_alias.clone()) {
1108 Reply::Success { state, .. } => state,
1109 Reply::Failure | Reply::Error(_) => after_alias,
1110 };
1111 return Reply::Success {
1112 tokens,
1113 state: final_state,
1114 };
1115 }
1116
1117 let (prop_tokens, after_props) =
1119 match seq(c_ns_properties(0, Context::BlockKey), s_separate_in_line())(state.clone()) {
1120 Reply::Success { tokens, state } => (tokens, state),
1121 Reply::Failure | Reply::Error(_) => (Vec::new(), state.clone()),
1122 };
1123
1124 let key_result = alt(
1126 crate::flow::c_double_quoted(0, Context::BlockKey),
1127 alt(
1128 crate::flow::c_single_quoted(0, Context::BlockKey),
1129 alt(
1130 crate::flow::ns_plain(0, Context::BlockKey),
1131 alt(
1132 crate::flow::c_flow_sequence(0, Context::BlockKey),
1133 crate::flow::c_flow_mapping(0, Context::BlockKey),
1134 ),
1135 ),
1136 ),
1137 )(after_props.clone());
1138
1139 match key_result {
1140 Reply::Success {
1141 tokens: key_tokens,
1142 state: after_key,
1143 } => {
1144 let final_state = match s_separate_in_line()(after_key.clone()) {
1146 Reply::Success { state, .. } => state,
1147 Reply::Failure | Reply::Error(_) => after_key,
1148 };
1149 let mut all = prop_tokens;
1150 all.extend(key_tokens);
1151 Reply::Success {
1152 tokens: all,
1153 state: final_state,
1154 }
1155 }
1156 Reply::Failure => {
1157 if prop_tokens.is_empty() {
1158 Reply::Failure
1159 } else {
1160 Reply::Success {
1162 tokens: prop_tokens,
1163 state: after_props,
1164 }
1165 }
1166 }
1167 Reply::Error(e) => Reply::Error(e),
1168 }
1169 })
1170}
1171
1172fn c_l_block_map_implicit_value(n: i32) -> Parser<'static> {
1177 Box::new(move |state| {
1178 let check_state = match s_separate_in_line()(state.clone()) {
1180 Reply::Success { state: s, .. } if s.peek() == Some(':') => s,
1181 Reply::Success { .. } | Reply::Failure | Reply::Error(_) => state.clone(),
1182 };
1183 if !is_value_indicator(check_state.input) {
1184 return Reply::Failure;
1185 }
1186 let colon_pos = check_state.pos;
1187 let after_colon = check_state.advance(':');
1188
1189 let colon_token = crate::token::Token {
1190 code: Code::Indicator,
1191 pos: colon_pos,
1192 text: ":",
1193 };
1194
1195 let value_result = alt(
1196 seq(
1197 s_separate(n, Context::BlockOut),
1198 s_l_block_node(n, Context::BlockOut),
1199 ),
1200 alt(
1201 s_l_block_node(n, Context::BlockIn),
1202 seq(e_node(), s_l_comments()),
1203 ),
1204 )(after_colon.clone());
1205
1206 let (value_tokens, final_state) = match value_result {
1207 Reply::Success { tokens, state } => (tokens, state),
1208 Reply::Failure | Reply::Error(_) => (Vec::new(), after_colon),
1209 };
1210
1211 let mut all = vec![colon_token];
1212 all.extend(value_tokens);
1213 Reply::Success {
1214 tokens: all,
1215 state: final_state,
1216 }
1217 })
1218}
1219
1220fn ns_l_compact_implicit_entry(n: i32) -> Parser<'static> {
1222 wrap_tokens(
1223 Code::BeginPair,
1224 Code::EndPair,
1225 Box::new(move |state| {
1226 let (key_tokens, after_key) = match ns_s_block_map_implicit_key()(state.clone()) {
1227 Reply::Success { tokens, state } => (tokens, state),
1228 Reply::Failure | Reply::Error(_) => {
1229 if is_value_indicator(state.input) {
1230 (Vec::new(), state.clone())
1231 } else {
1232 return Reply::Failure;
1233 }
1234 }
1235 };
1236 match c_l_block_map_implicit_value(n)(after_key) {
1237 Reply::Success {
1238 mut tokens,
1239 state: final_state,
1240 } => {
1241 let mut all = key_tokens;
1242 all.append(&mut tokens);
1243 Reply::Success {
1244 tokens: all,
1245 state: final_state,
1246 }
1247 }
1248 Reply::Failure => Reply::Failure,
1249 Reply::Error(e) => Reply::Error(e),
1250 }
1251 }),
1252 )
1253}
1254
1255fn ns_l_block_map_entry_no_indent(n: i32) -> Parser<'static> {
1258 alt(
1259 c_l_block_map_explicit_entry(n),
1260 ns_l_compact_implicit_entry(n),
1261 )
1262}
1263
1264fn ns_l_compact_mapping(n: i32) -> Parser<'static> {
1269 wrap_tokens(
1270 Code::BeginMapping,
1271 Code::EndMapping,
1272 seq(
1273 ns_l_block_map_entry_no_indent(n),
1274 many0(seq(s_indent(n), ns_l_block_map_entry_no_indent(n))),
1275 ),
1276 )
1277}
1278
1279#[must_use]
1285pub fn s_l_block_node(n: i32, c: Context) -> Parser<'static> {
1286 alt(s_l_block_in_block(n, c), s_l_flow_in_block(n))
1287}
1288
1289#[must_use]
1295pub fn s_l_flow_in_block(n: i32) -> Parser<'static> {
1296 seq(
1297 s_separate_ge(n + 1, Context::FlowOut),
1298 seq(
1299 neg_lookahead(c_forbidden()),
1300 seq(ns_flow_node(n + 1, Context::FlowOut), s_l_comments()),
1301 ),
1302 )
1303}
1304
1305#[must_use]
1307pub fn s_l_block_in_block(n: i32, c: Context) -> Parser<'static> {
1308 alt(s_l_block_scalar(n, c), s_l_block_collection(n, c))
1309}
1310
1311#[must_use]
1313pub fn s_l_block_scalar(n: i32, c: Context) -> Parser<'static> {
1314 Box::new(move |state| {
1315 let (sep_tokens, after_sep) = match s_separate_ge(n + 1, c)(state.clone()) {
1317 Reply::Success { tokens, state } => (tokens, state),
1318 Reply::Failure | Reply::Error(_) => (Vec::new(), state.clone()),
1319 };
1320
1321 let (prop_tokens, after_props) =
1324 match seq(c_ns_properties(n + 1, c), s_separate_ge(n + 1, c))(after_sep.clone()) {
1325 Reply::Success { tokens, state } => (tokens, state),
1326 Reply::Failure | Reply::Error(_) => (Vec::new(), after_sep.clone()),
1327 };
1328
1329 let scalar_result = alt(c_l_literal(n), c_l_folded(n))(after_props.clone());
1333 let (scalar_tokens, after_scalar) = match scalar_result {
1334 Reply::Success { tokens, state } => (tokens, state),
1335 Reply::Failure => return Reply::Failure,
1336 Reply::Error(e) => return Reply::Error(e),
1337 };
1338
1339 let mut all = sep_tokens;
1340 all.extend(prop_tokens);
1341 all.extend(scalar_tokens);
1342 Reply::Success {
1343 tokens: all,
1344 state: after_scalar,
1345 }
1346 })
1347}
1348
1349#[must_use]
1351pub fn s_l_block_collection(n: i32, c: Context) -> Parser<'static> {
1352 Box::new(move |state| {
1353 let (prop_tokens, after_props) =
1357 match seq(s_separate_ge(n + 1, c), c_ns_properties(n + 1, c))(state.clone()) {
1358 Reply::Success { tokens, state } => (tokens, state),
1359 Reply::Failure | Reply::Error(_) => (Vec::new(), state.clone()),
1360 };
1361
1362 let (comment_tokens, after_comments) = match s_l_comments()(after_props.clone()) {
1364 Reply::Success { tokens, state } => (tokens, state),
1365 Reply::Failure | Reply::Error(_) => (Vec::new(), after_props.clone()),
1366 };
1367
1368 let m = seq_spaces(n, c);
1372 let with_props = alt(l_block_sequence(m), l_block_mapping(n + 1))(after_comments);
1374
1375 if !prop_tokens.is_empty() {
1379 let (retry_comments, retry_after) = match s_l_comments()(state.clone()) {
1380 Reply::Success { tokens, state } => (tokens, state),
1381 Reply::Failure | Reply::Error(_) => (Vec::new(), state.clone()),
1382 };
1383 let without_props = alt(l_block_sequence(m), l_block_mapping(n + 1))(retry_after);
1384
1385 let with_end = match &with_props {
1386 Reply::Success { state, .. } => state.pos.byte_offset,
1387 Reply::Failure | Reply::Error(_) => 0,
1388 };
1389 let without_end = match &without_props {
1390 Reply::Success { state, .. } => state.pos.byte_offset,
1391 Reply::Failure | Reply::Error(_) => 0,
1392 };
1393
1394 if without_end > with_end {
1395 match without_props {
1396 Reply::Success { tokens, state } => {
1397 let mut all = retry_comments;
1398 all.extend(tokens);
1399 return Reply::Success { tokens: all, state };
1400 }
1401 Reply::Failure => return Reply::Failure,
1402 Reply::Error(e) => return Reply::Error(e),
1403 }
1404 }
1405 }
1406
1407 match with_props {
1408 Reply::Success { tokens, state } => {
1409 let mut all = prop_tokens;
1410 all.extend(comment_tokens);
1411 all.extend(tokens);
1412 Reply::Success { tokens: all, state }
1413 }
1414 Reply::Failure => Reply::Failure,
1415 Reply::Error(e) => Reply::Error(e),
1416 }
1417 })
1418}
1419
1420#[cfg(test)]
1425#[allow(
1426 clippy::indexing_slicing,
1427 clippy::expect_used,
1428 clippy::unwrap_used,
1429 unused_imports
1430)]
1431mod tests {
1432 use super::*;
1433 use crate::combinator::{Context, Reply, State};
1434 use crate::token::Code;
1435
1436 fn state(input: &str) -> State<'_> {
1437 State::new(input)
1438 }
1439
1440 fn state_with(input: &str, n: i32, c: Context) -> State<'_> {
1441 State::with_context(input, n, c)
1442 }
1443
1444 fn is_success(reply: &Reply<'_>) -> bool {
1445 matches!(reply, Reply::Success { .. })
1446 }
1447
1448 fn is_failure(reply: &Reply<'_>) -> bool {
1449 matches!(reply, Reply::Failure)
1450 }
1451
1452 fn remaining<'a>(reply: &'a Reply<'a>) -> &'a str {
1453 match reply {
1454 Reply::Success { state, .. } => state.input,
1455 Reply::Failure | Reply::Error(_) => panic!("expected success, got failure/error"),
1456 }
1457 }
1458
1459 fn codes(reply: Reply<'_>) -> Vec<Code> {
1460 match reply {
1461 Reply::Success { tokens, .. } => tokens.into_iter().map(|t| t.code).collect(),
1462 Reply::Failure | Reply::Error(_) => vec![],
1463 }
1464 }
1465
1466 #[test]
1471 fn c_chomping_indicator_strip_returns_strip() {
1472 let (chomp, after) = c_chomping_indicator(state("-"));
1473 assert_eq!(chomp, Chomping::Strip);
1474 assert_eq!(after.input, "");
1475 }
1476
1477 #[test]
1478 fn c_chomping_indicator_keep_returns_keep() {
1479 let (chomp, after) = c_chomping_indicator(state("+"));
1480 assert_eq!(chomp, Chomping::Keep);
1481 assert_eq!(after.input, "");
1482 }
1483
1484 #[test]
1485 fn c_chomping_indicator_absent_returns_clip() {
1486 let (chomp, after) = c_chomping_indicator(state("something"));
1487 assert_eq!(chomp, Chomping::Clip);
1488 assert_eq!(after.input, "something");
1489 }
1490
1491 #[test]
1496 fn c_indentation_indicator_explicit_digit() {
1497 let reply = c_indentation_indicator(state("2\n"));
1498 assert!(is_success(&reply));
1499 assert_eq!(remaining(&reply), "\n");
1500 }
1501
1502 #[test]
1503 fn c_indentation_indicator_rejects_zero() {
1504 let reply = c_indentation_indicator(state("0\n"));
1505 assert!(is_failure(&reply));
1506 }
1507
1508 #[test]
1509 fn c_indentation_indicator_absent_succeeds_with_zero_consumption() {
1510 let reply = c_indentation_indicator(state("\n"));
1511 assert!(is_success(&reply));
1512 assert_eq!(remaining(&reply), "\n");
1513 }
1514
1515 #[test]
1516 fn c_indentation_indicator_digit_nine() {
1517 let reply = c_indentation_indicator(state("9rest"));
1518 assert!(is_success(&reply));
1519 assert_eq!(remaining(&reply), "rest");
1520 }
1521
1522 #[test]
1527 fn c_b_block_header_indent_then_chomp() {
1528 let result = parse_block_header(state("2-\n"));
1529 assert!(result.is_some());
1530 let (m, chomp, _, after) = result.unwrap();
1531 assert_eq!(m, 2);
1532 assert_eq!(chomp, Chomping::Strip);
1533 assert_eq!(after.input, "");
1534 }
1535
1536 #[test]
1537 fn c_b_block_header_chomp_then_indent() {
1538 let result = parse_block_header(state("-2\n"));
1539 assert!(result.is_some());
1540 let (m, chomp, _, after) = result.unwrap();
1541 assert_eq!(m, 2);
1542 assert_eq!(chomp, Chomping::Strip);
1543 assert_eq!(after.input, "");
1544 }
1545
1546 #[test]
1547 fn c_b_block_header_chomp_only() {
1548 let result = parse_block_header(state("-\n"));
1549 assert!(result.is_some());
1550 let (m, chomp, _, after) = result.unwrap();
1551 assert_eq!(chomp, Chomping::Strip);
1552 assert_eq!(after.input, "");
1553 let _ = m;
1554 }
1555
1556 #[test]
1557 fn c_b_block_header_indent_only() {
1558 let result = parse_block_header(state("2\n"));
1559 assert!(result.is_some());
1560 let (m, _, _, after) = result.unwrap();
1561 assert_eq!(m, 2);
1562 assert_eq!(after.input, "");
1563 }
1564
1565 #[test]
1566 fn c_b_block_header_neither_indicator() {
1567 let result = parse_block_header(state("\n"));
1568 assert!(result.is_some());
1569 let (_, _, _, after) = result.unwrap();
1570 assert_eq!(after.input, "");
1571 }
1572
1573 #[test]
1574 fn c_b_block_header_with_trailing_comment() {
1575 let result = parse_block_header(state("2 # comment\n"));
1576 assert!(result.is_some());
1577 let (m, _, _, after) = result.unwrap();
1578 assert_eq!(m, 2);
1579 assert_eq!(after.input, "");
1580 }
1581
1582 #[test]
1587 fn c_l_literal_accepts_simple_literal_scalar() {
1588 let reply = c_l_literal(0)(state("|\n hello\n"));
1589 assert!(is_success(&reply));
1590 let c = codes(c_l_literal(0)(state("|\n hello\n")));
1591 assert!(c.contains(&Code::BeginScalar));
1592 assert!(c.contains(&Code::EndScalar));
1593 }
1594
1595 #[test]
1596 fn c_l_literal_emits_indicator_for_pipe() {
1597 let c = codes(c_l_literal(0)(state("|\n hello\n")));
1598 assert!(c.contains(&Code::Indicator));
1599 }
1600
1601 #[test]
1602 fn c_l_literal_consumes_entire_block() {
1603 let reply = c_l_literal(0)(state("|\n hello\n world\n"));
1604 assert!(is_success(&reply));
1605 assert_eq!(remaining(&reply), "");
1606 }
1607
1608 #[test]
1609 fn c_l_literal_leaves_less_indented_content_unconsumed() {
1610 let reply = c_l_literal(0)(state("|\n hello\nrest\n"));
1611 assert!(is_success(&reply));
1612 assert!(remaining(&reply).starts_with("rest"));
1613 }
1614
1615 #[test]
1616 fn c_l_literal_clip_mode_strips_final_newlines_but_keeps_one() {
1617 let reply = c_l_literal(0)(state("|\n hello\n\n\n"));
1618 assert!(is_success(&reply));
1619 let c = codes(c_l_literal(0)(state("|\n hello\n\n\n")));
1620 assert!(c.contains(&Code::LineFeed));
1621 }
1622
1623 #[test]
1624 fn c_l_literal_strip_mode_removes_all_trailing_newlines() {
1625 let reply = c_l_literal(0)(state("|-\n hello\n\n"));
1626 assert!(is_success(&reply));
1627 let c = codes(c_l_literal(0)(state("|-\n hello\n\n")));
1628 let text_pos = c.iter().rposition(|&x| x == Code::Text);
1630 if let Some(pos) = text_pos {
1631 assert!(!c[pos..].contains(&Code::LineFeed));
1632 }
1633 }
1634
1635 #[test]
1636 fn c_l_literal_keep_mode_retains_all_trailing_newlines() {
1637 let reply = c_l_literal(0)(state("|+\n hello\n\n\n"));
1638 assert!(is_success(&reply));
1639 let c = codes(c_l_literal(0)(state("|+\n hello\n\n\n")));
1641 assert!(c.contains(&Code::LineFeed) || c.contains(&Code::Break));
1642 }
1643
1644 #[test]
1645 fn c_l_literal_explicit_indentation_indicator() {
1646 let reply = c_l_literal(0)(state("|2\n hello\n world\n"));
1647 assert!(is_success(&reply));
1648 assert_eq!(remaining(&reply), "");
1649 }
1650
1651 #[test]
1652 fn c_l_literal_explicit_indent_does_not_consume_less_indented() {
1653 let reply = c_l_literal(0)(state("|2\n hello\n world\n"));
1654 assert!(is_success(&reply));
1655 assert!(remaining(&reply).contains("world"));
1656 }
1657
1658 #[test]
1659 fn c_l_literal_auto_detects_indentation_from_first_content_line() {
1660 let reply = c_l_literal(0)(state("|\n hello\n world\n"));
1661 assert!(is_success(&reply));
1662 assert_eq!(remaining(&reply), "");
1663 }
1664
1665 #[test]
1666 fn c_l_literal_empty_body_with_strip() {
1667 let reply = c_l_literal(0)(state("|-\n"));
1668 assert!(is_success(&reply));
1669 let c = codes(c_l_literal(0)(state("|-\n")));
1670 assert!(!c.contains(&Code::Text));
1671 assert!(!c.contains(&Code::LineFeed));
1672 }
1673
1674 #[test]
1675 fn c_l_literal_empty_body_with_clip() {
1676 let reply = c_l_literal(0)(state("|\n"));
1677 assert!(is_success(&reply));
1678 let c = codes(c_l_literal(0)(state("|\n")));
1679 assert!(!c.contains(&Code::Text));
1680 }
1681
1682 #[test]
1683 fn c_l_literal_empty_body_with_keep() {
1684 let reply = c_l_literal(0)(state("|+\n"));
1685 assert!(is_success(&reply));
1686 }
1687
1688 #[test]
1689 fn c_l_literal_preserves_internal_blank_lines() {
1690 let reply = c_l_literal(0)(state("|\n hello\n\n world\n"));
1691 assert!(is_success(&reply));
1692 let c = codes(c_l_literal(0)(state("|\n hello\n\n world\n")));
1693 assert!(c.contains(&Code::Text));
1694 }
1695
1696 #[test]
1697 fn c_l_literal_strip_chomp_with_explicit_indent() {
1698 let reply = c_l_literal(0)(state("|-2\n hello\n\n"));
1699 assert!(is_success(&reply));
1700 let c = codes(c_l_literal(0)(state("|-2\n hello\n\n")));
1701 let text_pos = c.iter().rposition(|&x| x == Code::Text);
1702 if let Some(pos) = text_pos {
1703 assert!(!c[pos..].contains(&Code::LineFeed));
1704 }
1705 }
1706
1707 #[test]
1708 fn c_l_literal_keep_chomp_with_explicit_indent() {
1709 let reply = c_l_literal(0)(state("|+2\n hello\n\n"));
1710 assert!(is_success(&reply));
1711 let c = codes(c_l_literal(0)(state("|+2\n hello\n\n")));
1712 assert!(c.contains(&Code::LineFeed) || c.contains(&Code::Break));
1713 }
1714
1715 #[test]
1716 fn c_l_literal_nested_at_n_equals_2() {
1717 let reply = c_l_literal(2)(state_with("|\n hello\n", 2, Context::BlockIn));
1718 assert!(is_success(&reply));
1719 }
1720
1721 #[test]
1722 fn c_l_literal_fails_at_non_pipe_character() {
1723 let reply = c_l_literal(0)(state(">\n hello\n"));
1724 assert!(is_failure(&reply));
1725 }
1726
1727 #[test]
1732 fn c_l_folded_accepts_simple_folded_scalar() {
1733 let reply = c_l_folded(0)(state(">\n hello\n"));
1734 assert!(is_success(&reply));
1735 let c = codes(c_l_folded(0)(state(">\n hello\n")));
1736 assert!(c.contains(&Code::BeginScalar));
1737 assert!(c.contains(&Code::EndScalar));
1738 }
1739
1740 #[test]
1741 fn c_l_folded_emits_indicator_for_gt() {
1742 let c = codes(c_l_folded(0)(state(">\n hello\n")));
1743 assert!(c.contains(&Code::Indicator));
1744 }
1745
1746 #[test]
1747 fn c_l_folded_folds_two_content_lines() {
1748 let reply = c_l_folded(0)(state(">\n hello\n world\n"));
1749 assert!(is_success(&reply));
1750 }
1751
1752 #[test]
1753 fn c_l_folded_clip_mode_keeps_one_trailing_newline() {
1754 let reply = c_l_folded(0)(state(">\n hello\n\n"));
1755 assert!(is_success(&reply));
1756 }
1757
1758 #[test]
1759 fn c_l_folded_strip_mode_removes_trailing_newlines() {
1760 let reply = c_l_folded(0)(state(">-\n hello\n\n"));
1761 assert!(is_success(&reply));
1762 let c = codes(c_l_folded(0)(state(">-\n hello\n\n")));
1763 let text_pos = c.iter().rposition(|&x| x == Code::Text);
1764 if let Some(pos) = text_pos {
1765 assert!(!c[pos..].contains(&Code::LineFeed));
1766 }
1767 }
1768
1769 #[test]
1770 fn c_l_folded_keep_mode_retains_trailing_newlines() {
1771 let reply = c_l_folded(0)(state(">+\n hello\n\n\n"));
1772 assert!(is_success(&reply));
1773 let c = codes(c_l_folded(0)(state(">+\n hello\n\n\n")));
1774 assert!(c.contains(&Code::LineFeed) || c.contains(&Code::Break));
1775 }
1776
1777 #[test]
1778 fn c_l_folded_spaced_lines_not_folded() {
1779 let reply = c_l_folded(0)(state(">\n hello\n\n world\n"));
1780 assert!(is_success(&reply));
1781 let c = codes(c_l_folded(0)(state(">\n hello\n\n world\n")));
1782 assert!(c.iter().filter(|&&x| x == Code::Text).count() >= 2);
1783 }
1784
1785 #[test]
1786 fn c_l_folded_more_indented_lines_not_folded() {
1787 let reply = c_l_folded(0)(state(">\n normal\n indented\n normal\n"));
1788 assert!(is_success(&reply));
1789 }
1790
1791 #[test]
1792 fn c_l_folded_explicit_indentation_indicator() {
1793 let reply = c_l_folded(0)(state(">2\n hello\n world\n"));
1794 assert!(is_success(&reply));
1795 assert_eq!(remaining(&reply), "");
1796 }
1797
1798 #[test]
1799 fn c_l_folded_auto_detects_indentation() {
1800 let reply = c_l_folded(0)(state(">\n hello\n world\n"));
1801 assert!(is_success(&reply));
1802 assert_eq!(remaining(&reply), "");
1803 }
1804
1805 #[test]
1806 fn c_l_folded_empty_body_with_strip() {
1807 let reply = c_l_folded(0)(state(">-\n"));
1808 assert!(is_success(&reply));
1809 let c = codes(c_l_folded(0)(state(">-\n")));
1810 assert!(!c.contains(&Code::Text));
1811 }
1812
1813 #[test]
1814 fn c_l_folded_empty_body_with_clip() {
1815 let reply = c_l_folded(0)(state(">\n"));
1816 assert!(is_success(&reply));
1817 }
1818
1819 #[test]
1820 fn c_l_folded_empty_body_with_keep() {
1821 let reply = c_l_folded(0)(state(">+\n"));
1822 assert!(is_success(&reply));
1823 }
1824
1825 #[test]
1826 fn c_l_folded_leaves_less_indented_content_unconsumed() {
1827 let reply = c_l_folded(0)(state(">\n hello\nrest\n"));
1828 assert!(is_success(&reply));
1829 assert!(remaining(&reply).starts_with("rest"));
1830 }
1831
1832 #[test]
1833 fn c_l_folded_strip_with_explicit_indent() {
1834 let reply = c_l_folded(0)(state(">-2\n hello\n\n"));
1835 assert!(is_success(&reply));
1836 let c = codes(c_l_folded(0)(state(">-2\n hello\n\n")));
1837 let text_pos = c.iter().rposition(|&x| x == Code::Text);
1838 if let Some(pos) = text_pos {
1839 assert!(!c[pos..].contains(&Code::LineFeed));
1840 }
1841 }
1842
1843 #[test]
1844 fn c_l_folded_keep_with_explicit_indent() {
1845 let reply = c_l_folded(0)(state(">+2\n hello\n\n"));
1846 assert!(is_success(&reply));
1847 let c = codes(c_l_folded(0)(state(">+2\n hello\n\n")));
1848 assert!(c.contains(&Code::LineFeed) || c.contains(&Code::Break));
1849 }
1850
1851 #[test]
1852 fn c_l_folded_fails_at_non_gt_character() {
1853 let reply = c_l_folded(0)(state("|\n hello\n"));
1854 assert!(is_failure(&reply));
1855 }
1856
1857 #[test]
1858 fn c_l_folded_nested_at_n_equals_2() {
1859 let reply = c_l_folded(2)(state_with(">\n hello\n", 2, Context::BlockIn));
1860 assert!(is_success(&reply));
1861 }
1862
1863 #[test]
1868 fn l_trail_comments_accepts_comment_at_lower_indent() {
1869 let reply = l_trail_comments(2)(state("# comment\n"));
1871 assert!(is_success(&reply));
1872 let c = codes(l_trail_comments(2)(state("# comment\n")));
1873 assert!(c.contains(&Code::BeginComment));
1874 }
1875
1876 #[test]
1877 fn l_trail_comments_accepts_multiple_comments() {
1878 let reply = l_trail_comments(2)(state("# one\n# two\n"));
1880 assert!(is_success(&reply));
1881 }
1882
1883 #[test]
1884 fn l_trail_comments_fails_on_non_comment() {
1885 let reply = l_trail_comments(2)(state("plaintext\n"));
1887 assert!(is_failure(&reply));
1888 }
1889
1890 #[test]
1891 fn l_strip_empty_consumes_blank_lines() {
1892 let reply = l_strip_empty(0)(state("\n\n\n"));
1893 assert!(is_success(&reply));
1894 assert_eq!(remaining(&reply), "");
1895 }
1896
1897 #[test]
1898 fn l_strip_empty_stops_before_non_blank() {
1899 let reply = l_strip_empty(0)(state("\n\ncontent"));
1900 assert!(is_success(&reply));
1901 assert_eq!(remaining(&reply), "content");
1902 }
1903
1904 #[test]
1905 fn l_keep_empty_consumes_blank_indented_lines() {
1906 let reply = l_keep_empty(2)(state("\n\n"));
1907 assert!(is_success(&reply));
1908 let c = codes(l_keep_empty(2)(state("\n\n")));
1909 assert!(c.contains(&Code::Break));
1910 }
1911
1912 #[test]
1913 fn l_chomped_empty_strip_consumes_only_blank_lines() {
1914 let reply = l_chomped_empty(0, Chomping::Strip)(state("\n\nrest"));
1915 assert!(is_success(&reply));
1916 let c = codes(l_chomped_empty(0, Chomping::Strip)(state("\n\nrest")));
1917 assert!(!c.contains(&Code::LineFeed));
1918 assert_eq!(
1919 remaining(&l_chomped_empty(0, Chomping::Strip)(state("\n\nrest"))),
1920 "rest"
1921 );
1922 }
1923
1924 #[test]
1925 fn l_chomped_empty_clip_consumes_nothing() {
1926 let reply = l_chomped_empty(0, Chomping::Clip)(state("\n"));
1928 assert!(is_success(&reply));
1929 }
1930
1931 #[test]
1932 fn l_chomped_empty_keep_emits_breaks() {
1933 let reply = l_chomped_empty(0, Chomping::Keep)(state("\n\n"));
1934 assert!(is_success(&reply));
1935 let c = codes(l_chomped_empty(0, Chomping::Keep)(state("\n\n")));
1936 assert!(c.contains(&Code::Break));
1937 }
1938
1939 #[test]
1940 fn b_chomped_last_strip_consumes_break() {
1941 let reply = b_chomped_last(Chomping::Strip)(state("\nrest"));
1942 assert!(is_success(&reply));
1943 assert_eq!(remaining(&reply), "rest");
1944 let c = codes(b_chomped_last(Chomping::Strip)(state("\nrest")));
1945 assert!(!c.contains(&Code::LineFeed));
1946 }
1947
1948 #[test]
1949 fn b_chomped_last_clip_emits_line_feed() {
1950 let reply = b_chomped_last(Chomping::Clip)(state("\nrest"));
1951 assert!(is_success(&reply));
1952 let c = codes(b_chomped_last(Chomping::Clip)(state("\nrest")));
1953 assert!(c.contains(&Code::LineFeed));
1954 assert_eq!(remaining(&reply), "rest");
1955 }
1956
1957 #[test]
1958 fn b_chomped_last_keep_emits_line_feed() {
1959 let reply = b_chomped_last(Chomping::Keep)(state("\nrest"));
1960 assert!(is_success(&reply));
1961 let c = codes(b_chomped_last(Chomping::Keep)(state("\nrest")));
1962 assert!(c.contains(&Code::LineFeed));
1963 assert_eq!(remaining(&reply), "rest");
1964 }
1965
1966 #[test]
1971 fn l_block_sequence_accepts_single_entry() {
1972 let reply = l_block_sequence(0)(state("- hello\n"));
1973 assert!(is_success(&reply));
1974 let c = codes(l_block_sequence(0)(state("- hello\n")));
1975 assert!(c.contains(&Code::BeginSequence));
1976 assert!(c.contains(&Code::EndSequence));
1977 }
1978
1979 #[test]
1980 fn l_block_sequence_accepts_multiple_entries() {
1981 let reply = l_block_sequence(0)(state("- hello\n- world\n"));
1982 assert!(is_success(&reply));
1983 assert_eq!(remaining(&reply), "");
1984 }
1985
1986 #[test]
1987 fn l_block_sequence_emits_indicator_for_dash() {
1988 let c = codes(l_block_sequence(0)(state("- hello\n")));
1989 assert!(c.contains(&Code::Indicator));
1990 }
1991
1992 #[test]
1993 fn l_block_sequence_stops_before_less_indented_line() {
1994 let reply = l_block_sequence(1)(state(" - hello\n - world\nrest\n"));
1996 assert!(is_success(&reply));
1997 assert!(remaining(&reply).starts_with("rest"));
1998 }
1999
2000 #[test]
2001 fn l_block_sequence_fails_when_no_dash_present() {
2002 let reply = l_block_sequence(0)(state("hello\n"));
2003 assert!(is_failure(&reply));
2004 }
2005
2006 #[test]
2007 fn c_l_block_seq_entry_accepts_block_scalar_value() {
2008 let reply = l_block_sequence(0)(state("- |\n hello\n"));
2009 assert!(is_success(&reply));
2010 let c = codes(l_block_sequence(0)(state("- |\n hello\n")));
2011 assert!(c.contains(&Code::BeginScalar));
2012 }
2013
2014 #[test]
2015 fn c_l_block_seq_entry_accepts_nested_sequence() {
2016 let reply = l_block_sequence(0)(state("- - hello\n"));
2017 assert!(is_success(&reply));
2018 }
2019
2020 #[test]
2021 fn c_l_block_seq_entry_accepts_empty_value() {
2022 let reply = l_block_sequence(0)(state("-\n"));
2023 assert!(is_success(&reply));
2024 }
2025
2026 #[test]
2027 fn s_b_block_indented_accepts_compact_sequence() {
2028 let reply = s_b_block_indented(0, Context::BlockIn)(state("- a\n- b\n"));
2029 assert!(is_success(&reply));
2030 }
2031
2032 #[test]
2033 fn s_b_block_indented_accepts_compact_mapping() {
2034 let reply = s_b_block_indented(0, Context::BlockIn)(state(" a: 1\n b: 2\n"));
2035 assert!(is_success(&reply));
2036 }
2037
2038 #[test]
2039 fn ns_l_compact_sequence_accepts_nested_entries() {
2040 let reply = ns_l_compact_sequence(0)(state("- a\n- b\n"));
2041 assert!(is_success(&reply));
2042 let c = codes(ns_l_compact_sequence(0)(state("- a\n- b\n")));
2043 assert!(c.contains(&Code::BeginSequence));
2044 }
2045
2046 #[test]
2047 fn l_block_sequence_accepts_block_mapping_entry() {
2048 let reply = l_block_sequence(0)(state("- a: 1\n b: 2\n"));
2049 assert!(is_success(&reply));
2050 let c = codes(l_block_sequence(0)(state("- a: 1\n b: 2\n")));
2051 assert!(c.contains(&Code::BeginMapping));
2052 }
2053
2054 #[test]
2055 fn l_block_sequence_multiline_entry_consumed() {
2056 let reply = l_block_sequence(0)(state("- |\n line1\n line2\n- next\n"));
2057 assert!(is_success(&reply));
2058 }
2059
2060 #[test]
2061 fn l_block_sequence_entry_with_flow_sequence_value() {
2062 let reply = l_block_sequence(0)(state("- [a, b]\n"));
2063 assert!(is_success(&reply));
2064 let c = codes(l_block_sequence(0)(state("- [a, b]\n")));
2065 assert!(c.contains(&Code::BeginSequence));
2066 }
2067
2068 #[test]
2069 fn l_block_sequence_entry_with_plain_scalar() {
2070 let reply = l_block_sequence(0)(state("- hello world\n"));
2071 assert!(is_success(&reply));
2072 let c = codes(l_block_sequence(0)(state("- hello world\n")));
2073 assert!(c.contains(&Code::Text));
2074 }
2075
2076 #[test]
2077 fn l_block_sequence_entry_with_anchor() {
2078 let reply = l_block_sequence(0)(state("- &anchor hello\n"));
2079 assert!(is_success(&reply));
2080 let c = codes(l_block_sequence(0)(state("- &anchor hello\n")));
2081 assert!(c.contains(&Code::BeginAnchor));
2082 }
2083
2084 #[test]
2089 fn l_block_mapping_accepts_single_implicit_entry() {
2090 let reply = l_block_mapping(0)(state("key: value\n"));
2091 assert!(is_success(&reply));
2092 let c = codes(l_block_mapping(0)(state("key: value\n")));
2093 assert!(c.contains(&Code::BeginMapping));
2094 assert!(c.contains(&Code::EndMapping));
2095 }
2096
2097 #[test]
2098 fn l_block_mapping_accepts_multiple_entries() {
2099 let reply = l_block_mapping(0)(state("a: 1\nb: 2\n"));
2100 assert!(is_success(&reply));
2101 assert_eq!(remaining(&reply), "");
2102 }
2103
2104 #[test]
2105 fn l_block_mapping_emits_begin_pair_per_entry() {
2106 let c = codes(l_block_mapping(0)(state("a: 1\nb: 2\n")));
2107 assert_eq!(c.iter().filter(|&&x| x == Code::BeginPair).count(), 2);
2108 }
2109
2110 #[test]
2111 fn l_block_mapping_emits_indicator_for_colon() {
2112 let c = codes(l_block_mapping(0)(state("key: value\n")));
2113 assert!(c.contains(&Code::Indicator));
2114 }
2115
2116 #[test]
2117 fn l_block_mapping_stops_before_less_indented_line() {
2118 let reply = l_block_mapping(1)(state(" a: 1\n b: 2\nrest\n"));
2120 assert!(is_success(&reply));
2121 assert!(remaining(&reply).starts_with("rest"));
2122 }
2123
2124 #[test]
2125 fn l_block_mapping_fails_when_no_key_present() {
2126 let reply = l_block_mapping(0)(state("- hello\n"));
2127 assert!(is_failure(&reply));
2128 }
2129
2130 #[test]
2131 fn ns_l_block_map_entry_accepts_explicit_key() {
2132 let reply = l_block_mapping(0)(state("? key\n: value\n"));
2133 assert!(is_success(&reply));
2134 let c = codes(l_block_mapping(0)(state("? key\n: value\n")));
2135 assert!(c.contains(&Code::Indicator));
2136 }
2137
2138 #[test]
2139 fn ns_l_block_map_entry_accepts_implicit_key() {
2140 let reply = l_block_mapping(0)(state("key: value\n"));
2141 assert!(is_success(&reply));
2142 }
2143
2144 #[test]
2145 fn c_l_block_map_explicit_key_emits_indicator() {
2146 let c = codes(l_block_mapping(0)(state("? key\n: value\n")));
2147 assert!(c.contains(&Code::Indicator));
2148 }
2149
2150 #[test]
2151 fn l_block_map_explicit_value_accepts_colon_value() {
2152 let reply = l_block_mapping(0)(state("? key\n: value\n"));
2153 assert!(is_success(&reply));
2154 let c = codes(l_block_mapping(0)(state("? key\n: value\n")));
2155 assert!(c.contains(&Code::Indicator));
2156 }
2157
2158 #[test]
2159 fn l_block_map_explicit_value_accepts_empty_value() {
2160 let reply = l_block_mapping(0)(state("? key\n:\n"));
2161 assert!(is_success(&reply));
2162 }
2163
2164 #[test]
2165 fn ns_s_block_map_implicit_key_accepts_plain_scalar() {
2166 let reply = ns_s_block_map_implicit_key()(state("key"));
2167 assert!(is_success(&reply));
2168 let c = codes(ns_s_block_map_implicit_key()(state("key")));
2169 assert!(c.contains(&Code::Text));
2170 }
2171
2172 #[test]
2173 fn ns_s_block_map_implicit_key_accepts_quoted_scalar() {
2174 let reply = ns_s_block_map_implicit_key()(state("\"key\""));
2175 assert!(is_success(&reply));
2176 let c = codes(ns_s_block_map_implicit_key()(state("\"key\"")));
2177 assert!(c.contains(&Code::BeginScalar));
2178 }
2179
2180 #[test]
2181 fn c_l_block_map_implicit_value_accepts_block_scalar() {
2182 let reply = l_block_mapping(0)(state("key: |\n content\n"));
2183 assert!(is_success(&reply));
2184 let c = codes(l_block_mapping(0)(state("key: |\n content\n")));
2185 assert!(c.contains(&Code::BeginScalar));
2186 }
2187
2188 #[test]
2189 fn c_l_block_map_implicit_value_accepts_plain_scalar() {
2190 let reply = l_block_mapping(0)(state("key: value\n"));
2191 assert!(is_success(&reply));
2192 let c = codes(l_block_mapping(0)(state("key: value\n")));
2193 assert!(c.contains(&Code::Text));
2194 }
2195
2196 #[test]
2197 fn c_l_block_map_implicit_value_accepts_empty_value() {
2198 let reply = l_block_mapping(0)(state("key:\n"));
2199 assert!(is_success(&reply));
2200 }
2201
2202 #[test]
2203 fn ns_l_compact_mapping_accepts_multiple_entries() {
2204 let reply = ns_l_compact_mapping(0)(state("a: 1\nb: 2\n"));
2205 assert!(is_success(&reply));
2206 let c = codes(ns_l_compact_mapping(0)(state("a: 1\nb: 2\n")));
2207 assert!(c.contains(&Code::BeginMapping));
2208 }
2209
2210 #[test]
2211 fn l_block_mapping_value_is_block_sequence() {
2212 let reply = l_block_mapping(0)(state("items:\n - a\n - b\n"));
2213 assert!(is_success(&reply));
2214 let c = codes(l_block_mapping(0)(state("items:\n - a\n - b\n")));
2215 assert!(c.contains(&Code::BeginSequence));
2216 }
2217
2218 #[test]
2219 fn l_block_mapping_value_is_nested_mapping() {
2220 let reply = l_block_mapping(0)(state("outer:\n inner: val\n"));
2221 assert!(is_success(&reply));
2222 let c = codes(l_block_mapping(0)(state("outer:\n inner: val\n")));
2223 assert!(c.iter().filter(|&&x| x == Code::BeginPair).count() >= 2);
2224 }
2225
2226 #[test]
2227 fn l_block_mapping_entry_with_anchor_on_key() {
2228 let reply = l_block_mapping(0)(state("&k key: value\n"));
2229 assert!(is_success(&reply));
2230 let c = codes(l_block_mapping(0)(state("&k key: value\n")));
2231 assert!(c.contains(&Code::BeginAnchor));
2232 }
2233
2234 #[test]
2239 fn s_l_block_node_accepts_literal_scalar_in_block_in() {
2240 let reply = s_l_block_node(0, Context::BlockIn)(state("|\n hello\n"));
2241 assert!(is_success(&reply));
2242 let c = codes(s_l_block_node(0, Context::BlockIn)(state("|\n hello\n")));
2243 assert!(c.contains(&Code::BeginScalar));
2244 }
2245
2246 #[test]
2247 fn s_l_block_node_accepts_folded_scalar_in_block_in() {
2248 let reply = s_l_block_node(0, Context::BlockIn)(state(">\n hello\n"));
2249 assert!(is_success(&reply));
2250 let c = codes(s_l_block_node(0, Context::BlockIn)(state(">\n hello\n")));
2251 assert!(c.contains(&Code::BeginScalar));
2252 }
2253
2254 #[test]
2255 fn s_l_block_node_accepts_block_sequence_in_block_out() {
2256 let reply = s_l_block_node(0, Context::BlockOut)(state("\n- hello\n"));
2257 assert!(is_success(&reply));
2258 let c = codes(s_l_block_node(0, Context::BlockOut)(state("\n- hello\n")));
2259 assert!(c.contains(&Code::BeginSequence));
2260 }
2261
2262 #[test]
2263 fn s_l_block_node_accepts_block_mapping_in_block_out() {
2264 let reply = s_l_block_node(0, Context::BlockOut)(state("\n key: value\n"));
2266 assert!(is_success(&reply));
2267 let c = codes(s_l_block_node(0, Context::BlockOut)(state(
2268 "\n key: value\n",
2269 )));
2270 assert!(c.contains(&Code::BeginMapping));
2271 }
2272
2273 #[test]
2274 fn s_l_flow_in_block_accepts_flow_sequence() {
2275 let reply = s_l_flow_in_block(0)(state("\n [a, b]\n"));
2277 assert!(is_success(&reply));
2278 let c = codes(s_l_flow_in_block(0)(state("\n [a, b]\n")));
2279 assert!(c.contains(&Code::BeginSequence));
2280 }
2281
2282 #[test]
2283 fn s_l_flow_in_block_accepts_flow_mapping() {
2284 let reply = s_l_flow_in_block(0)(state("\n {a: b}\n"));
2285 assert!(is_success(&reply));
2286 let c = codes(s_l_flow_in_block(0)(state("\n {a: b}\n")));
2287 assert!(c.contains(&Code::BeginMapping));
2288 }
2289
2290 #[test]
2291 fn s_l_flow_in_block_accepts_double_quoted_scalar() {
2292 let reply = s_l_flow_in_block(0)(state("\n \"hello\"\n"));
2293 assert!(is_success(&reply));
2294 let c = codes(s_l_flow_in_block(0)(state("\n \"hello\"\n")));
2295 assert!(c.contains(&Code::BeginScalar));
2296 }
2297
2298 #[test]
2299 fn s_l_block_scalar_accepts_literal_scalar() {
2300 let reply = s_l_block_scalar(0, Context::BlockIn)(state("|\n hello\n"));
2301 assert!(is_success(&reply));
2302 let c = codes(s_l_block_scalar(0, Context::BlockIn)(state("|\n hello\n")));
2303 assert!(c.contains(&Code::BeginScalar));
2304 }
2305
2306 #[test]
2307 fn s_l_block_scalar_accepts_folded_scalar() {
2308 let reply = s_l_block_scalar(0, Context::BlockIn)(state(">\n hello\n"));
2309 assert!(is_success(&reply));
2310 let c = codes(s_l_block_scalar(0, Context::BlockIn)(state(">\n hello\n")));
2311 assert!(c.contains(&Code::BeginScalar));
2312 }
2313
2314 #[test]
2315 fn s_l_block_collection_accepts_block_sequence() {
2316 let reply = s_l_block_collection(0, Context::BlockOut)(state("\n- a\n- b\n"));
2317 assert!(is_success(&reply));
2318 let c = codes(s_l_block_collection(0, Context::BlockOut)(state(
2319 "\n- a\n- b\n",
2320 )));
2321 assert!(c.contains(&Code::BeginSequence));
2322 }
2323
2324 #[test]
2325 fn s_l_block_collection_accepts_block_mapping() {
2326 let reply = s_l_block_collection(0, Context::BlockOut)(state("\n a: 1\n b: 2\n"));
2328 assert!(is_success(&reply));
2329 let c = codes(s_l_block_collection(0, Context::BlockOut)(state(
2330 "\n a: 1\n b: 2\n",
2331 )));
2332 assert!(c.contains(&Code::BeginMapping));
2333 }
2334
2335 #[test]
2336 fn seq_spaces_block_out_uses_n_minus_1() {
2337 let reply = l_block_sequence(0)(state_with("- hello\n", 0, Context::BlockOut));
2339 assert!(is_success(&reply));
2340 }
2341
2342 #[test]
2343 fn seq_spaces_block_in_uses_n() {
2344 let reply = l_block_sequence(0)(state_with("- hello\n", 0, Context::BlockIn));
2346 assert!(is_success(&reply));
2347 }
2348
2349 #[test]
2350 fn s_l_block_in_block_accepts_block_scalar_content() {
2351 let reply = s_l_block_in_block(0, Context::BlockOut)(state("|\n hello\n"));
2352 assert!(is_success(&reply));
2353 let c = codes(s_l_block_in_block(0, Context::BlockOut)(state(
2354 "|\n hello\n",
2355 )));
2356 assert!(c.contains(&Code::BeginScalar));
2357 }
2358
2359 #[test]
2364 fn s_l_block_scalar_accepts_anchor_before_literal() {
2365 let reply = s_l_block_scalar(0, Context::BlockIn)(state("&a |\n hello\n"));
2366 assert!(is_success(&reply));
2367 let c = codes(s_l_block_scalar(0, Context::BlockIn)(state(
2368 "&a |\n hello\n",
2369 )));
2370 assert!(c.contains(&Code::BeginAnchor));
2371 assert!(c.contains(&Code::BeginScalar));
2372 }
2373
2374 #[test]
2375 fn s_l_block_scalar_accepts_tag_before_folded() {
2376 let reply = s_l_block_scalar(0, Context::BlockIn)(state("!!str >\n hello\n"));
2377 assert!(is_success(&reply));
2378 let c = codes(s_l_block_scalar(0, Context::BlockIn)(state(
2379 "!!str >\n hello\n",
2380 )));
2381 assert!(c.contains(&Code::BeginTag));
2382 assert!(c.contains(&Code::BeginScalar));
2383 }
2384
2385 #[test]
2386 fn l_block_mapping_accepts_tagged_value() {
2387 let reply = l_block_mapping(0)(state("key: !!str value\n"));
2388 assert!(is_success(&reply));
2389 let c = codes(l_block_mapping(0)(state("key: !!str value\n")));
2390 assert!(c.contains(&Code::BeginTag));
2391 }
2392
2393 #[test]
2394 fn l_block_sequence_accepts_anchored_entry() {
2395 let reply = l_block_sequence(0)(state("- &a hello\n"));
2396 assert!(is_success(&reply));
2397 let c = codes(l_block_sequence(0)(state("- &a hello\n")));
2398 assert!(c.contains(&Code::BeginAnchor));
2399 }
2400
2401 #[test]
2402 fn l_block_mapping_accepts_alias_as_value() {
2403 let reply = l_block_mapping(0)(state("key: *anchor\n"));
2404 assert!(is_success(&reply));
2405 let c = codes(l_block_mapping(0)(state("key: *anchor\n")));
2406 assert!(c.contains(&Code::BeginAlias));
2407 }
2408
2409 #[test]
2414 fn detect_indentation_skips_leading_blank_lines() {
2415 let reply = c_l_literal(0)(state("|\n\n hello\n"));
2416 assert!(is_success(&reply));
2417 let c = codes(c_l_literal(0)(state("|\n\n hello\n")));
2418 assert!(c.contains(&Code::Text));
2419 }
2420
2421 #[test]
2422 fn detect_indentation_uses_first_non_empty_line() {
2423 let reply = c_l_literal(0)(state("|\n\n\n hello\n"));
2424 assert!(is_success(&reply));
2425 assert_eq!(remaining(&reply), "");
2426 }
2427
2428 #[test]
2429 fn detect_indentation_minimum_is_n_plus_1() {
2430 let reply = c_l_literal(2)(state("|\n hello\n"));
2432 assert!(is_success(&reply));
2433 }
2434
2435 #[test]
2436 fn detect_indentation_rejects_content_at_n_or_less() {
2437 let reply = c_l_literal(2)(state("|\n hello\n"));
2439 if is_success(&reply) {
2441 assert!(remaining(&reply).contains("hello"));
2442 }
2443 }
2444
2445 #[test]
2446 fn detect_indentation_all_blank_body_succeeds() {
2447 let reply = c_l_literal(0)(state("|\n\n"));
2448 assert!(is_success(&reply));
2449 }
2450
2451 #[test]
2452 fn detect_indentation_with_tab_in_blank_line_ignored() {
2453 let reply = c_l_literal(0)(state("|\n\t\n hello\n"));
2455 assert!(is_success(&reply));
2456 }
2457
2458 #[test]
2463 fn literal_clip_single_trailing_newline() {
2464 let reply = c_l_literal(0)(state("|\n hello\n\n"));
2465 assert!(is_success(&reply));
2466 let c = codes(c_l_literal(0)(state("|\n hello\n\n")));
2467 let text_pos = c.iter().rposition(|&x| x == Code::Text);
2469 let lf_count = text_pos.map_or(0, |pos| {
2470 c[pos..].iter().filter(|&&x| x == Code::LineFeed).count()
2471 });
2472 assert_eq!(lf_count, 1);
2473 }
2474
2475 #[test]
2476 fn literal_strip_no_trailing_newline() {
2477 let reply = c_l_literal(0)(state("|-\n hello\n"));
2478 assert!(is_success(&reply));
2479 let c = codes(c_l_literal(0)(state("|-\n hello\n")));
2480 let text_pos = c.iter().rposition(|&x| x == Code::Text);
2481 if let Some(pos) = text_pos {
2482 assert!(!c[pos..].contains(&Code::LineFeed));
2483 }
2484 }
2485
2486 #[test]
2487 fn literal_keep_multiple_trailing_newlines() {
2488 let reply = c_l_literal(0)(state("|+\n hello\n\n\n"));
2489 assert!(is_success(&reply));
2490 let c = codes(c_l_literal(0)(state("|+\n hello\n\n\n")));
2491 let break_count = c
2492 .iter()
2493 .filter(|&&x| x == Code::LineFeed || x == Code::Break)
2494 .count();
2495 assert!(break_count >= 2);
2496 }
2497
2498 #[test]
2499 fn folded_clip_single_trailing_newline() {
2500 let reply = c_l_folded(0)(state(">\n hello\n\n"));
2501 assert!(is_success(&reply));
2502 }
2503
2504 #[test]
2505 fn folded_strip_no_trailing_newline() {
2506 let reply = c_l_folded(0)(state(">-\n hello\n"));
2507 assert!(is_success(&reply));
2508 let c = codes(c_l_folded(0)(state(">-\n hello\n")));
2509 let text_pos = c.iter().rposition(|&x| x == Code::Text);
2510 if let Some(pos) = text_pos {
2511 assert!(!c[pos..].contains(&Code::LineFeed));
2512 }
2513 }
2514
2515 #[test]
2516 fn folded_keep_multiple_trailing_newlines() {
2517 let reply = c_l_folded(0)(state(">+\n hello\n\n\n"));
2518 assert!(is_success(&reply));
2519 let c = codes(c_l_folded(0)(state(">+\n hello\n\n\n")));
2520 let break_count = c
2521 .iter()
2522 .filter(|&&x| x == Code::LineFeed || x == Code::Break)
2523 .count();
2524 assert!(break_count >= 2);
2525 }
2526
2527 #[test]
2528 fn literal_strip_empty_body_no_tokens_after_scalar_begin() {
2529 let reply = c_l_literal(0)(state("|-\n\n"));
2530 assert!(is_success(&reply));
2531 let c = codes(c_l_literal(0)(state("|-\n\n")));
2532 assert!(!c.contains(&Code::Text));
2533 }
2534
2535 #[test]
2536 fn folded_strip_empty_body_no_tokens_after_scalar_begin() {
2537 let reply = c_l_folded(0)(state(">-\n\n"));
2538 assert!(is_success(&reply));
2539 let c = codes(c_l_folded(0)(state(">-\n\n")));
2540 assert!(!c.contains(&Code::Text));
2541 }
2542}