1use crate::{
4 error_recovery::{ErrorBuilder, ErrorRecoveryContext, ParseContext, RecoveryStrategy},
5 lex::{lex, SyntaxKind},
6 parse::Parse,
7 ParseErrorKind, PositionedParseError,
8};
9use rowan::ast::AstNode;
10use rowan::GreenNodeBuilder;
11use std::path::Path;
12use std::str::FromStr;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub(crate) struct ParsedYaml {
21 pub(crate) green_node: rowan::GreenNode,
23 pub(crate) errors: Vec<String>,
25 pub(crate) positioned_errors: Vec<PositionedParseError>,
27}
28
29use crate::nodes::ast_node;
31pub use crate::nodes::{Lang, SyntaxNode};
32
33pub use crate::nodes::{
35 Alias, Comment, Directive, Document, Mapping, MappingEntry, Scalar, ScalarConversionError,
36 Sequence, TaggedNode,
37};
38
39ast_node!(
40 YamlFile,
41 ROOT,
42 "A YAML file containing one or more documents"
43);
44
45impl YamlFile {
46 pub fn snapshot(&self) -> Self {
53 YamlFile(SyntaxNode::new_root_mut(self.0.green().into_owned()))
54 }
55
56 pub fn tree_eq(&self, other: &Self) -> bool {
60 let a = self.0.green();
61 let b = other.0.green();
62 let a_ref: &rowan::GreenNodeData = &a;
63 let b_ref: &rowan::GreenNodeData = &b;
64 std::ptr::eq(a_ref as *const _, b_ref as *const _) || a_ref == b_ref
65 }
66}
67
68pub trait ValueNode: rowan::ast::AstNode<Language = Lang> {
70 fn is_inline(&self) -> bool;
72}
73
74impl ValueNode for Mapping {
75 fn is_inline(&self) -> bool {
76 if self.0.children_with_tokens().any(|c| {
78 c.as_token()
79 .map(|t| t.kind() == SyntaxKind::LEFT_BRACE || t.kind() == SyntaxKind::RIGHT_BRACE)
80 .unwrap_or(false)
81 }) {
82 return true;
83 }
84 false
85 }
86}
87
88impl ValueNode for Sequence {
89 fn is_inline(&self) -> bool {
90 if self.0.children_with_tokens().any(|c| {
92 c.as_token()
93 .map(|t| {
94 t.kind() == SyntaxKind::LEFT_BRACKET || t.kind() == SyntaxKind::RIGHT_BRACKET
95 })
96 .unwrap_or(false)
97 }) {
98 return true;
99 }
100 false
101 }
102}
103
104impl ValueNode for Scalar {
105 fn is_inline(&self) -> bool {
106 true
108 }
109}
110
111pub(crate) fn ends_with_newline(node: &SyntaxNode) -> bool {
115 node.last_token()
116 .map(|t| t.kind() == SyntaxKind::NEWLINE)
117 .unwrap_or(false)
118}
119
120pub(crate) fn add_newline_token(
122 elements: &mut Vec<rowan::NodeOrToken<rowan::SyntaxNode<Lang>, rowan::SyntaxToken<Lang>>>,
123) {
124 let mut nl_builder = rowan::GreenNodeBuilder::new();
125 nl_builder.start_node(SyntaxKind::ROOT.into());
126 nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
127 nl_builder.finish_node();
128 let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
129 if let Some(token) = nl_node.first_token() {
130 elements.push(token.into());
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub struct Set(SyntaxNode);
137
138impl Set {
139 pub fn cast(node: SyntaxNode) -> Option<Self> {
141 if node.kind() == SyntaxKind::TAGGED_NODE {
142 if let Some(tagged_node) = TaggedNode::cast(node.clone()) {
143 if tagged_node.tag().as_deref() == Some("!!set") {
144 return Some(Set(node));
145 }
146 }
147 }
148 None
149 }
150
151 fn inner_mapping(&self) -> Option<Mapping> {
153 self.0.children().find_map(Mapping::cast)
154 }
155
156 pub fn members(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
169 self.inner_mapping().into_iter().flat_map(|m| {
183 let mapping_node = m.syntax().clone();
184 let has_entries = mapping_node
185 .children()
186 .any(|n| n.kind() == SyntaxKind::MAPPING_ENTRY);
187
188 if has_entries {
189 mapping_node
191 .children()
192 .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
193 .filter_map(|entry| {
194 entry
195 .children()
196 .find(|n| n.kind() == SyntaxKind::KEY)
197 .and_then(|key| key.children().next())
198 .and_then(crate::as_yaml::YamlNode::from_syntax)
199 })
200 .collect::<Vec<_>>()
201 } else {
202 mapping_node
204 .children()
205 .filter(|n| n.kind() == SyntaxKind::KEY)
206 .filter_map(|key| {
207 key.children()
208 .next()
209 .and_then(crate::as_yaml::YamlNode::from_syntax)
210 })
211 .collect::<Vec<_>>()
212 }
213 })
214 }
215
216 pub fn len(&self) -> usize {
218 self.members().count()
219 }
220
221 pub fn is_empty(&self) -> bool {
223 self.members().next().is_none()
224 }
225
226 pub fn contains(&self, value: impl crate::AsYaml) -> bool {
228 self.members()
229 .any(|member| crate::as_yaml::yaml_eq(&member, &value))
230 }
231}
232
233fn extract_content_node(wrapper: &SyntaxNode) -> Option<SyntaxNode> {
237 use crate::lex::SyntaxKind;
238 match wrapper.kind() {
239 SyntaxKind::VALUE | SyntaxKind::KEY => wrapper.children().next(),
240 _ => Some(wrapper.clone()),
241 }
242}
243
244fn smart_cast<T: AstNode<Language = Lang>>(node: SyntaxNode) -> Option<T> {
246 if let Some(content) = extract_content_node(&node) {
247 T::cast(content)
248 } else {
249 None
250 }
251}
252
253pub(crate) fn extract_scalar(node: &SyntaxNode) -> Option<Scalar> {
255 smart_cast(node.clone())
256}
257
258pub(crate) fn extract_mapping(node: &SyntaxNode) -> Option<Mapping> {
260 smart_cast(node.clone())
261}
262
263pub(crate) fn extract_sequence(node: &SyntaxNode) -> Option<Sequence> {
265 smart_cast(node.clone())
266}
267
268pub(crate) fn extract_tagged_node(node: &SyntaxNode) -> Option<TaggedNode> {
270 smart_cast(node.clone())
271}
272
273pub(crate) fn copy_node_to_builder(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
275 builder.start_node(node.kind().into());
276 add_node_children_to(builder, node);
277 builder.finish_node();
278}
279
280pub(crate) fn add_node_children_to(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
282 for child in node.children_with_tokens() {
283 match child {
284 rowan::NodeOrToken::Node(child_node) => {
285 builder.start_node(child_node.kind().into());
286 add_node_children_to(builder, &child_node);
287 builder.finish_node();
288 }
289 rowan::NodeOrToken::Token(token) => {
290 builder.token(token.kind().into(), token.text());
291 }
292 }
293 }
294}
295
296pub(crate) fn dump_cst_to_string(node: &SyntaxNode, indent: usize) -> String {
298 let mut result = String::new();
299 let indent_str = " ".repeat(indent);
300
301 for child in node.children_with_tokens() {
302 match child {
303 rowan::NodeOrToken::Node(n) => {
304 result.push_str(&format!("{}{:?}\n", indent_str, n.kind()));
305 result.push_str(&dump_cst_to_string(&n, indent + 1));
306 }
307 rowan::NodeOrToken::Token(t) => {
308 result.push_str(&format!("{} {:?} {:?}\n", indent_str, t.kind(), t.text()));
309 }
310 }
311 }
312 result
313}
314
315impl Default for YamlFile {
316 fn default() -> Self {
317 Self::new()
318 }
319}
320
321impl YamlFile {
322 pub fn new() -> YamlFile {
324 let mut builder = GreenNodeBuilder::new();
325 builder.start_node(SyntaxKind::ROOT.into());
326 builder.finish_node();
327 YamlFile(SyntaxNode::new_root_mut(builder.finish()))
328 }
329
330 pub fn parse(text: &str) -> Parse<YamlFile> {
332 Parse::parse_yaml(text)
333 }
334
335 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<YamlFile, crate::YamlError> {
337 let contents = std::fs::read_to_string(path)?;
338 Self::from_str(&contents)
339 }
340
341 pub fn from_str_relaxed(s: &str) -> (YamlFile, Vec<String>) {
357 let parsed = YamlFile::parse(s);
358 let errors = parsed.errors();
359 (parsed.tree(), errors)
360 }
361
362 pub fn from_path_relaxed<P: AsRef<Path>>(
367 path: P,
368 ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
369 let contents = std::fs::read_to_string(path)?;
370 Ok(Self::from_str_relaxed(&contents))
371 }
372
373 pub fn read_relaxed<R: std::io::Read>(
378 mut r: R,
379 ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
380 let mut buf = String::new();
381 r.read_to_string(&mut buf)?;
382 Ok(Self::from_str_relaxed(&buf))
383 }
384
385 pub fn documents(&self) -> impl Iterator<Item = Document> {
387 self.0.children().filter_map(Document::cast)
388 }
389
390 pub fn comments(&self) -> impl Iterator<Item = Comment> {
405 crate::nodes::comment::comments(&self.0)
406 }
407
408 pub fn document(&self) -> Option<Document> {
413 self.documents().next()
414 }
415
416 pub fn ensure_document(&self) -> Document {
420 if self.documents().next().is_none() {
421 let doc = Document::new_mapping();
423 self.push_document(doc);
424 }
425 self.documents()
426 .next()
427 .expect("Document should exist after ensuring")
428 }
429
430 pub fn directives(&self) -> impl Iterator<Item = Directive> {
432 self.0.children().filter_map(Directive::cast)
433 }
434
435 pub fn add_directive(&self, directive_text: &str) {
444 let mut builder = GreenNodeBuilder::new();
446 builder.start_node(SyntaxKind::DIRECTIVE.into());
447 builder.token(SyntaxKind::DIRECTIVE.into(), directive_text);
448 builder.finish_node();
449 let directive_node = SyntaxNode::new_root_mut(builder.finish());
450
451 self.0.splice_children(0..0, vec![directive_node.into()]);
453 }
454
455 pub fn push_document(&self, document: Document) {
457 let children_count = self.0.children_with_tokens().count();
458
459 self.0
461 .splice_children(children_count..children_count, vec![document.0.into()]);
462 }
463
464 pub fn set(&self, key: impl crate::AsYaml, value: impl crate::AsYaml) {
473 if let Some(doc) = self.document() {
474 doc.set(key, value);
475 }
476 }
477
478 pub fn insert_after(
487 &self,
488 after_key: impl crate::AsYaml,
489 key: impl crate::AsYaml,
490 value: impl crate::AsYaml,
491 ) -> bool {
492 if let Some(doc) = self.document() {
493 doc.insert_after(after_key, key, value)
494 } else {
495 false
496 }
497 }
498
499 pub fn insert_before(
508 &self,
509 before_key: impl crate::AsYaml,
510 key: impl crate::AsYaml,
511 value: impl crate::AsYaml,
512 ) -> bool {
513 if let Some(doc) = self.document() {
514 doc.insert_before(before_key, key, value)
515 } else {
516 false
517 }
518 }
519
520 pub fn move_after(
531 &self,
532 after_key: impl crate::AsYaml,
533 key: impl crate::AsYaml,
534 value: impl crate::AsYaml,
535 ) -> bool {
536 if let Some(doc) = self.document() {
537 doc.move_after(after_key, key, value)
538 } else {
539 false
540 }
541 }
542
543 pub fn move_before(
554 &self,
555 before_key: impl crate::AsYaml,
556 key: impl crate::AsYaml,
557 value: impl crate::AsYaml,
558 ) -> bool {
559 if let Some(doc) = self.document() {
560 doc.move_before(before_key, key, value)
561 } else {
562 false
563 }
564 }
565
566 pub fn insert_at_index(
575 &self,
576 index: usize,
577 key: impl crate::AsYaml,
578 value: impl crate::AsYaml,
579 ) {
580 if let Some(doc) = self.document() {
581 doc.insert_at_index(index, key, value);
582 } else {
584 let mut builder = GreenNodeBuilder::new();
587 builder.start_node(SyntaxKind::DOCUMENT.into());
588 builder.start_node(SyntaxKind::MAPPING.into());
589 builder.finish_node(); builder.finish_node(); let doc = Document(SyntaxNode::new_root_mut(builder.finish()));
592
593 self.0.splice_children(0..0, vec![doc.0.into()]);
595
596 if let Some(doc) = self.document() {
598 doc.insert_at_index(index, key, value);
599 }
600 }
601 }
602}
603
604impl FromStr for YamlFile {
605 type Err = crate::YamlError;
606
607 fn from_str(s: &str) -> Result<Self, Self::Err> {
608 let parsed = YamlFile::parse(s);
609 if !parsed.positioned_errors().is_empty() {
610 let first = &parsed.positioned_errors()[0];
611 let lc = crate::byte_offset_to_line_column(s, first.range.start as usize);
612 return Err(crate::YamlError::Parse {
613 message: first.message.clone(),
614 line: Some(lc.line),
615 column: Some(lc.column),
616 });
617 }
618 Ok(parsed.tree())
619 }
620}
621
622const MAX_FLOW_DEPTH: usize = 256;
626
627struct Parser {
629 tokens: Vec<(SyntaxKind, String)>,
630 current_token_index: usize,
631 builder: GreenNodeBuilder<'static>,
632 errors: Vec<String>,
633 positioned_errors: Vec<PositionedParseError>,
634 in_flow_context: bool,
635 error_context: ErrorRecoveryContext,
637 in_value_context: bool,
639 current_line_indent: usize,
641 flow_depth: usize,
643}
644
645impl Parser {
646 fn new(text: &str) -> Self {
647 let lexed = lex(text);
648 let mut tokens = Vec::new();
649
650 for (kind, token_text) in lexed {
651 tokens.push((kind, token_text.to_string()));
652 }
653
654 let token_count = tokens.len();
656 tokens.reverse();
657
658 Self {
659 tokens,
660 current_token_index: token_count,
661 builder: GreenNodeBuilder::new(),
662 errors: Vec::new(),
663 positioned_errors: Vec::new(),
664 in_flow_context: false,
665 error_context: ErrorRecoveryContext::new(text.to_string()),
666 in_value_context: false,
667 current_line_indent: 0,
668 flow_depth: 0,
669 }
670 }
671
672 fn parse(mut self) -> ParsedYaml {
673 self.builder.start_node(SyntaxKind::ROOT.into());
674
675 if self.current() == Some(SyntaxKind::BOM) {
678 self.bump(); }
680
681 self.skip_ws_and_newlines();
682
683 while self.current() == Some(SyntaxKind::DIRECTIVE) {
685 self.parse_directive();
686 self.skip_ws_and_newlines();
687 }
688
689 if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
692 self.parse_document();
693 self.skip_ws_and_newlines();
694
695 while self.current() == Some(SyntaxKind::DOC_START)
697 || self.current() == Some(SyntaxKind::DIRECTIVE)
698 {
699 while self.current() == Some(SyntaxKind::DIRECTIVE) {
701 self.parse_directive();
702 self.skip_ws_and_newlines();
703 }
704
705 if self.current() == Some(SyntaxKind::DOC_START)
707 || (self.current().is_some() && self.current() != Some(SyntaxKind::EOF))
708 {
709 self.parse_document();
710 self.skip_ws_and_newlines();
711 } else {
712 break;
713 }
714 }
715 }
716
717 while self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
720 self.builder.start_node(SyntaxKind::ERROR.into());
721
722 while self.current().is_some()
724 && self.current() != Some(SyntaxKind::EOF)
725 && self.current() != Some(SyntaxKind::DOC_START)
726 && self.current() != Some(SyntaxKind::DIRECTIVE)
727 {
728 self.bump();
729 }
730
731 self.builder.finish_node();
732
733 if self.current() == Some(SyntaxKind::DOC_START)
735 || self.current() == Some(SyntaxKind::DIRECTIVE)
736 {
737 while self.current() == Some(SyntaxKind::DIRECTIVE) {
739 self.parse_directive();
740 self.skip_ws_and_newlines();
741 }
742
743 if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
745 self.parse_document();
746 self.skip_ws_and_newlines();
747 }
748 }
749 }
750
751 self.builder.finish_node();
752
753 ParsedYaml {
754 green_node: self.builder.finish(),
755 errors: self.errors,
756 positioned_errors: self.positioned_errors,
757 }
758 }
759
760 fn parse_document(&mut self) {
761 self.builder.start_node(SyntaxKind::DOCUMENT.into());
762
763 if self.current() == Some(SyntaxKind::DOC_START) {
765 self.bump();
766 self.skip_ws_and_newlines();
767 }
768
769 if self.current().is_some()
771 && self.current() != Some(SyntaxKind::DOC_END)
772 && self.current() != Some(SyntaxKind::DOC_START)
773 {
774 self.parse_value();
775 }
776
777 if self.current() == Some(SyntaxKind::DOC_END) {
779 self.bump();
780
781 self.skip_whitespace();
783 if self.current().is_some()
784 && self.current() != Some(SyntaxKind::NEWLINE)
785 && self.current() != Some(SyntaxKind::EOF)
786 && self.current() != Some(SyntaxKind::DOC_START)
787 && self.current() != Some(SyntaxKind::DIRECTIVE)
788 {
789 self.builder.start_node(SyntaxKind::ERROR.into());
791 while self.current().is_some()
792 && self.current() != Some(SyntaxKind::NEWLINE)
793 && self.current() != Some(SyntaxKind::EOF)
794 && self.current() != Some(SyntaxKind::DOC_START)
795 && self.current() != Some(SyntaxKind::DIRECTIVE)
796 {
797 self.bump();
798 }
799 self.builder.finish_node();
800 }
801 }
802
803 self.builder.finish_node();
804 }
805
806 fn parse_value(&mut self) {
807 self.parse_value_with_base_indent(0);
808 }
809
810 fn parse_value_with_base_indent(&mut self, base_indent: usize) {
811 match self.current() {
812 Some(SyntaxKind::COMMENT) => {
813 self.bump(); self.skip_ws_and_newlines(); self.parse_value_with_base_indent(base_indent);
818 }
819 Some(SyntaxKind::DASH) if !self.in_flow_context => {
820 self.parse_sequence_with_base_indent(base_indent)
821 }
822 Some(SyntaxKind::ANCHOR) => {
823 self.bump(); self.skip_whitespace();
825 self.parse_value_with_base_indent(base_indent);
826 }
827 Some(SyntaxKind::REFERENCE) => self.parse_alias(),
828 Some(SyntaxKind::TAG) => self.parse_tagged_value(),
829 Some(SyntaxKind::MERGE_KEY) => {
830 self.parse_mapping_with_base_indent(base_indent);
832 }
833 Some(SyntaxKind::QUESTION) => {
834 self.parse_explicit_key_mapping();
836 }
837 Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
838 Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
839 Some(
840 SyntaxKind::STRING
841 | SyntaxKind::INT
842 | SyntaxKind::FLOAT
843 | SyntaxKind::BOOL
844 | SyntaxKind::NULL,
845 ) => {
846 if !self.in_flow_context && !self.in_value_context && self.is_mapping_key() {
850 self.parse_mapping_with_base_indent(base_indent);
851 } else {
852 self.parse_scalar();
853 }
854 }
855 Some(SyntaxKind::LEFT_BRACKET) => {
856 if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
859 {
860 self.parse_complex_key_mapping();
861 } else {
862 self.parse_flow_sequence();
863 }
864 }
865 Some(SyntaxKind::LEFT_BRACE) => {
866 if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
869 {
870 self.parse_complex_key_mapping();
871 } else {
872 self.parse_flow_mapping();
873 }
874 }
875 Some(SyntaxKind::INDENT) => {
876 self.bump(); self.parse_value(); }
880 Some(SyntaxKind::NEWLINE) => {
881 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
884 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
885 self.bump(); self.parse_value_with_base_indent(indent_level);
887 } else {
888 self.builder.start_node(SyntaxKind::SCALAR.into());
890 self.builder.finish_node();
891 }
892 }
893 _ => self.parse_scalar(),
894 }
895 }
896
897 fn parse_alias(&mut self) {
898 self.builder.start_node(SyntaxKind::ALIAS.into());
901 if self.current() == Some(SyntaxKind::REFERENCE) {
902 self.bump(); }
904 self.builder.finish_node();
905 }
906
907 fn parse_scalar(&mut self) {
908 self.builder.start_node(SyntaxKind::SCALAR.into());
909
910 if matches!(
912 self.current(),
913 Some(SyntaxKind::QUOTE | SyntaxKind::SINGLE_QUOTE)
914 ) {
915 let quote_type = self
916 .current()
917 .expect("current token is Some: checked by matches! guard above");
918 self.bump(); while self.current().is_some() && self.current() != Some(quote_type) {
922 self.bump();
923 }
924
925 if self.current() == Some(quote_type) {
926 self.bump(); } else {
928 let expected_quote = if quote_type == SyntaxKind::QUOTE {
929 "\""
930 } else {
931 "'"
932 };
933 let error_msg = self.create_detailed_error(
934 "Unterminated quoted string",
935 &format!("closing quote {}", expected_quote),
936 self.current_text(),
937 );
938 self.add_error_and_recover(
939 error_msg,
940 quote_type,
941 ParseErrorKind::UnterminatedString,
942 );
943 }
944 } else {
945 if matches!(
947 self.current(),
948 Some(
949 SyntaxKind::STRING
950 | SyntaxKind::UNTERMINATED_STRING
951 | SyntaxKind::INT
952 | SyntaxKind::FLOAT
953 | SyntaxKind::BOOL
954 | SyntaxKind::NULL
955 )
956 ) {
957 if self.current() == Some(SyntaxKind::UNTERMINATED_STRING) {
959 self.add_error(
960 "Unterminated quoted string".to_string(),
961 ParseErrorKind::UnterminatedString,
962 );
963 }
964 if !self.in_flow_context {
965 let scalar_indent = self.current_line_indent;
973
974 while let Some(kind) = self.current() {
975 if kind == SyntaxKind::COMMENT {
976 break;
978 }
979
980 if kind == SyntaxKind::NEWLINE {
981 if self.is_plain_scalar_continuation(scalar_indent) {
983 self.bump(); while matches!(
988 self.current(),
989 Some(SyntaxKind::INDENT | SyntaxKind::WHITESPACE)
990 ) {
991 self.bump();
992 }
993
994 continue;
996 } else {
997 break;
999 }
1000 }
1001
1002 if matches!(
1004 kind,
1005 SyntaxKind::LEFT_BRACKET
1006 | SyntaxKind::LEFT_BRACE
1007 | SyntaxKind::RIGHT_BRACKET
1008 | SyntaxKind::RIGHT_BRACE
1009 | SyntaxKind::COMMA
1010 ) {
1011 break;
1012 }
1013
1014 if kind == SyntaxKind::WHITESPACE {
1016 if self.tokens.len() >= 2 {
1018 let next_kind = self.tokens[self.tokens.len() - 2].0;
1019 if next_kind == SyntaxKind::COMMENT {
1020 break;
1022 }
1023 }
1024 }
1025
1026 self.bump();
1027 }
1028 } else {
1029 let is_quoted_string = if let Some(SyntaxKind::STRING) = self.current() {
1037 self.current_text()
1038 .map(|text| text.starts_with('"') || text.starts_with('\''))
1039 .unwrap_or(false)
1040 } else {
1041 false
1042 };
1043
1044 self.bump(); if !is_quoted_string {
1049 while let Some(kind) = self.current() {
1050 if matches!(
1052 kind,
1053 SyntaxKind::COMMA
1054 | SyntaxKind::RIGHT_BRACE
1055 | SyntaxKind::RIGHT_BRACKET
1056 | SyntaxKind::COMMENT
1057 ) {
1058 break;
1059 }
1060
1061 if kind == SyntaxKind::NEWLINE {
1064 self.bump(); while matches!(
1067 self.current(),
1068 Some(SyntaxKind::WHITESPACE | SyntaxKind::INDENT)
1069 ) {
1070 self.bump();
1071 }
1072 continue;
1074 }
1075
1076 if kind == SyntaxKind::WHITESPACE {
1080 if self.tokens.len() >= 2 {
1083 let after_whitespace = self.tokens[self.tokens.len() - 2].0;
1085 if matches!(
1086 after_whitespace,
1087 SyntaxKind::COMMA
1088 | SyntaxKind::RIGHT_BRACE
1089 | SyntaxKind::RIGHT_BRACKET
1090 | SyntaxKind::NEWLINE
1091 | SyntaxKind::COMMENT
1092 ) {
1093 break;
1095 }
1096 }
1098 }
1099
1100 if kind == SyntaxKind::COLON && self.tokens.len() >= 2 {
1102 let next_kind = self.tokens[self.tokens.len() - 2].0;
1103 if matches!(
1104 next_kind,
1105 SyntaxKind::COMMA
1106 | SyntaxKind::RIGHT_BRACE
1107 | SyntaxKind::RIGHT_BRACKET
1108 | SyntaxKind::WHITESPACE
1109 | SyntaxKind::NEWLINE
1110 ) {
1111 break;
1113 }
1114 }
1115
1116 self.bump();
1117 }
1118 }
1119 }
1120 } else {
1121 while let Some(kind) = self.current() {
1123 if matches!(
1124 kind,
1125 SyntaxKind::NEWLINE
1126 | SyntaxKind::DASH
1127 | SyntaxKind::COMMENT
1128 | SyntaxKind::DOC_START
1129 | SyntaxKind::DOC_END
1130 ) {
1131 break;
1132 }
1133
1134 if kind == SyntaxKind::COLON {
1137 if self.in_flow_context {
1138 if self.tokens.len() >= 2 {
1141 let next_kind = self.tokens[self.tokens.len() - 2].0;
1142 if matches!(
1143 next_kind,
1144 SyntaxKind::COMMA
1145 | SyntaxKind::RIGHT_BRACE
1146 | SyntaxKind::RIGHT_BRACKET
1147 | SyntaxKind::WHITESPACE
1148 | SyntaxKind::NEWLINE
1149 ) {
1150 break;
1152 }
1153 }
1154 } else {
1156 break;
1158 }
1159 }
1160
1161 if self.in_flow_context
1163 && matches!(
1164 kind,
1165 SyntaxKind::LEFT_BRACKET
1166 | SyntaxKind::RIGHT_BRACKET
1167 | SyntaxKind::LEFT_BRACE
1168 | SyntaxKind::RIGHT_BRACE
1169 | SyntaxKind::COMMA
1170 )
1171 {
1172 break;
1173 }
1174 self.bump();
1175 }
1176 }
1177 }
1178
1179 self.builder.finish_node();
1180 }
1181
1182 fn parse_tagged_value(&mut self) {
1183 let tag_text = self.peek_tag_text();
1185
1186 match tag_text {
1187 Some("!!set") => self.parse_tagged_set(),
1188 Some("!!omap") => self.parse_tagged_omap(),
1189 Some("!!pairs") => self.parse_tagged_pairs(),
1190 _ => {
1191 self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1193 self.bump(); while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1197 self.bump();
1198 }
1199
1200 self.parse_value();
1202
1203 self.builder.finish_node();
1204 }
1205 }
1206 }
1207
1208 fn peek_tag_text(&self) -> Option<&str> {
1209 self.tokens
1210 .last()
1211 .filter(|(kind, _)| *kind == SyntaxKind::TAG)
1212 .map(|(_, text)| text.as_str())
1213 }
1214
1215 fn parse_tagged_set(&mut self) {
1216 self.parse_tagged_collection(true); }
1218
1219 fn parse_tagged_omap(&mut self) {
1220 self.parse_tagged_collection(false); }
1222
1223 fn parse_tagged_pairs(&mut self) {
1224 self.parse_tagged_collection(false); }
1226
1227 fn parse_tagged_collection(&mut self, is_mapping: bool) {
1228 self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1229
1230 self.bump(); while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1235 self.bump();
1236 }
1237
1238 match self.current() {
1240 Some(SyntaxKind::LEFT_BRACE) if is_mapping => self.parse_flow_mapping(),
1241 Some(SyntaxKind::LEFT_BRACKET) if !is_mapping => self.parse_flow_sequence(),
1242 Some(SyntaxKind::NEWLINE) => {
1243 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1246 self.bump(); }
1248 if is_mapping {
1249 self.parse_mapping();
1250 } else {
1251 self.parse_sequence();
1252 }
1253 }
1254 _ => {
1255 if is_mapping {
1256 self.parse_mapping();
1257 } else {
1258 self.parse_sequence();
1259 }
1260 }
1261 }
1262
1263 self.builder.finish_node();
1264 }
1265
1266 fn parse_literal_block_scalar(&mut self) {
1267 self.builder.start_node(SyntaxKind::SCALAR.into());
1268 self.bump(); self.parse_block_scalar_header();
1270 self.parse_block_scalar_content();
1271 self.builder.finish_node();
1272 }
1273
1274 fn parse_folded_block_scalar(&mut self) {
1275 self.builder.start_node(SyntaxKind::SCALAR.into());
1276 self.bump(); self.parse_block_scalar_header();
1278 self.parse_block_scalar_content();
1279 self.builder.finish_node();
1280 }
1281
1282 fn parse_block_scalar_header(&mut self) {
1283 while let Some(kind) = self.current() {
1288 match kind {
1289 SyntaxKind::NEWLINE | SyntaxKind::COMMENT => break,
1290 SyntaxKind::INT => {
1291 if let Some(text) = self.current_text() {
1293 if text.len() == 1
1294 && text
1295 .chars()
1296 .next()
1297 .expect("text is non-empty: len == 1 checked above")
1298 .is_ascii_digit()
1299 {
1300 self.bump(); } else {
1302 break;
1304 }
1305 } else {
1306 break;
1307 }
1308 }
1309 SyntaxKind::STRING => {
1310 if let Some(text) = self.current_text() {
1312 if text == "+" || text == "-" {
1313 self.bump(); } else {
1315 break;
1317 }
1318 } else {
1319 break;
1320 }
1321 }
1322 SyntaxKind::WHITESPACE => {
1323 self.bump();
1325 }
1326 _ => {
1327 break;
1329 }
1330 }
1331 }
1332
1333 if self.current() == Some(SyntaxKind::COMMENT) {
1335 self.bump();
1336 }
1337
1338 if self.current() == Some(SyntaxKind::NEWLINE) {
1340 self.bump();
1341 }
1342 }
1343
1344 fn parse_block_scalar_content(&mut self) {
1345 let mut last_was_newline = false;
1347 let mut base_indent: Option<usize> = None;
1348 let mut first_content_indent: Option<usize> = None;
1349
1350 while let Some(kind) = self.current() {
1351 if kind == SyntaxKind::INDENT && first_content_indent.is_none() {
1353 first_content_indent = self.current_text().map(|t| t.len());
1354 }
1355
1356 if base_indent.is_none() && first_content_indent.is_some() {
1358 base_indent = first_content_indent;
1359 }
1360
1361 if self.is_at_unindented_content_for_block_scalar(last_was_newline, base_indent) {
1363 break;
1364 }
1365
1366 match kind {
1367 SyntaxKind::DOC_START | SyntaxKind::DOC_END => break,
1369 SyntaxKind::NEWLINE => {
1371 self.bump();
1372 last_was_newline = true;
1373 continue;
1374 }
1375 _ => {
1377 self.bump();
1378 last_was_newline = false;
1379 }
1380 }
1381 }
1382 }
1383
1384 fn is_at_unindented_content_for_block_scalar(
1385 &self,
1386 after_newline: bool,
1387 base_indent: Option<usize>,
1388 ) -> bool {
1389 if after_newline {
1392 let current = self.current();
1394
1395 if matches!(
1397 current,
1398 Some(SyntaxKind::COLON) | Some(SyntaxKind::QUESTION)
1399 ) {
1400 return true;
1401 }
1402
1403 if let Some(base) = base_indent {
1405 if current == Some(SyntaxKind::INDENT) {
1406 if let Some(text) = self.current_text() {
1407 if text.len() < base {
1408 return true;
1410 }
1411 }
1412 }
1413 }
1414
1415 if current != Some(SyntaxKind::INDENT)
1417 && current != Some(SyntaxKind::WHITESPACE)
1418 && current != Some(SyntaxKind::NEWLINE)
1419 && current != Some(SyntaxKind::COMMENT)
1420 {
1421 return true;
1423 }
1424 }
1425 false
1426 }
1427
1428 fn parse_mapping(&mut self) {
1429 self.parse_mapping_with_base_indent(0);
1430 }
1431
1432 fn parse_mapping_with_base_indent(&mut self, base_indent: usize) {
1433 self.builder.start_node(SyntaxKind::MAPPING.into());
1434 self.error_context.push_context(ParseContext::Mapping);
1435
1436 while self.current().is_some() {
1437 let tokens_before_iter = self.tokens.len();
1438 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1440 break;
1441 }
1442
1443 loop {
1445 if self.current() == Some(SyntaxKind::COMMENT) {
1446 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1449 break;
1450 }
1451 self.bump();
1452 if self.current() == Some(SyntaxKind::NEWLINE) {
1453 self.bump();
1454 }
1455 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1456 break;
1457 }
1458 } else {
1459 break;
1460 }
1461 }
1462
1463 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1467 break;
1468 }
1469
1470 if !self.is_mapping_key() && !self.is_complex_mapping_key() {
1472 break;
1473 }
1474
1475 if self.current() == Some(SyntaxKind::LEFT_BRACKET)
1477 || self.current() == Some(SyntaxKind::LEFT_BRACE)
1478 {
1479 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1481
1482 self.builder.start_node(SyntaxKind::KEY.into());
1483 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
1484 self.parse_flow_sequence();
1485 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
1486 self.parse_flow_mapping();
1487 }
1488 self.builder.finish_node();
1489
1490 self.skip_ws_and_newlines();
1491
1492 if self.current() == Some(SyntaxKind::COLON) {
1493 self.bump();
1494 self.skip_whitespace();
1495
1496 self.builder.start_node(SyntaxKind::VALUE.into());
1497 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1498 self.parse_value();
1499 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1500 self.bump();
1501 if self.current() == Some(SyntaxKind::INDENT) {
1502 self.bump();
1503 self.parse_value();
1504 }
1505 }
1506 self.builder.finish_node();
1507 } else {
1508 let error_msg = self.create_detailed_error(
1509 "Missing colon in mapping",
1510 "':' after key",
1511 self.current_text(),
1512 );
1513 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1514 }
1515
1516 self.builder.finish_node();
1518 }
1519 else if self.current() == Some(SyntaxKind::QUESTION) {
1521 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1523
1524 self.bump(); self.skip_whitespace();
1527
1528 self.builder.start_node(SyntaxKind::KEY.into());
1529 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1530 self.parse_value();
1531 }
1532 self.builder.finish_node();
1533
1534 self.skip_ws_and_newlines();
1535
1536 if self.current() == Some(SyntaxKind::COLON) {
1538 self.bump(); self.skip_whitespace();
1540
1541 self.builder.start_node(SyntaxKind::VALUE.into());
1542 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1543 self.parse_value();
1544 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1545 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1547 self.bump(); self.parse_value();
1549 }
1550 }
1551 self.builder.finish_node();
1552 } else {
1553 self.builder.start_node(SyntaxKind::VALUE.into());
1555 self.builder.start_node(SyntaxKind::SCALAR.into());
1556 self.builder.token(SyntaxKind::NULL.into(), "");
1557 self.builder.finish_node();
1558 self.builder.finish_node();
1559 }
1560
1561 self.builder.finish_node();
1563 } else {
1564 self.parse_mapping_key_value_pair();
1565 }
1566
1567 if self.tokens.len() == tokens_before_iter {
1572 let unexpected = self.current_text().unwrap_or("").to_string();
1573 self.add_error(
1574 format!("Unexpected token in mapping: {:?}", unexpected),
1575 ParseErrorKind::Other,
1576 );
1577 self.bump();
1578 }
1579 }
1580
1581 self.builder.finish_node();
1582 self.error_context.pop_context();
1583 }
1584
1585 fn parse_sequence(&mut self) {
1586 self.parse_sequence_with_base_indent(0);
1587 }
1588
1589 fn parse_sequence_with_base_indent(&mut self, base_indent: usize) {
1590 self.builder.start_node(SyntaxKind::SEQUENCE.into());
1591 self.error_context.push_context(ParseContext::Sequence);
1592
1593 while self.current().is_some() {
1594 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1596 break;
1597 }
1598
1599 loop {
1601 if self.current() == Some(SyntaxKind::COMMENT) {
1602 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1605 break;
1606 }
1607 self.bump();
1608 if self.current() == Some(SyntaxKind::NEWLINE) {
1609 self.bump();
1610 }
1611 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1612 break;
1613 }
1614 } else {
1615 break;
1616 }
1617 }
1618
1619 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1623 break;
1624 }
1625
1626 if self.current() != Some(SyntaxKind::DASH) {
1628 break;
1629 }
1630 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1632
1633 let item_indent = self.current_line_indent;
1635
1636 self.bump(); self.skip_whitespace();
1638
1639 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1640 self.parse_value_with_base_indent(item_indent);
1642 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1643 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1646 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
1647 self.bump(); self.parse_value_with_base_indent(indent_level);
1650 }
1651 }
1652
1653 if self.current() == Some(SyntaxKind::NEWLINE) {
1655 self.bump();
1656 }
1657
1658 self.builder.finish_node();
1660 }
1661
1662 self.builder.finish_node();
1663 self.error_context.pop_context();
1664 }
1665
1666 fn next_flow_element_is_implicit_mapping(&self) -> bool {
1679 let tokens = std::iter::once(self.current().unwrap_or(SyntaxKind::EOF))
1681 .chain(self.upcoming_tokens());
1682 has_implicit_mapping_pattern(tokens)
1683 }
1684
1685 fn parse_implicit_flow_mapping(&mut self) {
1688 self.builder.start_node(SyntaxKind::MAPPING.into());
1689 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1690
1691 self.builder.start_node(SyntaxKind::KEY.into());
1693 self.parse_value();
1694 self.builder.finish_node();
1695
1696 self.skip_ws_and_newlines();
1697
1698 if self.current() == Some(SyntaxKind::COLON) {
1700 self.bump();
1701 self.skip_ws_and_newlines();
1702 }
1703
1704 self.builder.start_node(SyntaxKind::VALUE.into());
1706 if matches!(
1708 self.current(),
1709 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACKET)
1710 ) {
1711 } else {
1713 self.parse_value();
1714 }
1715 self.builder.finish_node();
1716
1717 self.builder.finish_node(); self.builder.finish_node(); }
1720}
1721
1722fn has_implicit_mapping_pattern(tokens: impl Iterator<Item = SyntaxKind>) -> bool {
1726 let mut depth = 0;
1727
1728 for kind in tokens {
1729 match kind {
1730 SyntaxKind::LEFT_BRACE | SyntaxKind::LEFT_BRACKET => {
1732 depth += 1;
1733 }
1734 SyntaxKind::RIGHT_BRACE | SyntaxKind::RIGHT_BRACKET => {
1736 if depth == 0 {
1737 return false;
1739 }
1740 depth -= 1;
1741 }
1742 SyntaxKind::COLON if depth == 0 => {
1744 return true;
1746 }
1747 SyntaxKind::COMMA if depth == 0 => {
1748 return false;
1750 }
1751 _ => {}
1753 }
1754 }
1755
1756 false
1758}
1759
1760impl Parser {
1761 fn parse_flow_sequence(&mut self) {
1762 self.builder.start_node(SyntaxKind::SEQUENCE.into());
1763 self.error_context.push_context(ParseContext::FlowSequence);
1764
1765 if self.flow_depth >= MAX_FLOW_DEPTH {
1766 self.add_error(
1767 format!(
1768 "Flow collection nested too deeply (limit {})",
1769 MAX_FLOW_DEPTH
1770 ),
1771 ParseErrorKind::Other,
1772 );
1773 while let Some(kind) = self.current() {
1776 if matches!(
1777 kind,
1778 SyntaxKind::RIGHT_BRACKET | SyntaxKind::DOC_START | SyntaxKind::DOC_END
1779 ) {
1780 break;
1781 }
1782 self.bump();
1783 }
1784 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1785 self.bump();
1786 }
1787 self.builder.finish_node();
1788 self.error_context.pop_context();
1789 return;
1790 }
1791 self.flow_depth += 1;
1792
1793 self.bump(); self.skip_ws_and_newlines(); let prev_flow = self.in_flow_context;
1797 self.in_flow_context = true;
1798
1799 while self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1800 let tokens_before = self.tokens.len();
1801
1802 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1804
1805 if self.next_flow_element_is_implicit_mapping() {
1808 self.parse_implicit_flow_mapping();
1810 } else {
1811 self.parse_value();
1813 }
1814
1815 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COMMA) {
1819 self.bump();
1820 self.skip_ws_and_newlines(); }
1822
1823 self.builder.finish_node(); if self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1826 if matches!(
1829 self.current(),
1830 Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1831 ) {
1832 break;
1834 }
1835 }
1836
1837 if self.tokens.len() == tokens_before {
1841 let unexpected = self.current_text().unwrap_or("").to_string();
1842 self.add_error(
1843 format!("Unexpected token in flow sequence: {:?}", unexpected),
1844 ParseErrorKind::Other,
1845 );
1846 self.bump();
1847 }
1848 }
1849
1850 self.in_flow_context = prev_flow;
1851
1852 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1853 self.bump();
1854 } else {
1855 let error_msg = self.create_detailed_error(
1856 "Unclosed flow sequence",
1857 "']' to close sequence",
1858 self.current_text(),
1859 );
1860 self.add_error_and_recover(
1861 error_msg,
1862 SyntaxKind::RIGHT_BRACKET,
1863 ParseErrorKind::UnclosedFlowSequence,
1864 );
1865 }
1866
1867 self.flow_depth -= 1;
1868 self.builder.finish_node();
1869 self.error_context.pop_context();
1870 }
1871
1872 fn parse_flow_mapping(&mut self) {
1873 self.builder.start_node(SyntaxKind::MAPPING.into());
1874 self.error_context.push_context(ParseContext::FlowMapping);
1875
1876 if self.flow_depth >= MAX_FLOW_DEPTH {
1877 self.add_error(
1878 format!(
1879 "Flow collection nested too deeply (limit {})",
1880 MAX_FLOW_DEPTH
1881 ),
1882 ParseErrorKind::Other,
1883 );
1884 while let Some(kind) = self.current() {
1885 if matches!(
1886 kind,
1887 SyntaxKind::RIGHT_BRACE | SyntaxKind::DOC_START | SyntaxKind::DOC_END
1888 ) {
1889 break;
1890 }
1891 self.bump();
1892 }
1893 if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
1894 self.bump();
1895 }
1896 self.builder.finish_node();
1897 self.error_context.pop_context();
1898 return;
1899 }
1900 self.flow_depth += 1;
1901
1902 self.bump(); self.skip_ws_and_newlines(); let prev_flow = self.in_flow_context;
1906 self.in_flow_context = true;
1907
1908 while self.current() != Some(SyntaxKind::RIGHT_BRACE) && self.current().is_some() {
1909 if matches!(
1911 self.current(),
1912 Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1913 ) {
1914 break;
1916 }
1917
1918 let tokens_before = self.tokens.len();
1919
1920 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1922
1923 self.builder.start_node(SyntaxKind::KEY.into());
1925
1926 if self.current() == Some(SyntaxKind::QUESTION) {
1928 self.bump(); self.skip_whitespace();
1930 }
1931
1932 self.parse_value();
1933 self.builder.finish_node();
1934
1935 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COLON) {
1938 self.bump();
1939 self.skip_ws_and_newlines(); if matches!(
1944 self.current(),
1945 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1946 ) {
1947 self.builder.start_node(SyntaxKind::VALUE.into());
1949 self.builder.start_node(SyntaxKind::SCALAR.into());
1950 self.builder.token(SyntaxKind::NULL.into(), "");
1951 self.builder.finish_node(); self.builder.finish_node(); } else {
1954 self.builder.start_node(SyntaxKind::VALUE.into());
1956 self.parse_value();
1957 self.builder.finish_node();
1958 }
1959 } else if matches!(
1960 self.current(),
1961 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1962 ) {
1963 self.builder.start_node(SyntaxKind::VALUE.into());
1967 self.builder.start_node(SyntaxKind::SCALAR.into());
1968 self.builder.token(SyntaxKind::NULL.into(), "");
1969 self.builder.finish_node(); self.builder.finish_node(); } else {
1972 let error_msg = self.create_detailed_error(
1973 "Missing colon in flow mapping",
1974 "':' after key",
1975 self.current_text(),
1976 );
1977 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1978 }
1979
1980 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COMMA) {
1984 self.bump();
1985 self.skip_ws_and_newlines(); }
1987
1988 self.builder.finish_node();
1990
1991 if self.tokens.len() == tokens_before {
1995 let unexpected = self.current_text().unwrap_or("").to_string();
1996 self.add_error(
1997 format!("Unexpected token in flow mapping: {:?}", unexpected),
1998 ParseErrorKind::Other,
1999 );
2000 self.bump();
2001 }
2002 }
2003
2004 self.in_flow_context = prev_flow;
2005
2006 if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
2007 self.bump();
2008 } else {
2009 let error_msg = self.create_detailed_error(
2010 "Unclosed flow mapping",
2011 "'}' to close mapping",
2012 self.current_text(),
2013 );
2014 self.add_error_and_recover(
2015 error_msg,
2016 SyntaxKind::RIGHT_BRACE,
2017 ParseErrorKind::UnclosedFlowMapping,
2018 );
2019 }
2020
2021 self.flow_depth -= 1;
2022 self.builder.finish_node();
2023 self.error_context.pop_context();
2024 }
2025
2026 fn parse_directive(&mut self) {
2027 self.builder.start_node(SyntaxKind::DIRECTIVE.into());
2028
2029 if self.current() == Some(SyntaxKind::DIRECTIVE) {
2030 self.bump(); } else {
2032 self.add_error("Expected directive".to_string(), ParseErrorKind::Other);
2033 }
2034
2035 self.builder.finish_node();
2036 }
2037
2038 fn parse_explicit_key_mapping(&mut self) {
2039 self.builder.start_node(SyntaxKind::MAPPING.into());
2041
2042 while self.current() == Some(SyntaxKind::QUESTION) {
2043 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2045
2046 self.bump(); self.skip_whitespace();
2049
2050 self.builder.start_node(SyntaxKind::KEY.into());
2052
2053 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2055 self.parse_value();
2056 }
2057
2058 if self.current() == Some(SyntaxKind::NEWLINE) {
2061 if self.tokens.len() >= 2 {
2064 let (next_kind, _) = &self.tokens[self.tokens.len() - 2];
2065 if *next_kind == SyntaxKind::INDENT {
2066 if self.tokens.len() >= 3 {
2068 let (token_after_indent, _) = &self.tokens[self.tokens.len() - 3];
2069 if *token_after_indent != SyntaxKind::DASH {
2072 self.bump(); self.bump(); while self.current().is_some()
2078 && self.current() != Some(SyntaxKind::NEWLINE)
2079 && self.current() != Some(SyntaxKind::COLON)
2080 {
2081 let before = self.tokens.len();
2082 self.parse_scalar();
2083 if self.current() == Some(SyntaxKind::WHITESPACE) {
2084 self.bump(); }
2086 if self.tokens.len() == before {
2090 break;
2091 }
2092 }
2093 }
2094 }
2095 }
2096 }
2097 }
2098
2099 self.builder.finish_node();
2100
2101 self.skip_ws_and_newlines();
2102
2103 if self.current() == Some(SyntaxKind::COLON) {
2105 self.bump(); self.skip_whitespace();
2107
2108 self.builder.start_node(SyntaxKind::VALUE.into());
2109 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2110 self.parse_value();
2111 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2112 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2115 self.bump(); self.parse_value();
2117 }
2118 }
2119 self.builder.finish_node();
2120 } else {
2121 self.builder.start_node(SyntaxKind::VALUE.into());
2123 self.builder.start_node(SyntaxKind::SCALAR.into());
2124 self.builder.token(SyntaxKind::NULL.into(), "");
2125 self.builder.finish_node();
2126 self.builder.finish_node();
2127 }
2128
2129 self.builder.finish_node();
2131
2132 self.skip_ws_and_newlines();
2133
2134 if self.current() != Some(SyntaxKind::QUESTION) && !self.is_mapping_key() {
2136 break;
2137 }
2138 }
2139
2140 while self.current().is_some() && self.is_mapping_key() {
2142 let tokens_before_iter = self.tokens.len();
2143 if self.current() == Some(SyntaxKind::QUESTION) {
2147 self.parse_explicit_key_entries();
2148 break;
2149 }
2150 self.parse_mapping_key_value_pair();
2151 self.skip_ws_and_newlines();
2152 if self.tokens.len() == tokens_before_iter {
2155 let unexpected = self.current_text().unwrap_or("").to_string();
2156 self.add_error(
2157 format!("Unexpected token in explicit-key mapping: {:?}", unexpected),
2158 ParseErrorKind::Other,
2159 );
2160 self.bump();
2161 }
2162 }
2163
2164 self.builder.finish_node();
2165 }
2166
2167 fn parse_complex_key_mapping(&mut self) {
2168 self.builder.start_node(SyntaxKind::MAPPING.into());
2170
2171 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2173
2174 self.builder.start_node(SyntaxKind::KEY.into());
2176 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2177 self.parse_flow_sequence();
2178 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2179 self.parse_flow_mapping();
2180 }
2181 self.builder.finish_node();
2182
2183 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COLON) {
2187 self.bump();
2188 self.skip_whitespace();
2189
2190 self.builder.start_node(SyntaxKind::VALUE.into());
2192 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2193 self.parse_value();
2194 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2195 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2197 self.bump(); self.parse_value();
2199 }
2200 }
2201 self.builder.finish_node();
2202 } else {
2203 let error_msg = self.create_detailed_error(
2204 "Missing colon in complex mapping",
2205 "':' after complex key",
2206 self.current_text(),
2207 );
2208 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2209 }
2210
2211 self.builder.finish_node();
2213
2214 self.skip_ws_and_newlines();
2215
2216 while self.current().is_some() {
2218 let tokens_before_iter = self.tokens.len();
2219 if self.current() == Some(SyntaxKind::QUESTION) {
2220 self.parse_explicit_key_entries();
2222 break;
2223 } else if self.is_complex_mapping_key()
2224 || (self.is_mapping_key() && self.current() != Some(SyntaxKind::QUESTION))
2225 {
2226 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2228
2229 self.builder.start_node(SyntaxKind::KEY.into());
2231
2232 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2233 self.parse_flow_sequence();
2234 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2235 self.parse_flow_mapping();
2236 } else if matches!(
2237 self.current(),
2238 Some(
2239 SyntaxKind::STRING
2240 | SyntaxKind::INT
2241 | SyntaxKind::FLOAT
2242 | SyntaxKind::BOOL
2243 | SyntaxKind::NULL
2244 | SyntaxKind::MERGE_KEY
2245 )
2246 ) {
2247 self.bump();
2248 }
2249 self.builder.finish_node();
2250
2251 self.skip_whitespace();
2252
2253 if self.current() == Some(SyntaxKind::COLON) {
2254 self.bump();
2255 self.skip_whitespace();
2256
2257 self.builder.start_node(SyntaxKind::VALUE.into());
2258 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2259 self.parse_value();
2260 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2261 self.bump();
2262 if self.current() == Some(SyntaxKind::INDENT) {
2263 self.bump();
2264 self.parse_value();
2265 }
2266 }
2267 self.builder.finish_node();
2268 }
2269
2270 self.builder.finish_node();
2272
2273 self.skip_ws_and_newlines();
2274 } else {
2275 break;
2276 }
2277
2278 if self.tokens.len() == tokens_before_iter {
2282 let unexpected = self.current_text().unwrap_or("").to_string();
2283 self.add_error(
2284 format!("Unexpected token in complex mapping: {:?}", unexpected),
2285 ParseErrorKind::Other,
2286 );
2287 self.bump();
2288 }
2289 }
2290
2291 self.builder.finish_node();
2292 }
2293
2294 fn parse_explicit_key_entries(&mut self) {
2295 while self.current() == Some(SyntaxKind::QUESTION) {
2297 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2299
2300 self.bump(); self.skip_whitespace();
2302
2303 self.builder.start_node(SyntaxKind::KEY.into());
2304 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2305 self.parse_value();
2306 }
2307 self.builder.finish_node();
2308
2309 self.skip_ws_and_newlines();
2310
2311 if self.current() == Some(SyntaxKind::COLON) {
2312 self.bump();
2313 self.skip_whitespace();
2314
2315 self.builder.start_node(SyntaxKind::VALUE.into());
2316 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2317 self.parse_value();
2318 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2319 self.bump();
2320 if self.current() == Some(SyntaxKind::INDENT) {
2321 self.bump();
2322 self.parse_value();
2323 }
2324 }
2325 self.builder.finish_node();
2326 } else {
2327 self.builder.start_node(SyntaxKind::VALUE.into());
2329 self.builder.start_node(SyntaxKind::SCALAR.into());
2330 self.builder.token(SyntaxKind::NULL.into(), "");
2331 self.builder.finish_node();
2332 self.builder.finish_node();
2333 }
2334
2335 self.builder.finish_node();
2337
2338 self.skip_ws_and_newlines();
2339 }
2340 }
2341
2342 fn is_complex_mapping_key(&self) -> bool {
2343 if !matches!(
2345 self.current(),
2346 Some(SyntaxKind::LEFT_BRACKET) | Some(SyntaxKind::LEFT_BRACE)
2347 ) {
2348 return false;
2349 }
2350
2351 let mut depth = 0;
2353 let start_kind = self.current();
2354 let close_kind = match start_kind {
2355 Some(SyntaxKind::LEFT_BRACKET) => SyntaxKind::RIGHT_BRACKET,
2356 Some(SyntaxKind::LEFT_BRACE) => SyntaxKind::RIGHT_BRACE,
2357 _ => return false,
2358 };
2359
2360 let mut found_close = false;
2361 for kind in self.upcoming_tokens() {
2362 if !found_close {
2363 if Some(kind) == start_kind {
2364 depth += 1;
2365 } else if kind == close_kind {
2366 if depth == 0 {
2367 found_close = true;
2369 } else {
2370 depth -= 1;
2371 }
2372 }
2373 } else {
2374 match kind {
2376 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => continue,
2377 SyntaxKind::COLON => return true,
2378 _ => return false,
2379 }
2380 }
2381 }
2382 false
2383 }
2384
2385 fn parse_mapping_value(&mut self) {
2386 match self.current() {
2390 Some(SyntaxKind::DASH) if !self.in_flow_context => self.parse_sequence(),
2391 Some(SyntaxKind::ANCHOR) => {
2392 self.bump(); self.skip_whitespace();
2394 self.parse_value_with_base_indent(0);
2395 }
2396 Some(SyntaxKind::REFERENCE) => self.parse_alias(),
2397 Some(SyntaxKind::TAG) => self.parse_tagged_value(),
2398 Some(SyntaxKind::QUESTION) => {
2399 self.parse_explicit_key_mapping();
2401 }
2402 Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
2403 Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
2404 Some(SyntaxKind::LEFT_BRACKET) => {
2405 if !self.in_flow_context && self.is_complex_mapping_key() {
2407 self.parse_complex_key_mapping();
2408 } else {
2409 self.parse_flow_sequence();
2410 }
2411 }
2412 Some(SyntaxKind::LEFT_BRACE) => {
2413 if !self.in_flow_context && self.is_complex_mapping_key() {
2415 self.parse_complex_key_mapping();
2416 } else {
2417 self.parse_flow_mapping();
2418 }
2419 }
2420 _ => {
2421 self.parse_scalar();
2424 }
2425 }
2426 }
2427
2428 fn is_mapping_key(&self) -> bool {
2429 if self.current() == Some(SyntaxKind::QUESTION) {
2431 return true;
2432 }
2433
2434 if self.current() == Some(SyntaxKind::MERGE_KEY) {
2436 return true;
2437 }
2438
2439 if self.current() == Some(SyntaxKind::DASH) {
2441 return false;
2442 }
2443
2444 let upcoming = self.upcoming_tokens();
2447 for kind in upcoming {
2448 match kind {
2449 SyntaxKind::COLON => {
2450 return true;
2451 }
2452 SyntaxKind::WHITESPACE => continue,
2453 _ => {
2455 return false;
2456 }
2457 }
2458 }
2459 false
2460 }
2461
2462 fn skip_whitespace(&mut self) {
2463 self.skip_tokens(&[SyntaxKind::WHITESPACE]);
2464 }
2465
2466 fn skip_tokens(&mut self, kinds: &[SyntaxKind]) {
2467 while let Some(current) = self.current() {
2468 if kinds.contains(¤t) {
2469 self.bump();
2470 } else {
2471 break;
2472 }
2473 }
2474 }
2475
2476 fn is_plain_scalar_continuation(&self, scalar_indent: usize) -> bool {
2479 let current_idx = self.tokens.len().saturating_sub(1);
2482
2483 if current_idx == 0 {
2484 return false; }
2486
2487 let mut peek_idx = current_idx.saturating_sub(1);
2490
2491 let next_line_indent = self
2493 .tokens
2494 .get(peek_idx)
2495 .and_then(|(kind, text)| {
2496 if *kind == SyntaxKind::INDENT {
2497 peek_idx = peek_idx.saturating_sub(1);
2498 Some(text.len())
2499 } else {
2500 None
2501 }
2502 })
2503 .unwrap_or(0);
2504
2505 while self
2507 .tokens
2508 .get(peek_idx)
2509 .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2510 {
2511 peek_idx = peek_idx.saturating_sub(1);
2512 }
2513
2514 let has_content = self.tokens.get(peek_idx).is_some_and(|(kind, _)| {
2516 matches!(
2517 kind,
2518 SyntaxKind::STRING
2519 | SyntaxKind::INT
2520 | SyntaxKind::FLOAT
2521 | SyntaxKind::BOOL
2522 | SyntaxKind::NULL
2523 | SyntaxKind::UNTERMINATED_STRING
2524 )
2525 });
2526
2527 if !has_content || next_line_indent <= scalar_indent {
2528 return false;
2529 }
2530
2531 if peek_idx > 0 {
2534 let mut check_idx = peek_idx.saturating_sub(1);
2535
2536 while self
2538 .tokens
2539 .get(check_idx)
2540 .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2541 {
2542 if check_idx == 0 {
2543 break;
2544 }
2545 check_idx = check_idx.saturating_sub(1);
2546 }
2547
2548 if self
2550 .tokens
2551 .get(check_idx)
2552 .is_some_and(|(kind, _)| *kind == SyntaxKind::COLON)
2553 {
2554 return false;
2555 }
2556 }
2557
2558 true
2559 }
2560
2561 fn is_at_dedented_position(&self, base_indent: usize) -> bool {
2565 if base_indent == 0 {
2571 self.current_line_indent > 0
2573 } else {
2574 self.current_line_indent < base_indent
2576 }
2577 }
2578
2579 fn skip_whitespace_only_with_dedent_check(&mut self, base_indent: usize) -> bool {
2582 while self.current().is_some() {
2583 match self.current() {
2584 Some(SyntaxKind::WHITESPACE) => {
2585 self.bump();
2586 }
2587 Some(SyntaxKind::NEWLINE) => {
2588 self.bump();
2589 match self.current() {
2591 Some(SyntaxKind::INDENT) => {
2592 if let Some((_, text)) = self.tokens.last() {
2593 if text.len() < base_indent {
2594 return true;
2596 }
2597 if base_indent == 0 && !text.is_empty() {
2598 return true;
2600 }
2601 }
2602 self.bump(); }
2604 Some(SyntaxKind::COMMENT) => {
2605 if base_indent > 0 {
2607 return true;
2609 }
2610 return false;
2612 }
2613 Some(SyntaxKind::WHITESPACE) | Some(SyntaxKind::NEWLINE) => {
2614 }
2616 None => {
2617 return false;
2619 }
2620 _ => {
2621 if base_indent > 0 {
2623 return true; }
2625 return false;
2627 }
2628 }
2629 }
2630 Some(SyntaxKind::INDENT) => {
2631 if let Some((_, text)) = self.tokens.last() {
2633 if text.len() < base_indent {
2634 return true; }
2636 }
2637 self.bump();
2638 }
2639 _ => {
2640 return false;
2642 }
2643 }
2644 }
2645 false
2646 }
2647
2648 fn skip_ws_and_newlines(&mut self) {
2649 self.skip_tokens(&[
2650 SyntaxKind::WHITESPACE,
2651 SyntaxKind::NEWLINE,
2652 SyntaxKind::INDENT,
2653 SyntaxKind::COMMENT,
2654 ]);
2655 }
2656
2657 fn parse_mapping_key_value_pair(&mut self) {
2658 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2660
2661 self.builder.start_node(SyntaxKind::KEY.into());
2663
2664 if self.current() == Some(SyntaxKind::ANCHOR) {
2666 self.bump(); self.skip_whitespace();
2668 }
2669
2670 if self.current() == Some(SyntaxKind::MERGE_KEY) {
2671 self.builder.start_node(SyntaxKind::SCALAR.into());
2672 self.bump(); self.builder.finish_node(); } else if self.current() == Some(SyntaxKind::REFERENCE) {
2675 self.parse_alias();
2677 } else if matches!(
2678 self.current(),
2679 Some(
2680 SyntaxKind::STRING
2681 | SyntaxKind::INT
2682 | SyntaxKind::FLOAT
2683 | SyntaxKind::BOOL
2684 | SyntaxKind::NULL
2685 )
2686 ) {
2687 self.builder.start_node(SyntaxKind::SCALAR.into());
2688 self.bump(); self.builder.finish_node(); }
2691 self.builder.finish_node(); self.skip_whitespace();
2694
2695 if self.current() == Some(SyntaxKind::COLON) {
2697 self.bump();
2698 self.skip_whitespace();
2699
2700 self.builder.start_node(SyntaxKind::VALUE.into());
2702 let mut has_value = false;
2703 if self.current().is_some()
2704 && self.current() != Some(SyntaxKind::NEWLINE)
2705 && self.current() != Some(SyntaxKind::COMMENT)
2706 {
2707 self.parse_mapping_value();
2709 has_value = true;
2710
2711 if self.current() == Some(SyntaxKind::WHITESPACE) {
2714 self.bump(); }
2716 if self.current() == Some(SyntaxKind::COMMENT) {
2717 self.bump(); }
2719 } else if self.current() == Some(SyntaxKind::COMMENT) {
2720 self.bump(); if self.current() == Some(SyntaxKind::NEWLINE) {
2726 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2729 let indent_level =
2730 self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2731 self.bump(); self.parse_value_with_base_indent(indent_level);
2734 has_value = true;
2735 }
2736 }
2737 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2739 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2742 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2743 self.bump(); self.parse_value_with_base_indent(indent_level);
2746 has_value = true;
2747 } else if self.current() == Some(SyntaxKind::DASH) {
2748 self.parse_sequence();
2751 has_value = true;
2752 }
2753 }
2754
2755 if !has_value {
2757 self.builder.start_node(SyntaxKind::SCALAR.into());
2758 self.builder.token(SyntaxKind::NULL.into(), "");
2759 self.builder.finish_node();
2760 }
2761
2762 self.builder.finish_node(); } else {
2764 let error_msg = self.create_detailed_error(
2765 "Missing colon in mapping",
2766 "':' after key",
2767 self.current_text(),
2768 );
2769 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2770 }
2771
2772 while self.current() == Some(SyntaxKind::WHITESPACE) {
2777 self.bump();
2778 }
2779
2780 if self.current() == Some(SyntaxKind::NEWLINE) {
2782 self.bump();
2783 }
2784
2785 self.builder.finish_node();
2787 }
2788
2789 fn bump(&mut self) {
2790 if let Some((kind, text)) = self.tokens.pop() {
2791 match kind {
2793 SyntaxKind::INDENT => {
2794 self.current_line_indent = text.len();
2795 }
2796 SyntaxKind::NEWLINE => {
2797 self.current_line_indent = 0;
2799 }
2800 _ => {}
2801 }
2802
2803 self.builder.token(kind.into(), &text);
2804 if self.current_token_index > 0 {
2805 self.current_token_index -= 1;
2806 }
2807 self.error_context.advance(text.len());
2809 }
2810 }
2811
2812 fn current(&self) -> Option<SyntaxKind> {
2813 self.tokens.last().map(|(kind, _)| *kind)
2814 }
2815
2816 fn current_text(&self) -> Option<&str> {
2817 self.tokens.last().map(|(_, text)| text.as_str())
2818 }
2819
2820 fn upcoming_tokens(&self) -> impl Iterator<Item = SyntaxKind> + '_ {
2822 let len = self.tokens.len();
2825 (0..len.saturating_sub(1))
2826 .rev()
2827 .map(move |i| self.tokens[i].0)
2828 }
2829
2830 fn add_error(&mut self, message: String, kind: ParseErrorKind) {
2831 let token_len = self.current_text().map(|s| s.len()).unwrap_or(1);
2833 let positioned_error = self.error_context.create_error(message, token_len, kind);
2834
2835 self.errors.push(positioned_error.message.clone());
2836 self.positioned_errors.push(positioned_error);
2837 }
2838
2839 fn add_error_and_recover(
2841 &mut self,
2842 message: String,
2843 expected: SyntaxKind,
2844 kind: ParseErrorKind,
2845 ) {
2846 self.add_error(message, kind);
2847
2848 let found = self.current();
2850 let strategy = self.error_context.suggest_recovery(expected, found);
2851
2852 match strategy {
2853 RecoveryStrategy::SkipToken => {
2854 if self.current().is_some() {
2856 self.bump();
2857 }
2858 }
2859 RecoveryStrategy::SkipToEndOfLine => {
2860 while self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2862 self.bump();
2863 }
2864 }
2865 RecoveryStrategy::InsertToken(kind) => {
2866 self.builder.token(kind.into(), "");
2868 }
2869 RecoveryStrategy::SyncToSafePoint => {
2870 let sync_point = self
2872 .error_context
2873 .find_sync_point(&self.tokens, self.tokens.len() - self.current_token_index);
2874 let tokens_to_skip = sync_point - (self.tokens.len() - self.current_token_index);
2875 for _ in 0..tokens_to_skip {
2876 if self.current().is_some() {
2877 self.bump();
2878 }
2879 }
2880 }
2881 }
2882 }
2883
2884 fn create_detailed_error(
2886 &self,
2887 base_message: &str,
2888 expected: &str,
2889 found: Option<&str>,
2890 ) -> String {
2891 let mut builder = ErrorBuilder::new(base_message);
2892 builder = builder.expected(expected);
2893
2894 if let Some(found_str) = found {
2895 builder = builder.found(found_str);
2896 } else if let Some(token) = self.current_text() {
2897 builder = builder.found(format!("'{}'", token));
2898 } else {
2899 builder = builder.found("end of input");
2900 }
2901
2902 let context = match self.error_context.current_context() {
2904 ParseContext::Mapping => "in mapping",
2905 ParseContext::Sequence => "in sequence",
2906 ParseContext::FlowMapping => "in flow mapping",
2907 ParseContext::FlowSequence => "in flow sequence",
2908 ParseContext::BlockScalar => "in block scalar",
2909 ParseContext::QuotedString => "in quoted string",
2910 _ => "at document level",
2911 };
2912 builder = builder.context(context);
2913
2914 let suggestion = self.get_error_suggestion(base_message, expected, found);
2916 if let Some(suggestion_text) = suggestion {
2917 builder = builder.suggestion(suggestion_text);
2918 }
2919
2920 builder.build()
2921 }
2922
2923 fn get_error_suggestion(
2925 &self,
2926 base_message: &str,
2927 expected: &str,
2928 found: Option<&str>,
2929 ) -> Option<String> {
2930 if base_message.contains("Unterminated quoted string") {
2931 return Some(
2932 "Add closing quote or check for unescaped quotes within the string".to_string(),
2933 );
2934 }
2935
2936 if base_message.contains("Missing colon") || expected.contains("':'") {
2937 return Some("Add ':' after the key, or check for proper indentation".to_string());
2938 }
2939
2940 if base_message.contains("Unclosed flow sequence") {
2941 return Some(
2942 "Add ']' to close the array, or check for missing commas between elements"
2943 .to_string(),
2944 );
2945 }
2946
2947 if base_message.contains("Unclosed flow mapping") {
2948 return Some(
2949 "Add '}' to close the object, or check for missing commas between key-value pairs"
2950 .to_string(),
2951 );
2952 }
2953
2954 if let Some(found_text) = found {
2955 if found_text.contains('\n') {
2956 return Some(
2957 "Unexpected newline - check indentation and YAML structure".to_string(),
2958 );
2959 }
2960
2961 if found_text.contains('\t') {
2962 return Some(
2963 "Tabs are not allowed in YAML - use spaces for indentation".to_string(),
2964 );
2965 }
2966 }
2967
2968 None
2969 }
2970}
2971
2972pub(crate) fn parse(text: &str) -> ParsedYaml {
2974 let parser = Parser::new(text);
2975 parser.parse()
2976}
2977
2978#[cfg(test)]
2981mod tests {
2982 use super::*;
2983 use crate::builder::{MappingBuilder, SequenceBuilder};
2984 use crate::scalar::ScalarValue;
2985 use crate::value::YamlValue; #[test]
2988 fn test_simple_mapping() {
2989 let yaml = "key: value";
2990 let parsed = YamlFile::from_str(yaml).unwrap();
2991 let doc = parsed.document().unwrap();
2992 let mapping = doc.as_mapping().unwrap();
2993
2994 assert_eq!(parsed.to_string().trim(), "key: value");
2996
2997 let value = mapping.get("key");
2999 assert!(value.is_some());
3000 }
3001
3002 #[test]
3003 fn test_simple_sequence() {
3004 let yaml = "- item1\n- item2";
3005 let parsed = YamlFile::from_str(yaml);
3006 assert!(parsed.is_ok());
3007 }
3008
3009 #[test]
3010 fn test_complex_yaml() {
3011 let yaml = r#"
3012name: my-app
3013version: 1.0.0
3014dependencies:
3015 - serde
3016 - tokio
3017config:
3018 port: 8080
3019 enabled: true
3020"#;
3021 let parsed = YamlFile::from_str(yaml).unwrap();
3022 assert_eq!(parsed.documents().count(), 1);
3023
3024 let doc = parsed.document().unwrap();
3025 assert!(doc.as_mapping().is_some());
3026 }
3027
3028 #[test]
3029 fn test_multiple_documents() {
3030 let yaml = r#"---
3031doc: first
3032---
3033doc: second
3034...
3035"#;
3036 let parsed = YamlFile::from_str(yaml).unwrap();
3037 assert_eq!(parsed.documents().count(), 2);
3038 }
3039
3040 #[test]
3041 fn test_flow_styles() {
3042 let yaml = r#"
3043array: [1, 2, 3]
3044object: {key: value, another: 42}
3045"#;
3046 let parsed = YamlFile::from_str(yaml).unwrap();
3047 assert!(parsed.document().is_some());
3048 }
3049
3050 #[test]
3051 fn test_scalar_types_parsing() {
3052 let yaml = r#"
3053string: hello
3054integer: 42
3055float: 3.14
3056bool_true: true
3057bool_false: false
3058null_value: null
3059tilde: ~
3060"#;
3061 let parsed = YamlFile::from_str(yaml).unwrap();
3062 let doc = parsed.document().unwrap();
3063 let mapping = doc.as_mapping().unwrap();
3064
3065 assert!(mapping.get("string").is_some());
3067 assert!(mapping.get("integer").is_some());
3068 assert!(mapping.get("float").is_some());
3069 assert!(mapping.get("bool_true").is_some());
3070 assert!(mapping.get("bool_false").is_some());
3071 assert!(mapping.get("null_value").is_some());
3072 assert!(mapping.get("tilde").is_some());
3073 }
3074
3075 #[test]
3076 fn test_preserve_formatting() {
3077 let yaml = r#"# Comment at start
3078key: value # inline comment
3079
3080# Another comment
3081list:
3082 - item1
3083 - item2
3084"#;
3085 let parsed = YamlFile::from_str(yaml).unwrap();
3086
3087 let doc = parsed.document().unwrap();
3088 let mapping = doc.as_mapping().unwrap();
3089 assert_eq!(
3090 mapping.get("key").unwrap().as_scalar().unwrap().as_string(),
3091 "value"
3092 );
3093 let list = mapping.get_sequence("list").unwrap();
3094 assert_eq!(list.len(), 2);
3095 let items: Vec<String> = list
3097 .values()
3098 .map(|v| v.to_string().trim().to_string())
3099 .collect();
3100 assert_eq!(items, vec!["item1", "item2"]);
3101
3102 let output = parsed.to_string();
3104 assert_eq!(output, yaml);
3105 }
3106
3107 #[test]
3108 fn test_quoted_strings() {
3109 let yaml = r#"
3110single: 'single quoted'
3111double: "double quoted"
3112plain: unquoted
3113"#;
3114 let parsed = YamlFile::from_str(yaml).unwrap();
3115 let doc = parsed.document().unwrap();
3116 let mapping = doc.as_mapping().unwrap();
3117
3118 assert!(mapping.get("single").is_some());
3119 assert!(mapping.get("double").is_some());
3120 assert!(mapping.get("plain").is_some());
3121 }
3122
3123 #[test]
3124 fn test_nested_structures() {
3125 let yaml = r#"
3126root:
3127 nested:
3128 deeply:
3129 value: 42
3130 list:
3131 - item1
3132 - item2
3133"#;
3134 let parsed = YamlFile::from_str(yaml).unwrap();
3135 assert!(parsed.document().is_some());
3136 }
3137
3138 #[test]
3139 fn test_empty_values() {
3140 let yaml = r#"
3141empty_string: ""
3142empty_after_colon:
3143another_key: value
3144"#;
3145 let parsed = YamlFile::from_str(yaml).unwrap();
3146 let doc = parsed.document().unwrap();
3147 let mapping = doc.as_mapping().unwrap();
3148
3149 assert!(mapping.get("empty_string").is_some());
3150 assert!(mapping.get("another_key").is_some());
3151 }
3152
3153 #[test]
3154 fn test_special_characters() {
3155 let yaml = r#"
3156special: "line1\nline2"
3157unicode: "emoji 😀"
3158escaped: 'it\'s escaped'
3159"#;
3160 let result = YamlFile::from_str(yaml);
3161 assert!(result.is_ok());
3163 }
3164
3165 #[test]
3168 fn test_error_handling() {
3169 let yaml = "key: value\n invalid indentation for key";
3171 let result = YamlFile::from_str(yaml);
3172 let _ = result;
3174 }
3175
3176 #[test]
3179 fn test_anchor_exact_output() {
3180 let yaml = "key: &anchor value\nref: *anchor";
3181 let parsed = YamlFile::from_str(yaml).unwrap();
3182 let output = parsed.to_string();
3183
3184 assert_eq!(output, "key: &anchor value\nref: *anchor");
3186 }
3187
3188 #[test]
3189 fn test_anchor_with_different_value_types() {
3190 let yaml = r#"string_anchor: &str_val "hello"
3191int_anchor: &int_val 42
3192bool_anchor: &bool_val true
3193null_anchor: &null_val null
3194str_ref: *str_val
3195int_ref: *int_val
3196bool_ref: *bool_val
3197null_ref: *null_val"#;
3198
3199 let parsed = YamlFile::from_str(yaml);
3200 assert!(
3201 parsed.is_ok(),
3202 "Should parse anchors with different value types"
3203 );
3204
3205 let yaml_doc = parsed.unwrap();
3206
3207 let doc = yaml_doc.document().unwrap();
3208 let mapping = doc.as_mapping().unwrap();
3209
3210 assert_eq!(
3212 mapping
3213 .get("string_anchor")
3214 .unwrap()
3215 .as_scalar()
3216 .unwrap()
3217 .as_string(),
3218 "hello"
3219 );
3220 assert_eq!(mapping.get("int_anchor").unwrap().to_i64(), Some(42));
3221 assert_eq!(mapping.get("bool_anchor").unwrap().to_bool(), Some(true));
3222 assert!(mapping.get("null_anchor").unwrap().as_scalar().is_some());
3223
3224 let str_ref = mapping.get("str_ref").unwrap();
3226 assert!(str_ref.is_alias());
3227 assert_eq!(str_ref.as_alias().unwrap().name(), "str_val");
3228
3229 let int_ref = mapping.get("int_ref").unwrap();
3230 assert!(int_ref.is_alias());
3231 assert_eq!(int_ref.as_alias().unwrap().name(), "int_val");
3232
3233 let bool_ref = mapping.get("bool_ref").unwrap();
3234 assert!(bool_ref.is_alias());
3235 assert_eq!(bool_ref.as_alias().unwrap().name(), "bool_val");
3236
3237 let null_ref = mapping.get("null_ref").unwrap();
3238 assert!(null_ref.is_alias());
3239 assert_eq!(null_ref.as_alias().unwrap().name(), "null_val");
3240
3241 let output = yaml_doc.to_string();
3243 assert_eq!(output, yaml);
3244 }
3245
3246 #[test]
3247 fn test_undefined_alias_parses_successfully() {
3248 let yaml = "key: *undefined";
3249 let parse_result = Parse::parse_yaml(yaml);
3250
3251 assert!(
3254 !parse_result.has_errors(),
3255 "Parser should not validate undefined aliases"
3256 );
3257
3258 let output = parse_result.tree().to_string();
3260 assert_eq!(output.trim(), "key: *undefined");
3261 }
3262
3263 #[test]
3264 fn test_anchor_names_with_alphanumeric_chars() {
3265 let yaml1 = "key1: &anchor_123 val1\nref1: *anchor_123";
3267 let parsed1 = YamlFile::from_str(yaml1);
3268 assert!(
3269 parsed1.is_ok(),
3270 "Should parse anchors with underscores and numbers"
3271 );
3272
3273 let file1 = parsed1.unwrap();
3274 let doc1 = file1.document().unwrap();
3275 let map1 = doc1.as_mapping().unwrap();
3276 assert_eq!(
3277 map1.get("key1").unwrap().as_scalar().unwrap().as_string(),
3278 "val1"
3279 );
3280 assert!(map1.get("ref1").unwrap().is_alias());
3281 assert_eq!(
3282 map1.get("ref1").unwrap().as_alias().unwrap().name(),
3283 "anchor_123"
3284 );
3285 assert_eq!(file1.to_string(), yaml1);
3286
3287 let yaml2 = "key2: &AnchorName val2\nref2: *AnchorName";
3288 let parsed2 = YamlFile::from_str(yaml2);
3289 assert!(parsed2.is_ok(), "Should parse anchors with mixed case");
3290
3291 let file2 = parsed2.unwrap();
3292 let doc2 = file2.document().unwrap();
3293 let map2 = doc2.as_mapping().unwrap();
3294 assert_eq!(
3295 map2.get("key2").unwrap().as_scalar().unwrap().as_string(),
3296 "val2"
3297 );
3298 assert!(map2.get("ref2").unwrap().is_alias());
3299 assert_eq!(
3300 map2.get("ref2").unwrap().as_alias().unwrap().name(),
3301 "AnchorName"
3302 );
3303 assert_eq!(file2.to_string(), yaml2);
3304
3305 let yaml3 = "key3: &anchor123abc val3\nref3: *anchor123abc";
3306 let parsed3 = YamlFile::from_str(yaml3);
3307 assert!(
3308 parsed3.is_ok(),
3309 "Should parse anchors with letters and numbers"
3310 );
3311
3312 let file3 = parsed3.unwrap();
3313 let doc3 = file3.document().unwrap();
3314 let map3 = doc3.as_mapping().unwrap();
3315 assert_eq!(
3316 map3.get("key3").unwrap().as_scalar().unwrap().as_string(),
3317 "val3"
3318 );
3319 assert!(map3.get("ref3").unwrap().is_alias());
3320 assert_eq!(
3321 map3.get("ref3").unwrap().as_alias().unwrap().name(),
3322 "anchor123abc"
3323 );
3324 assert_eq!(file3.to_string(), yaml3);
3325 }
3326
3327 #[test]
3328 fn test_anchor_in_sequence_detailed() {
3329 let yaml = r#"items:
3330 - &first_item value1
3331 - second_item
3332 - *first_item"#;
3333
3334 let parsed = YamlFile::from_str(yaml);
3335 assert!(parsed.is_ok(), "Should parse anchors in sequences");
3336
3337 let yaml_doc = parsed.unwrap();
3338
3339 let doc = yaml_doc.document().unwrap();
3340 let mapping = doc.as_mapping().unwrap();
3341 let seq = mapping.get_sequence("items").unwrap();
3342 assert_eq!(seq.len(), 3);
3343
3344 let item0 = seq.get(0).unwrap();
3346 let item1 = seq.get(1).unwrap();
3347 let item2 = seq.get(2).unwrap();
3348
3349 assert_eq!(item0.as_scalar().unwrap().as_string(), "value1");
3351
3352 assert_eq!(item1.as_scalar().unwrap().as_string(), "second_item");
3354
3355 assert!(item2.is_alias(), "Third item should be an alias");
3357 assert_eq!(item2.as_alias().unwrap().name(), "first_item");
3358
3359 let output = yaml_doc.to_string();
3360 assert_eq!(output, yaml);
3361 }
3362
3363 #[test]
3364 fn test_preserve_whitespace_around_anchors() {
3365 let yaml = "key: &anchor value \nref: *anchor ";
3366 let parsed = YamlFile::from_str(yaml).unwrap();
3367
3368 let doc = parsed.document().unwrap();
3369 let mapping = doc.as_mapping().unwrap();
3370 assert_eq!(
3371 mapping
3372 .get("key")
3373 .unwrap()
3374 .as_scalar()
3375 .unwrap()
3376 .as_string()
3377 .trim(),
3378 "value"
3379 );
3380 let ref_node = mapping.get("ref").unwrap();
3381 assert!(ref_node.is_alias());
3382 assert_eq!(ref_node.as_alias().unwrap().name(), "anchor");
3383
3384 let output = parsed.to_string();
3386 assert_eq!(output, yaml);
3387 }
3388
3389 #[test]
3390 fn test_literal_block_scalar_basic() {
3391 let yaml = r#"literal: |
3392 Line 1
3393 Line 2
3394 Line 3
3395"#;
3396 let parsed = YamlFile::from_str(yaml);
3397 assert!(parsed.is_ok(), "Should parse basic literal block scalar");
3398
3399 let yaml_doc = parsed.unwrap();
3400 let output = yaml_doc.to_string();
3401
3402 assert_eq!(output, yaml);
3404 }
3405
3406 #[test]
3407 fn test_folded_block_scalar_basic() {
3408 let yaml = r#"folded: >
3409 This is a very long line that will be folded
3410 into a single line in the output
3411 but preserves paragraph breaks.
3412
3413 This is a new paragraph.
3414"#;
3415 let parsed = YamlFile::from_str(yaml);
3416 assert!(parsed.is_ok(), "Should parse basic folded block scalar");
3417
3418 let yaml_doc = parsed.unwrap();
3419 let output = yaml_doc.to_string();
3420
3421 assert_eq!(output, yaml);
3423 }
3424
3425 #[test]
3426 fn test_literal_block_scalar_with_chomping_indicators() {
3427 let yaml1 = r#"strip: |-
3429 Line 1
3430 Line 2
3431
3432"#;
3433 let parsed1 = YamlFile::from_str(yaml1);
3434 assert!(
3435 parsed1.is_ok(),
3436 "Should parse literal block scalar with strip indicator"
3437 );
3438
3439 let file1 = parsed1.unwrap();
3440 let doc1 = file1.document().unwrap();
3441 let mapping1 = doc1.as_mapping().unwrap();
3442 let value1 = mapping1
3443 .get("strip")
3444 .unwrap()
3445 .as_scalar()
3446 .unwrap()
3447 .as_string();
3448 assert_eq!(value1, "Line 1\nLine 2");
3449
3450 let output1 = file1.to_string();
3451 assert_eq!(output1, yaml1);
3452
3453 let yaml2 = r#"keep: |+
3455 Line 1
3456 Line 2
3457
3458"#;
3459 let parsed2 = YamlFile::from_str(yaml2);
3460 assert!(
3461 parsed2.is_ok(),
3462 "Should parse literal block scalar with keep indicator"
3463 );
3464
3465 let file2 = parsed2.unwrap();
3466 let doc2 = file2.document().unwrap();
3467 let mapping2 = doc2.as_mapping().unwrap();
3468 let value2 = mapping2
3469 .get("keep")
3470 .unwrap()
3471 .as_scalar()
3472 .unwrap()
3473 .as_string();
3474 assert_eq!(value2, "Line 1\nLine 2\n\n");
3475
3476 let output2 = file2.to_string();
3477 assert_eq!(output2, yaml2);
3478 }
3479
3480 #[test]
3481 fn test_folded_block_scalar_with_chomping_indicators() {
3482 let yaml1 = r#"strip: >-
3484 Folded content that should
3485 be stripped of final newlines
3486"#;
3487 let parsed1 = YamlFile::from_str(yaml1);
3488 assert!(
3489 parsed1.is_ok(),
3490 "Should parse folded block scalar with strip indicator"
3491 );
3492
3493 let file1 = parsed1.unwrap();
3494 let doc1 = file1.document().unwrap();
3495 let mapping1 = doc1.as_mapping().unwrap();
3496 let value1 = mapping1
3497 .get("strip")
3498 .unwrap()
3499 .as_scalar()
3500 .unwrap()
3501 .as_string();
3502 assert_eq!(
3503 value1,
3504 "Folded content that should be stripped of final newlines"
3505 );
3506
3507 let output1 = file1.to_string();
3508 assert_eq!(output1, yaml1);
3509
3510 let yaml2 = r#"keep: >+
3512 Folded content that should
3513 keep all final newlines
3514
3515"#;
3516 let parsed2 = YamlFile::from_str(yaml2);
3517 assert!(
3518 parsed2.is_ok(),
3519 "Should parse folded block scalar with keep indicator"
3520 );
3521
3522 let file2 = parsed2.unwrap();
3523 let doc2 = file2.document().unwrap();
3524 let mapping2 = doc2.as_mapping().unwrap();
3525 let value2 = mapping2
3526 .get("keep")
3527 .unwrap()
3528 .as_scalar()
3529 .unwrap()
3530 .as_string();
3531 assert_eq!(
3532 value2,
3533 "Folded content that should keep all final newlines\n\n"
3534 );
3535
3536 let output2 = file2.to_string();
3537 assert_eq!(output2, yaml2);
3538 }
3539
3540 #[test]
3541 fn test_block_scalar_with_explicit_indentation() {
3542 let yaml1 = r#"explicit: |2
3543 Two space indent
3544 Another line
3545"#;
3546 let parsed1 = YamlFile::from_str(yaml1)
3547 .expect("Should parse literal block scalar with explicit indentation");
3548
3549 let doc1 = parsed1.document().expect("Should have document");
3550 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3551 let scalar1 = mapping1
3552 .get("explicit")
3553 .expect("Should have 'explicit' key");
3554 assert_eq!(
3555 scalar1.as_scalar().unwrap().as_string(),
3556 "Two space indent\nAnother line\n"
3557 );
3558
3559 let output1 = parsed1.to_string();
3560 assert_eq!(output1, yaml1);
3561
3562 let yaml2 = r#"folded_explicit: >3
3563 Three space indent
3564 Another folded line
3565"#;
3566 let parsed2 = YamlFile::from_str(yaml2)
3567 .expect("Should parse folded block scalar with explicit indentation");
3568
3569 let doc2 = parsed2.document().expect("Should have document");
3570 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
3571 let scalar2 = mapping2
3572 .get("folded_explicit")
3573 .expect("Should have 'folded_explicit' key");
3574 assert_eq!(
3575 scalar2.as_scalar().unwrap().as_string(),
3576 "Three space indent Another folded line\n"
3577 );
3578
3579 let output2 = parsed2.to_string();
3580 assert_eq!(output2, yaml2);
3581 }
3582
3583 #[test]
3584 fn test_block_scalar_in_mapping() {
3585 let yaml = r#"description: |
3586 This is a multi-line
3587 description that should
3588 preserve line breaks.
3589
3590 It can have multiple paragraphs too.
3591
3592summary: >
3593 This is a summary that
3594 should be folded into
3595 a single line.
3596
3597version: "1.0"
3598"#;
3599 let parsed =
3600 YamlFile::from_str(yaml).expect("Should parse block scalars in mapping context");
3601
3602 let doc = parsed.document().expect("Should have document");
3603 let mapping = doc.as_mapping().expect("Should be a mapping");
3604
3605 let description = mapping
3606 .get("description")
3607 .expect("Should have 'description' key");
3608 assert_eq!(
3609 description.as_scalar().unwrap().as_string(),
3610 "This is a multi-line\ndescription that should\npreserve line breaks.\n\nIt can have multiple paragraphs too.\n"
3611 );
3612
3613 let summary = mapping.get("summary").expect("Should have 'summary' key");
3614 assert_eq!(
3615 summary.as_scalar().unwrap().as_string(),
3616 "This is a summary that should be folded into a single line.\n"
3617 );
3618
3619 let version = mapping.get("version").expect("Should have 'version' key");
3620 assert_eq!(version.as_scalar().unwrap().as_string(), "1.0");
3621
3622 let output = parsed.to_string();
3623 assert_eq!(output, yaml);
3624 }
3625
3626 #[test]
3627 fn test_mixed_block_and_regular_scalars() {
3628 let yaml = r#"config:
3629 name: "My App"
3630 description: |
3631 This application does many things:
3632 - Feature 1
3633 - Feature 2
3634 - Feature 3
3635 summary: >
3636 A brief summary that spans
3637 multiple lines but should
3638 be folded together.
3639 version: 1.0
3640 enabled: true
3641"#;
3642 let parsed =
3643 YamlFile::from_str(yaml).expect("Should parse mixed block and regular scalars");
3644
3645 let doc = parsed.document().expect("Should have document");
3646 let mapping = doc.as_mapping().expect("Should be a mapping");
3647 let config_node = mapping.get("config").expect("Should have 'config' key");
3648 let config = config_node
3649 .as_mapping()
3650 .expect("Should be a nested mapping");
3651
3652 assert_eq!(
3653 config.get("name").unwrap().as_scalar().unwrap().as_string(),
3654 "My App"
3655 );
3656 assert_eq!(
3657 config
3658 .get("description")
3659 .unwrap()
3660 .as_scalar()
3661 .unwrap()
3662 .as_string(),
3663 "This application does many things:\n- Feature 1\n- Feature 2\n- Feature 3\n"
3664 );
3665 assert_eq!(
3666 config
3667 .get("summary")
3668 .unwrap()
3669 .as_scalar()
3670 .unwrap()
3671 .as_string(),
3672 "A brief summary that spans multiple lines but should be folded together.\n"
3673 );
3674 assert_eq!(
3675 config
3676 .get("version")
3677 .unwrap()
3678 .as_scalar()
3679 .unwrap()
3680 .as_string(),
3681 "1.0"
3682 );
3683 assert_eq!(config.get("enabled").unwrap().to_bool(), Some(true));
3684
3685 let output = parsed.to_string();
3686 assert_eq!(output, yaml);
3687 }
3688
3689 #[test]
3690 fn test_block_scalar_edge_cases() {
3691 let yaml1 = r#"empty_literal: |
3695empty_folded: >
3696"#;
3697 let parsed1 = YamlFile::from_str(yaml1).expect("Should parse this edge case");
3698
3699 let doc1 = parsed1.document().expect("Should have document");
3701 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3702 assert_eq!(mapping1.len(), 1, "Should have only one key");
3703 assert_eq!(
3704 mapping1
3705 .get("empty_literal")
3706 .unwrap()
3707 .as_scalar()
3708 .unwrap()
3709 .as_string(),
3710 "empty_folded: >\n"
3711 );
3712
3713 assert_eq!(parsed1.to_string(), yaml1);
3714
3715 let yaml2 = r#"whitespace: |
3717
3718
3719"#;
3720 let parsed2 =
3721 YamlFile::from_str(yaml2).expect("Should parse block scalar with only whitespace");
3722
3723 assert_eq!(parsed2.to_string(), yaml2);
3724
3725 let yaml3 = r#"first: |
3727 Content
3728second: value
3729"#;
3730 let parsed3 =
3731 YamlFile::from_str(yaml3).expect("Should parse block scalar followed by other keys");
3732
3733 let doc3 = parsed3.document().expect("Should have document");
3734 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
3735 assert_eq!(
3736 mapping3
3737 .get("first")
3738 .unwrap()
3739 .as_scalar()
3740 .unwrap()
3741 .as_string(),
3742 "Content\n"
3743 );
3744 assert_eq!(
3745 mapping3
3746 .get("second")
3747 .unwrap()
3748 .as_scalar()
3749 .unwrap()
3750 .as_string(),
3751 "value"
3752 );
3753
3754 let output3 = parsed3.to_string();
3755 assert_eq!(output3, yaml3);
3756 }
3757
3758 #[test]
3759 fn test_literal_block_scalar_advanced_formatting() {
3760 let yaml = r#"poem: |
3761 Roses are red,
3762 Violets are blue,
3763 YAML is great,
3764 And so are you!
3765
3766 This is another stanza
3767 with different content.
3768 And this line has extra indentation.
3769 Back to normal indentation.
3770
3771 Final stanza.
3772"#;
3773 let parsed = YamlFile::from_str(yaml).expect("Should parse complex literal block scalar");
3774
3775 let doc = parsed.document().expect("Should have document");
3776 let mapping = doc.as_mapping().expect("Should be a mapping");
3777 let poem = mapping.get("poem").expect("Should have 'poem' key");
3778 let expected_content = "Roses are red,\nViolets are blue,\nYAML is great,\nAnd so are you!\n\nThis is another stanza\nwith different content.\n And this line has extra indentation.\nBack to normal indentation.\n\nFinal stanza.\n";
3779 assert_eq!(poem.as_scalar().unwrap().as_string(), expected_content);
3780
3781 let output = parsed.to_string();
3782 assert_eq!(output, yaml);
3783 }
3784
3785 #[test]
3786 fn test_folded_block_scalar_paragraph_handling() {
3787 let yaml = r#"description: >
3788 This is the first paragraph that should
3789 be folded into a single line when processed
3790 by a YAML parser.
3791
3792 This is a second paragraph that should
3793 also be folded but kept separate from
3794 the first paragraph.
3795
3796
3797 This is a third paragraph after
3798 multiple blank lines.
3799
3800 Final paragraph.
3801"#;
3802 let parsed =
3803 YamlFile::from_str(yaml).expect("Should parse folded block scalar with paragraphs");
3804
3805 let doc = parsed.document().expect("Should have document");
3806 let mapping = doc.as_mapping().expect("Should be a mapping");
3807 let description = mapping
3808 .get("description")
3809 .expect("Should have 'description' key");
3810 let expected_content = "This is the first paragraph that should be folded into a single line when processed by a YAML parser.\nThis is a second paragraph that should also be folded but kept separate from the first paragraph.\nThis is a third paragraph after multiple blank lines.\nFinal paragraph.\n";
3811 assert_eq!(
3812 description.as_scalar().unwrap().as_string(),
3813 expected_content
3814 );
3815
3816 let output = parsed.to_string();
3817 assert_eq!(output, yaml);
3818 }
3819
3820 #[test]
3821 fn test_block_scalars_with_special_characters() {
3822 let yaml = r#"special_chars: |
3823 Line with colons: key: value
3824 Line with dashes - and more - dashes
3825 Line with quotes "double" and 'single'
3826 Line with brackets [array] and braces {object}
3827 Line with pipes | and greater than >
3828 Line with at @ and hash # symbols
3829 Line with percent % and exclamation !
3830
3831backslash_test: >
3832 This line has a backslash \ in it
3833 And this line has multiple \\ backslashes
3834
3835unicode_test: |
3836 This line has unicode: 你好世界
3837 And emojis: 🚀 🎉 ✨
3838"#;
3839 let parsed =
3840 YamlFile::from_str(yaml).expect("Should parse block scalars with special characters");
3841
3842 let doc = parsed.document().expect("Should have document");
3843 let mapping = doc.as_mapping().expect("Should be a mapping");
3844
3845 let special_chars = mapping
3846 .get("special_chars")
3847 .expect("Should have 'special_chars' key");
3848 assert_eq!(
3849 special_chars.as_scalar().unwrap().as_string(),
3850 "Line with colons: key: value\nLine with dashes - and more - dashes\nLine with quotes \"double\" and 'single'\nLine with brackets [array] and braces {object}\nLine with pipes | and greater than >\nLine with at @ and hash # symbols\nLine with percent % and exclamation !\n"
3851 );
3852
3853 let backslash_test = mapping
3854 .get("backslash_test")
3855 .expect("Should have 'backslash_test' key");
3856 assert_eq!(
3857 backslash_test.as_scalar().unwrap().as_string(),
3858 "This line has a backslash \\ in it And this line has multiple \\\\ backslashes\n"
3859 );
3860
3861 let unicode_test = mapping
3862 .get("unicode_test")
3863 .expect("Should have 'unicode_test' key");
3864 assert_eq!(
3865 unicode_test.as_scalar().unwrap().as_string(),
3866 "This line has unicode: 你好世界\nAnd emojis: 🚀 🎉 ✨\n"
3867 );
3868
3869 let output = parsed.to_string();
3870 assert_eq!(output, yaml);
3871 }
3872
3873 #[test]
3874 fn test_block_scalar_chomping_detailed() {
3875 let yaml_clip = r#"clip: |
3877 Line 1
3878 Line 2
3879
3880"#;
3881 let parsed_clip =
3882 YamlFile::from_str(yaml_clip).expect("Should parse block scalar with default clipping");
3883
3884 let doc_clip = parsed_clip.document().expect("Should have document");
3886 let mapping_clip = doc_clip.as_mapping().expect("Should be a mapping");
3887 assert_eq!(
3888 mapping_clip
3889 .get("clip")
3890 .unwrap()
3891 .as_scalar()
3892 .unwrap()
3893 .as_string(),
3894 "Line 1\nLine 2\n"
3895 );
3896
3897 assert_eq!(parsed_clip.to_string(), yaml_clip);
3898
3899 let yaml_strip = r#"strip: |-
3901 Line 1
3902 Line 2
3903
3904
3905
3906"#;
3907 let parsed_strip =
3908 YamlFile::from_str(yaml_strip).expect("Should parse block scalar with strip indicator");
3909
3910 let doc_strip = parsed_strip.document().expect("Should have document");
3912 let mapping_strip = doc_strip.as_mapping().expect("Should be a mapping");
3913 assert_eq!(
3914 mapping_strip
3915 .get("strip")
3916 .unwrap()
3917 .as_scalar()
3918 .unwrap()
3919 .as_string(),
3920 "Line 1\nLine 2"
3921 );
3922
3923 assert_eq!(parsed_strip.to_string(), yaml_strip);
3924
3925 let yaml_keep = r#"keep: |+
3927 Line 1
3928 Line 2
3929
3930
3931
3932"#;
3933 let parsed_keep =
3934 YamlFile::from_str(yaml_keep).expect("Should parse block scalar with keep indicator");
3935
3936 let doc_keep = parsed_keep.document().expect("Should have document");
3938 let mapping_keep = doc_keep.as_mapping().expect("Should be a mapping");
3939 assert_eq!(
3940 mapping_keep
3941 .get("keep")
3942 .unwrap()
3943 .as_scalar()
3944 .unwrap()
3945 .as_string(),
3946 "Line 1\nLine 2\n\n\n\n"
3947 );
3948
3949 assert_eq!(parsed_keep.to_string(), yaml_keep);
3950 }
3951
3952 #[test]
3953 fn test_block_scalar_explicit_indentation_detailed() {
3954 let yaml1 = r#"indent1: |1
3956 Single space indent
3957"#;
3958 let parsed1 = YamlFile::from_str(yaml1);
3959 assert!(parsed1.is_ok(), "Should parse |1 block scalar");
3960 let output1 = parsed1.unwrap().to_string();
3961 assert_eq!(output1, yaml1);
3962
3963 let yaml2 = r#"indent2: |2
3964 Two space indent
3965"#;
3966 let parsed2 = YamlFile::from_str(yaml2);
3967 assert!(parsed2.is_ok(), "Should parse |2 block scalar");
3968 let output2 = parsed2.unwrap().to_string();
3969 assert_eq!(output2, yaml2);
3970
3971 let yaml3 = r#"folded_indent: >2
3972 Two space folded
3973 content spans lines
3974"#;
3975 let parsed3 = YamlFile::from_str(yaml3);
3976 assert!(parsed3.is_ok(), "Should parse >2 folded block scalar");
3977 let output3 = parsed3.unwrap().to_string();
3978 assert_eq!(output3, yaml3);
3979 }
3980
3981 #[test]
3982 fn test_block_scalar_combined_indicators() {
3983 let yaml = r#"strip_with_indent: |2-
3984 Content with explicit indent
3985 and strip chomping
3986
3987
3988keep_with_indent: >3+
3989 Content with explicit indent
3990 and keep chomping
3991
3992
3993
3994folded_strip: >-
3995 Folded content
3996 with strip indicator
3997
3998literal_keep: |+
3999 Literal content
4000 with keep indicator
4001
4002
4003"#;
4004 let parsed =
4005 YamlFile::from_str(yaml).expect("Should parse block scalars with combined indicators");
4006
4007 let doc = parsed.document().expect("Should have document");
4008 let mapping = doc.as_mapping().expect("Should be a mapping");
4009
4010 assert_eq!(
4011 mapping
4012 .get("strip_with_indent")
4013 .unwrap()
4014 .as_scalar()
4015 .unwrap()
4016 .as_string(),
4017 "Content with explicit indent\nand strip chomping"
4018 );
4019 assert_eq!(
4020 mapping
4021 .get("keep_with_indent")
4022 .unwrap()
4023 .as_scalar()
4024 .unwrap()
4025 .as_string(),
4026 "Content with explicit indent and keep chomping\n\n\n\n"
4027 );
4028 assert_eq!(
4029 mapping
4030 .get("folded_strip")
4031 .unwrap()
4032 .as_scalar()
4033 .unwrap()
4034 .as_string(),
4035 "Folded content with strip indicator"
4036 );
4037 assert_eq!(
4038 mapping
4039 .get("literal_keep")
4040 .unwrap()
4041 .as_scalar()
4042 .unwrap()
4043 .as_string(),
4044 "Literal content\nwith keep indicator\n\n\n"
4045 );
4046
4047 let output = parsed.to_string();
4048 assert_eq!(output, yaml);
4049 }
4050
4051 #[test]
4052 fn test_block_scalar_whitespace_and_empty() {
4053 let yaml1 = r#"whitespace_only: |
4055
4056
4057
4058"#;
4059 let parsed1 =
4060 YamlFile::from_str(yaml1).expect("Should handle block scalar with only whitespace");
4061
4062 let doc1 = parsed1.document().expect("Should have document");
4063 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
4064 assert_eq!(
4065 mapping1
4066 .get("whitespace_only")
4067 .unwrap()
4068 .as_scalar()
4069 .unwrap()
4070 .as_string(),
4071 "\n"
4072 );
4073
4074 assert_eq!(parsed1.to_string(), yaml1);
4075
4076 let yaml2 = r#"mixed_indent: |
4078 Normal line
4079 Indented line
4080 Back to normal
4081 More indented
4082 Normal again
4083"#;
4084 let parsed2 = YamlFile::from_str(yaml2).expect("Should handle mixed indentation levels");
4085
4086 let doc2 = parsed2.document().expect("Should have document");
4087 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
4088 assert_eq!(
4089 mapping2
4090 .get("mixed_indent")
4091 .unwrap()
4092 .as_scalar()
4093 .unwrap()
4094 .as_string(),
4095 "Normal line\n Indented line\nBack to normal\n More indented\nNormal again\n"
4096 );
4097
4098 assert_eq!(parsed2.to_string(), yaml2);
4099
4100 let yaml3 = r#"first: |
4102 Content
4103immediate: value
4104another: |
4105 More content
4106final: end
4107"#;
4108 let parsed3 =
4109 YamlFile::from_str(yaml3).expect("Should handle multiple block scalars in mapping");
4110
4111 let doc3 = parsed3.document().expect("Should have document");
4112 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
4113 assert_eq!(
4114 mapping3
4115 .get("first")
4116 .unwrap()
4117 .as_scalar()
4118 .unwrap()
4119 .as_string(),
4120 "Content\n"
4121 );
4122 assert_eq!(
4123 mapping3
4124 .get("immediate")
4125 .unwrap()
4126 .as_scalar()
4127 .unwrap()
4128 .as_string(),
4129 "value"
4130 );
4131 assert_eq!(
4132 mapping3
4133 .get("another")
4134 .unwrap()
4135 .as_scalar()
4136 .unwrap()
4137 .as_string(),
4138 "More content\n"
4139 );
4140 assert_eq!(
4141 mapping3
4142 .get("final")
4143 .unwrap()
4144 .as_scalar()
4145 .unwrap()
4146 .as_string(),
4147 "end"
4148 );
4149
4150 let output3 = parsed3.to_string();
4151 assert_eq!(output3, yaml3);
4152 }
4153
4154 #[test]
4155 fn test_block_scalar_with_comments() {
4156 let yaml = r#"# Main configuration
4157config: | # This is a literal block
4158 # This comment is inside the block
4159 line1: value1
4160 # Another internal comment
4161 line2: value2
4162
4163# Outside comment
4164other: > # Folded block comment
4165 This content spans
4166 # This hash is part of the content, not a comment
4167 multiple lines
4168"#;
4169 let parsed = YamlFile::from_str(yaml).expect("Should parse block scalars with comments");
4170
4171 let doc = parsed.document().expect("Should have document");
4172 let mapping = doc.as_mapping().expect("Should be a mapping");
4173
4174 assert_eq!(
4181 mapping.get("config").unwrap().as_scalar().unwrap().as_string(),
4182 "# This comment is inside the block\nline1: value1\n# Another internal comment\nline2: value2\n\n# Outside comment\n"
4183 );
4184
4185 assert_eq!(
4186 mapping
4187 .get("other")
4188 .unwrap()
4189 .as_scalar()
4190 .unwrap()
4191 .as_string(),
4192 "This content spans # This hash is part of the content, not a comment multiple lines\n"
4193 );
4194
4195 let output = parsed.to_string();
4196 assert_eq!(output, yaml);
4197 }
4198
4199 #[test]
4200 fn test_block_scalar_empty_and_minimal() {
4201 let yaml = r#"empty_literal: |
4202
4203empty_folded: >
4204
4205minimal_literal: |
4206 x
4207
4208minimal_folded: >
4209 y
4210
4211just_newlines: |
4212
4213
4214
4215just_spaces: |
4216
4217
4218
4219"#;
4220 let parsed =
4221 YamlFile::from_str(yaml).expect("Should handle empty and minimal block scalars");
4222
4223 let doc = parsed.document().expect("Should have document");
4224 let mapping = doc.as_mapping().expect("Should be a mapping");
4225
4226 assert_eq!(
4227 mapping
4228 .get("empty_literal")
4229 .unwrap()
4230 .as_scalar()
4231 .unwrap()
4232 .as_string(),
4233 "\n"
4234 );
4235 assert_eq!(
4236 mapping
4237 .get("empty_folded")
4238 .unwrap()
4239 .as_scalar()
4240 .unwrap()
4241 .as_string(),
4242 "\n"
4243 );
4244 assert_eq!(
4245 mapping
4246 .get("minimal_literal")
4247 .unwrap()
4248 .as_scalar()
4249 .unwrap()
4250 .as_string(),
4251 "x\n"
4252 );
4253 assert_eq!(
4254 mapping
4255 .get("minimal_folded")
4256 .unwrap()
4257 .as_scalar()
4258 .unwrap()
4259 .as_string(),
4260 "y\n"
4261 );
4262 assert_eq!(
4263 mapping
4264 .get("just_newlines")
4265 .unwrap()
4266 .as_scalar()
4267 .unwrap()
4268 .as_string(),
4269 "\n"
4270 );
4271 assert_eq!(
4272 mapping
4273 .get("just_spaces")
4274 .unwrap()
4275 .as_scalar()
4276 .unwrap()
4277 .as_string(),
4278 "\n"
4279 );
4280
4281 let output = parsed.to_string();
4282 assert_eq!(output, yaml);
4283 }
4284
4285 #[test]
4286 fn test_block_scalar_with_document_markers() {
4287 let yaml = r#"---
4288doc1: |
4289 This is the first document
4290 with a literal block scalar.
4291
4292next_key: value
4293---
4294doc2: >
4295 This is the second document
4296 with a folded block scalar.
4297
4298another_key: another_value
4299...
4300"#;
4301 let parsed =
4302 YamlFile::from_str(yaml).expect("Should parse block scalars with document markers");
4303
4304 let doc = parsed.document().expect("Should have first document");
4306 let mapping = doc.as_mapping().expect("Should be a mapping");
4307
4308 assert_eq!(
4309 mapping
4310 .get("doc1")
4311 .unwrap()
4312 .as_scalar()
4313 .unwrap()
4314 .as_string(),
4315 "This is the first document\nwith a literal block scalar.\n"
4316 );
4317 assert_eq!(
4318 mapping
4319 .get("next_key")
4320 .unwrap()
4321 .as_scalar()
4322 .unwrap()
4323 .as_string(),
4324 "value"
4325 );
4326
4327 let output = parsed.to_string();
4329 assert_eq!(output, yaml);
4330 }
4331
4332 #[test]
4333 fn test_block_scalar_formatting_preservation() {
4334 let original = r#"preserve_me: |
4335 Line with multiple spaces
4336 Line with tabs here
4337 Line with trailing spaces
4338
4339 Empty line above and below
4340
4341 Final line
4342"#;
4343 let parsed = YamlFile::from_str(original).expect("Should preserve exact formatting");
4344
4345 let doc = parsed.document().expect("Should have document");
4346 let mapping = doc.as_mapping().expect("Should be a mapping");
4347
4348 let preserve_me = mapping
4349 .get("preserve_me")
4350 .expect("Should have 'preserve_me' key");
4351 let expected = "Line with multiple spaces\nLine with\ttabs\there\nLine with trailing spaces\n\nEmpty line above and below\n\nFinal line\n";
4352 assert_eq!(preserve_me.as_scalar().unwrap().as_string(), expected);
4353
4354 let output = parsed.to_string();
4356 assert_eq!(output, original);
4357 }
4358
4359 #[test]
4360 fn test_block_scalar_complex_yaml_content() {
4361 let yaml = r#"yaml_content: |
4362 # This block contains YAML-like content
4363 nested:
4364 - item: value
4365 - item: another
4366
4367 mapping:
4368 key1: |
4369 Even more nested literal content
4370 key2: value
4371
4372 anchors: &anchor
4373 anchor_content: data
4374
4375 reference: *anchor
4376
4377quoted_yaml: >
4378 This folded block contains
4379 YAML structures: {key: value, array: [1, 2, 3]}
4380 that should be treated as plain text.
4381"#;
4382 let parsed = YamlFile::from_str(yaml)
4383 .expect("Should parse block scalars containing YAML-like structures");
4384
4385 let doc = parsed.document().expect("Should have document");
4386 let mapping = doc.as_mapping().expect("Should be a mapping");
4387
4388 let expected_yaml_content = "# This block contains YAML-like content\nnested:\n - item: value\n - item: another\n\nmapping:\n key1: |\n Even more nested literal content\n key2: value\n\nanchors: &anchor\n anchor_content: data\n\nreference: *anchor\n";
4389 assert_eq!(
4390 mapping
4391 .get("yaml_content")
4392 .unwrap()
4393 .as_scalar()
4394 .unwrap()
4395 .as_string(),
4396 expected_yaml_content
4397 );
4398
4399 let expected_quoted_yaml = "This folded block contains YAML structures: {key: value, array: [1, 2, 3]} that should be treated as plain text.\n";
4400 assert_eq!(
4401 mapping
4402 .get("quoted_yaml")
4403 .unwrap()
4404 .as_scalar()
4405 .unwrap()
4406 .as_string(),
4407 expected_quoted_yaml
4408 );
4409
4410 let output = parsed.to_string();
4411 assert_eq!(output, yaml);
4412 }
4413
4414 #[test]
4415 fn test_block_scalar_performance_large_content() {
4416 let mut large_content = String::new();
4418 for i in 1..=100 {
4419 large_content.push_str(&format!(
4420 " Line number {} with some content that makes it longer\n",
4421 i
4422 ));
4423 }
4424
4425 let yaml = format!(
4426 "large_literal: |\n{}\nlarge_folded: >\n{}\n",
4427 large_content, large_content
4428 );
4429
4430 let parsed =
4431 YamlFile::from_str(&yaml).expect("Should parse large block scalars without errors");
4432
4433 let doc = parsed.document().expect("Should have document");
4434 let mapping = doc.as_mapping().expect("Should be a mapping");
4435
4436 let literal_value = mapping
4437 .get("large_literal")
4438 .expect("Should have large_literal key")
4439 .as_scalar()
4440 .expect("Should be scalar")
4441 .as_string();
4442
4443 let mut expected_literal = String::new();
4445 for i in 1..=100 {
4446 expected_literal.push_str(&format!(
4447 "Line number {} with some content that makes it longer\n",
4448 i
4449 ));
4450 }
4451 assert_eq!(literal_value, expected_literal);
4452
4453 let folded_value = mapping
4454 .get("large_folded")
4455 .expect("Should have large_folded key")
4456 .as_scalar()
4457 .expect("Should be scalar")
4458 .as_string();
4459
4460 let mut expected_folded = String::new();
4462 for i in 1..=100 {
4463 if i > 1 {
4464 expected_folded.push(' ');
4465 }
4466 expected_folded.push_str(&format!(
4467 "Line number {} with some content that makes it longer",
4468 i
4469 ));
4470 }
4471 expected_folded.push('\n');
4472 assert_eq!(folded_value, expected_folded);
4473
4474 let output = parsed.to_string();
4475 assert_eq!(output, yaml);
4476 }
4477
4478 #[test]
4479 fn test_block_scalar_error_recovery() {
4480 let yaml = r#"good_key: value
4482bad_block: |
4483incomplete_key
4484another_good: works
4485"#;
4486 let parsed = YamlFile::from_str(yaml).expect("Should parse");
4487
4488 let doc = parsed.document().expect("Should have document");
4489 let mapping = doc.as_mapping().expect("Should be a mapping");
4490
4491 assert_eq!(
4493 mapping
4494 .get("good_key")
4495 .unwrap()
4496 .as_scalar()
4497 .unwrap()
4498 .as_string(),
4499 "value"
4500 );
4501
4502 assert_eq!(
4504 mapping
4505 .get("bad_block")
4506 .unwrap()
4507 .as_scalar()
4508 .unwrap()
4509 .as_string(),
4510 "incomplete_key\n"
4511 );
4512
4513 assert_eq!(
4515 mapping
4516 .get("another_good")
4517 .unwrap()
4518 .as_scalar()
4519 .unwrap()
4520 .as_string(),
4521 "works"
4522 );
4523
4524 let output = parsed.to_string();
4525 assert_eq!(output, yaml);
4526 }
4527
4528 #[test]
4529 fn test_block_scalar_with_flow_structures() {
4530 let yaml = r#"mixed_styles: |
4531 This literal block contains:
4532 - A flow sequence: [1, 2, 3]
4533 - A flow mapping: {key: value, other: data}
4534 - Mixed content: [a, {nested: true}, c]
4535
4536flow_then_block:
4537 flow_seq: [item1, item2]
4538 block_literal: |
4539 This comes after flow style
4540 and should work fine.
4541 flow_map: {after: block}
4542"#;
4543 let parsed = YamlFile::from_str(yaml).expect("Should parse mixed flow and block styles");
4544
4545 let doc = parsed.document().expect("Should have document");
4546 let mapping = doc.as_mapping().expect("Should be a mapping");
4547
4548 let expected_mixed = "This literal block contains:\n- A flow sequence: [1, 2, 3]\n- A flow mapping: {key: value, other: data}\n- Mixed content: [a, {nested: true}, c]\n";
4550 assert_eq!(
4551 mapping
4552 .get("mixed_styles")
4553 .unwrap()
4554 .as_scalar()
4555 .unwrap()
4556 .as_string(),
4557 expected_mixed
4558 );
4559
4560 let flow_then_block_value = mapping.get("flow_then_block").unwrap();
4562 let flow_then_block = flow_then_block_value.as_mapping().unwrap();
4563
4564 let flow_seq_value = flow_then_block.get("flow_seq").unwrap();
4566 let flow_seq = flow_seq_value.as_sequence().unwrap();
4567 assert_eq!(flow_seq.len(), 2);
4568 assert_eq!(
4569 flow_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
4570 "item1"
4571 );
4572 assert_eq!(
4573 flow_seq.get(1).unwrap().as_scalar().unwrap().as_string(),
4574 "item2"
4575 );
4576
4577 assert_eq!(
4579 flow_then_block
4580 .get("block_literal")
4581 .unwrap()
4582 .as_scalar()
4583 .unwrap()
4584 .as_string(),
4585 "This comes after flow style\nand should work fine.\n"
4586 );
4587
4588 let flow_map_value = flow_then_block.get("flow_map").unwrap();
4590 let flow_map = flow_map_value.as_mapping().unwrap();
4591 assert_eq!(
4592 flow_map
4593 .get("after")
4594 .unwrap()
4595 .as_scalar()
4596 .unwrap()
4597 .as_string(),
4598 "block"
4599 );
4600
4601 let output = parsed.to_string();
4602 assert_eq!(output, yaml);
4603 }
4604
4605 #[test]
4606 fn test_block_scalar_indentation_edge_cases() {
4607 let yaml1 = r#"empty: |
4609next: value"#;
4610 let parsed1 = YamlFile::from_str(yaml1);
4611 assert!(parsed1.is_ok(), "Should handle empty block followed by key");
4612
4613 let yaml2 = r#"inconsistent: |
4615 normal indent
4616 more indent
4617 back to normal
4618 even more
4619 normal
4620"#;
4621 let parsed2 = YamlFile::from_str(yaml2);
4622 assert!(
4623 parsed2.is_ok(),
4624 "Should handle inconsistent but valid indentation"
4625 );
4626
4627 let yaml3 = "tabs: |\n\tTab indented line\n\tAnother tab line\n";
4629 let parsed3 = YamlFile::from_str(yaml3);
4630 assert!(
4631 parsed3.is_ok(),
4632 "Should handle tab characters in block scalars"
4633 );
4634 }
4635
4636 #[test]
4637 fn test_block_scalar_with_anchors_and_aliases() {
4638 let yaml = r#"template: &template |
4639 This is a template
4640 with multiple lines
4641 that can be referenced.
4642
4643instance1: *template
4644
4645instance2:
4646 content: *template
4647 other: value
4648
4649modified: |
4650 <<: *template
4651 Additional content here
4652"#;
4653 let parsed =
4654 YamlFile::from_str(yaml).expect("Should parse block scalars with anchors and aliases");
4655
4656 let doc = parsed.document().expect("Should have document");
4657 let mapping = doc.as_mapping().expect("Should be a mapping");
4658
4659 let expected_template =
4661 "This is a template\nwith multiple lines\nthat can be referenced.\n";
4662 let template_value = mapping.get("template").unwrap();
4663 assert_eq!(
4664 template_value.as_scalar().unwrap().as_string(),
4665 expected_template
4666 );
4667
4668 let instance1 = mapping.get("instance1").unwrap();
4670 assert!(
4671 instance1.is_alias(),
4672 "instance1 should be an alias, not a scalar"
4673 );
4674 assert_eq!(instance1.as_alias().unwrap().name(), "template");
4675
4676 let instance2_value = mapping.get("instance2").unwrap();
4678 let instance2 = instance2_value.as_mapping().unwrap();
4679
4680 let content = instance2.get("content").unwrap();
4682 assert!(
4683 content.is_alias(),
4684 "content should be an alias, not a scalar"
4685 );
4686 assert_eq!(content.as_alias().unwrap().name(), "template");
4687
4688 assert_eq!(
4690 instance2
4691 .get("other")
4692 .unwrap()
4693 .as_scalar()
4694 .unwrap()
4695 .as_string(),
4696 "value"
4697 );
4698
4699 let modified = mapping.get("modified").unwrap();
4702 assert!(
4703 modified.is_scalar(),
4704 "modified should be a scalar, not an alias"
4705 );
4706 assert_eq!(
4707 modified.as_scalar().unwrap().as_string(),
4708 "<<: *template\nAdditional content here\n"
4709 );
4710
4711 let output = parsed.to_string();
4712 assert_eq!(output, yaml);
4713 }
4714
4715 #[test]
4716 fn test_block_scalar_newline_variations() {
4717 let yaml_unix = "unix: |\n Line 1\n Line 2\n";
4719 let parsed_unix = YamlFile::from_str(yaml_unix).expect("Should handle Unix newlines");
4720
4721 let yaml_windows = "windows: |\r\n Line 1\r\n Line 2\r\n";
4722 let parsed_windows =
4723 YamlFile::from_str(yaml_windows).expect("Should handle Windows newlines");
4724
4725 let doc_unix = parsed_unix.document().expect("Should have document");
4727 let mapping_unix = doc_unix.as_mapping().expect("Should be a mapping");
4728 assert_eq!(
4729 mapping_unix
4730 .get("unix")
4731 .unwrap()
4732 .as_scalar()
4733 .unwrap()
4734 .as_string(),
4735 "Line 1\nLine 2\n"
4736 );
4737
4738 let doc_windows = parsed_windows.document().expect("Should have document");
4740 let mapping_windows = doc_windows.as_mapping().expect("Should be a mapping");
4741 assert_eq!(
4742 mapping_windows
4743 .get("windows")
4744 .unwrap()
4745 .as_scalar()
4746 .unwrap()
4747 .as_string(),
4748 "Line 1\nLine 2\n"
4749 );
4750
4751 assert_eq!(parsed_unix.to_string(), yaml_unix);
4752 assert_eq!(parsed_windows.to_string(), yaml_windows);
4753 }
4754
4755 #[test]
4756 fn test_block_scalar_boundary_detection() {
4757 let yaml = r#"config:
4759 description: |
4760 This is a configuration
4761 with multiple lines.
4762
4763 name: "MyApp"
4764 version: 1.0
4765
4766 settings: >
4767 These are settings that
4768 span multiple lines too.
4769
4770 debug: true
4771"#;
4772 let parsed =
4773 YamlFile::from_str(yaml).expect("Should properly detect block scalar boundaries");
4774
4775 let doc = parsed.document().expect("Should have document");
4776 let mapping = doc.as_mapping().expect("Should be a mapping");
4777 let config_value = mapping.get("config").unwrap();
4778 let config = config_value.as_mapping().unwrap();
4779
4780 assert_eq!(
4782 config
4783 .get("description")
4784 .unwrap()
4785 .as_scalar()
4786 .unwrap()
4787 .as_string(),
4788 "This is a configuration\nwith multiple lines.\n"
4789 );
4790
4791 assert_eq!(
4793 config.get("name").unwrap().as_scalar().unwrap().as_string(),
4794 "MyApp"
4795 );
4796
4797 assert_eq!(
4799 config
4800 .get("version")
4801 .unwrap()
4802 .as_scalar()
4803 .unwrap()
4804 .as_string(),
4805 "1.0"
4806 );
4807
4808 assert_eq!(
4810 config
4811 .get("settings")
4812 .unwrap()
4813 .as_scalar()
4814 .unwrap()
4815 .as_string(),
4816 "These are settings that span multiple lines too.\n"
4817 );
4818
4819 assert_eq!(
4821 config
4822 .get("debug")
4823 .unwrap()
4824 .as_scalar()
4825 .unwrap()
4826 .as_string(),
4827 "true"
4828 );
4829
4830 let output = parsed.to_string();
4831 assert_eq!(output, yaml);
4832 }
4833
4834 #[test]
4835 fn test_block_scalar_with_numeric_content() {
4836 let yaml = r#"numbers_as_text: |
4837 123
4838 45.67
4839 -89
4840 +100
4841 0xFF
4842 1e5
4843 true
4844 false
4845 null
4846
4847calculations: >
4848 The result is: 2 + 2 = 4
4849 And 10 * 5 = 50
4850 Also: 100% complete
4851"#;
4852 let parsed = YamlFile::from_str(yaml)
4853 .expect("Should parse numeric content as text in block scalars");
4854
4855 let doc = parsed.document().expect("Should have document");
4856 let mapping = doc.as_mapping().expect("Should be a mapping");
4857
4858 let expected_numbers = "123\n45.67\n-89\n+100\n0xFF\n1e5\ntrue\nfalse\nnull\n";
4860 assert_eq!(
4861 mapping
4862 .get("numbers_as_text")
4863 .unwrap()
4864 .as_scalar()
4865 .unwrap()
4866 .as_string(),
4867 expected_numbers
4868 );
4869
4870 let expected_calculations =
4872 "The result is: 2 + 2 = 4 And 10 * 5 = 50 Also: 100% complete\n";
4873 assert_eq!(
4874 mapping
4875 .get("calculations")
4876 .unwrap()
4877 .as_scalar()
4878 .unwrap()
4879 .as_string(),
4880 expected_calculations
4881 );
4882
4883 let output = parsed.to_string();
4884 assert_eq!(output, yaml);
4885 }
4886
4887 #[test]
4888 fn test_block_scalar_exact_preservation() {
4889 let test_cases = [
4891 r#"simple: |
4893 Hello World
4894"#,
4895 r#"folded: >
4897 Hello World
4898"#,
4899 r#"strip: |-
4901 Content
4902
4903keep: |+
4904 Content
4905
4906"#,
4907 r#"explicit: |2
4909 Two space indent
4910"#,
4911 r#"config:
4913 script: |
4914 #!/bin/bash
4915 echo "Starting deployment"
4916
4917 for service in api web worker; do
4918 echo "Deploying $service"
4919 kubectl apply -f $service.yaml
4920 done
4921
4922 description: >
4923 This configuration defines a deployment
4924 script that will be executed during
4925 the CI/CD pipeline.
4926"#,
4927 ];
4928
4929 for (i, yaml) in test_cases.iter().enumerate() {
4930 let parsed = YamlFile::from_str(yaml);
4931 assert!(parsed.is_ok(), "Test case {} should parse successfully", i);
4932
4933 let output = parsed.unwrap().to_string();
4934 assert_eq!(
4935 output, *yaml,
4936 "Test case {} should preserve exact formatting",
4937 i
4938 );
4939 }
4940 }
4941
4942 #[test]
4943 fn test_block_scalar_chomping_exact() {
4944 let yaml_strip = r#"strip: |-
4945 Content
4946"#;
4947 let parsed_strip = YamlFile::from_str(yaml_strip).unwrap();
4948 assert_eq!(parsed_strip.to_string(), yaml_strip);
4949
4950 let yaml_keep = r#"keep: |+
4951 Content
4952
4953"#;
4954 let parsed_keep = YamlFile::from_str(yaml_keep).unwrap();
4955 assert_eq!(parsed_keep.to_string(), yaml_keep);
4956
4957 let yaml_folded_strip = r#"folded: >-
4958 Content
4959"#;
4960 let parsed_folded_strip = YamlFile::from_str(yaml_folded_strip).unwrap();
4961 assert_eq!(parsed_folded_strip.to_string(), yaml_folded_strip);
4962 }
4963
4964 #[test]
4965 fn test_block_scalar_indentation_exact() {
4966 let yaml1 = r#"indent1: |1
4967 Single space
4968"#;
4969 let parsed1 = YamlFile::from_str(yaml1).unwrap();
4970 assert_eq!(parsed1.to_string(), yaml1);
4971
4972 let yaml2 = r#"indent2: |2
4973 Two spaces
4974"#;
4975 let parsed2 = YamlFile::from_str(yaml2).unwrap();
4976 assert_eq!(parsed2.to_string(), yaml2);
4977
4978 let yaml3 = r#"combined: |3+
4979 Content with keep
4980
4981"#;
4982 let parsed3 = YamlFile::from_str(yaml3).unwrap();
4983 assert_eq!(parsed3.to_string(), yaml3);
4984 }
4985
4986 #[test]
4987 fn test_block_scalar_mapping_exact() {
4988 let yaml = r#"description: |
4989 Line 1
4990 Line 2
4991
4992summary: >
4993 Folded content
4994
4995version: "1.0"
4996"#;
4997 let parsed = YamlFile::from_str(yaml).unwrap();
4998 assert_eq!(parsed.to_string(), yaml);
4999 }
5000
5001 #[test]
5002 fn test_block_scalar_sequence_exact() {
5003 let yaml = r#"items:
5004 - |
5005 First item content
5006 with multiple lines
5007
5008 - >
5009 Second item folded
5010 content
5011
5012 - regular_item
5013"#;
5014 let parsed = YamlFile::from_str(yaml).unwrap();
5015 assert_eq!(parsed.to_string(), yaml);
5016 }
5017
5018 #[test]
5019 fn test_block_scalar_empty_exact() {
5020 let yaml1 = r#"empty: |
5021
5022"#;
5023 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5024 assert_eq!(parsed1.to_string(), yaml1);
5025
5026 let yaml2 = r#"empty_folded: >
5027
5028"#;
5029 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5030 assert_eq!(parsed2.to_string(), yaml2);
5031 }
5032
5033 #[test]
5034 fn test_empty_documents_in_stream() {
5035 let yaml = "---\n---\nkey: value\n---\n...\n";
5037 let parsed = YamlFile::from_str(yaml).unwrap();
5038 assert_eq!(parsed.documents().count(), 3);
5039 assert_eq!(parsed.to_string(), yaml);
5040 }
5041
5042 #[test]
5043 fn test_mixed_document_end_markers() {
5044 let yaml = "---\nfirst: doc\n...\n---\nsecond: doc\n---\nthird: doc\n...\n";
5046 let parsed = YamlFile::from_str(yaml).unwrap();
5047 assert_eq!(parsed.documents().count(), 3);
5048 assert_eq!(parsed.to_string(), yaml);
5049 }
5050
5051 #[test]
5052 fn test_complex_document_stream() {
5053 let yaml = r#"%YAML 1.2
5054%TAG ! tag:example.com,2000:app/
5055---
5056template: &anchor
5057 key: !custom value
5058instance:
5059 <<: *anchor
5060 extra: data
5061...
5062%YAML 1.2
5063---
5064- item1
5065- item2: nested
5066...
5067---
5068literal: |
5069 Block content
5070 Multiple lines
5071folded: >
5072 Folded content
5073 on multiple lines
5074...
5075"#;
5076 let parsed = YamlFile::from_str(yaml).unwrap();
5077 assert_eq!(parsed.documents().count(), 3);
5078 assert_eq!(parsed.to_string(), yaml);
5079 }
5080
5081 #[test]
5082 fn test_number_format_parsing() {
5083 let yaml = YamlFile::from_str("value: 0b1010").unwrap();
5085 assert_eq!(yaml.to_string().trim(), "value: 0b1010");
5086
5087 let yaml = YamlFile::from_str("value: 0B1111").unwrap();
5088 assert_eq!(yaml.to_string().trim(), "value: 0B1111");
5089
5090 let yaml = YamlFile::from_str("value: 0o755").unwrap();
5092 assert_eq!(yaml.to_string().trim(), "value: 0o755");
5093
5094 let yaml = YamlFile::from_str("value: 0O644").unwrap();
5095 assert_eq!(yaml.to_string().trim(), "value: 0O644");
5096
5097 let yaml = YamlFile::from_str("value: -0b1010").unwrap();
5099 assert_eq!(yaml.to_string().trim(), "value: -0b1010");
5100
5101 let yaml = YamlFile::from_str("value: +0o755").unwrap();
5102 assert_eq!(yaml.to_string().trim(), "value: +0o755");
5103
5104 let yaml = YamlFile::from_str("value: 0755").unwrap();
5106 assert_eq!(yaml.to_string().trim(), "value: 0755");
5107
5108 let yaml = YamlFile::from_str("value: 0xFF").unwrap();
5109 assert_eq!(yaml.to_string().trim(), "value: 0xFF");
5110 }
5111
5112 #[test]
5113 fn test_invalid_number_formats_as_strings() {
5114 let yaml = YamlFile::from_str("value: 0b2").unwrap();
5116 assert_eq!(yaml.to_string().trim(), "value: 0b2");
5117
5118 let yaml = YamlFile::from_str("value: 0o9").unwrap();
5119 assert_eq!(yaml.to_string().trim(), "value: 0o9");
5120
5121 let yaml = YamlFile::from_str("value: 0xGH").unwrap();
5122 assert_eq!(yaml.to_string().trim(), "value: 0xGH");
5123 }
5124
5125 #[test]
5126 fn test_number_formats_in_complex_structures() {
5127 let input = r#"
5128config:
5129 permissions: 0o755
5130 flags: 0b11010
5131 color: 0xFF00FF
5132 count: 42"#;
5133
5134 let yaml = YamlFile::from_str(input).unwrap();
5135
5136 let doc = yaml.document().expect("Should have document");
5137 let mapping = doc.as_mapping().expect("Should be a mapping");
5138 let config_value = mapping.get("config").unwrap();
5139 let config = config_value.as_mapping().unwrap();
5140
5141 assert_eq!(
5142 config
5143 .get("permissions")
5144 .unwrap()
5145 .as_scalar()
5146 .unwrap()
5147 .as_string(),
5148 "0o755"
5149 );
5150 assert_eq!(
5151 config
5152 .get("flags")
5153 .unwrap()
5154 .as_scalar()
5155 .unwrap()
5156 .as_string(),
5157 "0b11010"
5158 );
5159 assert_eq!(
5160 config
5161 .get("color")
5162 .unwrap()
5163 .as_scalar()
5164 .unwrap()
5165 .as_string(),
5166 "0xFF00FF"
5167 );
5168 assert_eq!(
5169 config
5170 .get("count")
5171 .unwrap()
5172 .as_scalar()
5173 .unwrap()
5174 .as_string(),
5175 "42"
5176 );
5177
5178 let output = yaml.to_string();
5179 assert_eq!(output, input);
5180 }
5181
5182 #[test]
5183 fn test_editing_operations() {
5184 let yaml = YamlFile::from_str("name: old-name\nversion: 1.0.0").unwrap();
5186 if let Some(doc) = yaml.document() {
5187 doc.set("name", "new-name");
5188 doc.set("version", "2.0.0");
5189
5190 assert_eq!(doc.get_string("name"), Some("new-name".to_string()));
5192 assert_eq!(doc.get_string("version"), Some("2.0.0".to_string()));
5193
5194 let output = doc.to_string();
5196 assert_eq!(output, "name: new-name\nversion: 2.0.0");
5197 }
5198 }
5199
5200 #[test]
5201 fn test_timestamp_parsing_and_validation() {
5202 use crate::scalar::{ScalarType, ScalarValue};
5203
5204 let test_cases = vec![
5206 ("2001-12-14 21:59:43.10 -5", true), ("2001-12-15T02:59:43.1Z", true), ("2002-12-14", true), ("2001-12-14t21:59:43.10-05:00", true), ("2001-12-14 21:59:43.10", true), ("2001-12-14T21:59:43", true), ("not-a-timestamp", false), ("2001-13-14", false), ("2001-12-32", false), ];
5216
5217 for (timestamp_str, should_be_valid) in test_cases {
5218 let scalar = ScalarValue::parse(timestamp_str);
5219
5220 if should_be_valid {
5221 assert_eq!(
5222 scalar.scalar_type(),
5223 ScalarType::Timestamp,
5224 "Failed to recognize '{}' as timestamp",
5225 timestamp_str
5226 );
5227 assert!(scalar.is_timestamp());
5228
5229 assert_eq!(scalar.value(), timestamp_str);
5231
5232 let yaml = format!("timestamp: {}", timestamp_str);
5234 let parsed = YamlFile::from_str(&yaml).unwrap();
5235
5236 let doc = parsed.document().expect("Should have document");
5237 let mapping = doc.as_mapping().expect("Should be a mapping");
5238 assert_eq!(
5239 mapping
5240 .get("timestamp")
5241 .unwrap()
5242 .as_scalar()
5243 .unwrap()
5244 .as_string(),
5245 timestamp_str,
5246 "Timestamp '{}' not preserved",
5247 timestamp_str
5248 );
5249
5250 let output = parsed.to_string();
5251 assert_eq!(output, yaml);
5252 } else {
5253 assert_ne!(
5254 scalar.scalar_type(),
5255 ScalarType::Timestamp,
5256 "'{}' should not be recognized as timestamp",
5257 timestamp_str
5258 );
5259 }
5260 }
5261
5262 let yaml_with_timestamps = r#"
5264created_at: 2001-12-14 21:59:43.10 -5
5265updated_at: 2001-12-15T02:59:43.1Z
5266date_only: 2002-12-14
5267timestamps_in_array:
5268 - 2001-12-14 21:59:43.10 -5
5269 - 2001-12-15T02:59:43.1Z
5270 - 2002-12-14"#;
5271
5272 let parsed = YamlFile::from_str(yaml_with_timestamps).unwrap();
5273
5274 let doc = parsed.document().expect("Should have document");
5275 let mapping = doc.as_mapping().expect("Should be a mapping");
5276 assert_eq!(
5277 mapping
5278 .get("created_at")
5279 .unwrap()
5280 .as_scalar()
5281 .unwrap()
5282 .as_string(),
5283 "2001-12-14 21:59:43.10 -5"
5284 );
5285 assert_eq!(
5286 mapping
5287 .get("updated_at")
5288 .unwrap()
5289 .as_scalar()
5290 .unwrap()
5291 .as_string(),
5292 "2001-12-15T02:59:43.1Z"
5293 );
5294 assert_eq!(
5295 mapping
5296 .get("date_only")
5297 .unwrap()
5298 .as_scalar()
5299 .unwrap()
5300 .as_string(),
5301 "2002-12-14"
5302 );
5303
5304 let array_value = mapping.get("timestamps_in_array").unwrap();
5305 let array = array_value.as_sequence().unwrap();
5306 assert_eq!(
5307 array.get(0).unwrap().as_scalar().unwrap().as_string(),
5308 "2001-12-14 21:59:43.10 -5"
5309 );
5310 assert_eq!(
5311 array.get(1).unwrap().as_scalar().unwrap().as_string(),
5312 "2001-12-15T02:59:43.1Z"
5313 );
5314 assert_eq!(
5315 array.get(2).unwrap().as_scalar().unwrap().as_string(),
5316 "2002-12-14"
5317 );
5318
5319 let output = parsed.to_string();
5320 assert_eq!(output, yaml_with_timestamps);
5321 }
5322
5323 #[test]
5324 fn test_regex_support_in_yaml() {
5325 use crate::scalar::{ScalarType, ScalarValue};
5326
5327 let yaml_with_regex = r#"
5329patterns:
5330 digits: !!regex '\d+'
5331 word: !!regex '\w+'
5332 simple: !!regex 'test'"#;
5333
5334 let parsed = YamlFile::from_str(yaml_with_regex).unwrap();
5335
5336 let output = parsed.to_string();
5338 assert_eq!(output, yaml_with_regex);
5339
5340 let regex_scalar = ScalarValue::regex(r"^\d{4}-\d{2}-\d{2}$");
5342 assert_eq!(regex_scalar.scalar_type(), ScalarType::Regex);
5343 assert!(regex_scalar.is_regex());
5344 assert_eq!(regex_scalar.value(), r"^\d{4}-\d{2}-\d{2}$");
5345 assert_eq!(
5346 regex_scalar.to_yaml_string(),
5347 r"!!regex ^\d{4}-\d{2}-\d{2}$"
5348 );
5349
5350 let yaml_simple = "pattern: !!regex '\\d+'";
5352 let parsed_simple = YamlFile::from_str(yaml_simple).unwrap();
5353
5354 let doc_simple = parsed_simple.document().expect("Should have document");
5355 let mapping_simple = doc_simple.as_mapping().expect("Should be a mapping");
5356 let pattern_value = mapping_simple
5357 .get("pattern")
5358 .expect("Should have pattern key");
5359
5360 assert!(pattern_value.is_tagged(), "pattern should be a tagged node");
5362 let tagged = pattern_value.as_tagged().expect("Should be tagged");
5363 assert_eq!(tagged.tag(), Some("!!regex".to_string()));
5364 assert_eq!(tagged.as_string(), Some("\\d+".to_string()));
5365
5366 let output_simple = parsed_simple.to_string();
5367 assert_eq!(output_simple, yaml_simple);
5368
5369 let complex_regex = r#"validation: !!regex '^https?://(?:[-\w.])+(?:\:[0-9]+)?'"#;
5371 let parsed_complex = YamlFile::from_str(complex_regex).unwrap();
5372
5373 let doc_complex = parsed_complex.document().expect("Should have document");
5374 let mapping_complex = doc_complex.as_mapping().expect("Should be a mapping");
5375 let validation_value = mapping_complex
5376 .get("validation")
5377 .expect("Should have validation key");
5378
5379 assert!(
5380 validation_value.is_tagged(),
5381 "validation should be a tagged node"
5382 );
5383 let tagged_complex = validation_value.as_tagged().expect("Should be tagged");
5384 assert_eq!(tagged_complex.tag(), Some("!!regex".to_string()));
5385 assert_eq!(
5386 tagged_complex.as_string(),
5387 Some("^https?://(?:[-\\w.])+(?:\\:[0-9]+)?".to_string())
5388 );
5389
5390 let output_complex = parsed_complex.to_string();
5391 assert_eq!(output_complex, complex_regex);
5392 }
5393
5394 #[test]
5395 fn test_regex_in_different_contexts() {
5396 let yaml_sequence = r#"
5398patterns:
5399 - !!regex '\d+'
5400 - !!regex '[a-z]+'
5401 - normal_string
5402 - !!regex '.*@.*\..*'
5403"#;
5404
5405 let parsed_seq = YamlFile::from_str(yaml_sequence).unwrap();
5406
5407 let doc_seq = parsed_seq.document().expect("Should have document");
5408 let mapping_seq = doc_seq.as_mapping().expect("Should be a mapping");
5409 let patterns_value = mapping_seq
5410 .get("patterns")
5411 .expect("Should have patterns key");
5412 let patterns = patterns_value.as_sequence().expect("Should be a sequence");
5413
5414 assert!(patterns.get(0).unwrap().is_tagged());
5416 assert_eq!(
5417 patterns.get(0).unwrap().as_tagged().unwrap().tag(),
5418 Some("!!regex".to_string())
5419 );
5420
5421 assert!(patterns.get(1).unwrap().is_tagged());
5423 assert_eq!(
5424 patterns.get(1).unwrap().as_tagged().unwrap().tag(),
5425 Some("!!regex".to_string())
5426 );
5427
5428 assert!(!patterns.get(2).unwrap().is_tagged());
5430 assert_eq!(
5431 patterns.get(2).unwrap().as_scalar().unwrap().as_string(),
5432 "normal_string"
5433 );
5434
5435 assert!(patterns.get(3).unwrap().is_tagged());
5437 assert_eq!(
5438 patterns.get(3).unwrap().as_tagged().unwrap().tag(),
5439 Some("!!regex".to_string())
5440 );
5441
5442 let output_seq = parsed_seq.to_string();
5443 assert_eq!(output_seq, yaml_sequence);
5444
5445 let yaml_nested = r#"
5447validation:
5448 email: !!regex '[^@]+@[^@]+\.[a-z]+'
5449 phone: !!regex '\d{3}-\d{3}-\d{4}'
5450 config:
5451 debug_pattern: !!regex 'DEBUG:.*'
5452 nested:
5453 deep_pattern: !!regex 'ERROR'
5454"#;
5455
5456 let parsed_nested = YamlFile::from_str(yaml_nested).unwrap();
5457 let output_nested = parsed_nested.to_string();
5459 assert_eq!(output_nested, yaml_nested);
5460
5461 let yaml_mixed = r#"
5463mixed_collection:
5464 - name: "test"
5465 patterns: [!!regex '\d+', !!regex '\w+']
5466 - patterns:
5467 simple: !!regex 'test'
5468 complex: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5469"#;
5470
5471 let parsed_mixed = YamlFile::from_str(yaml_mixed).unwrap();
5472 let output_mixed = parsed_mixed.to_string();
5474 assert_eq!(output_mixed, yaml_mixed);
5475
5476 let yaml_flow =
5478 r#"inline_patterns: {email: !!regex '[^@]+@[^@]+', phone: !!regex '\d{3}-\d{4}'}"#;
5479
5480 let parsed_flow = YamlFile::from_str(yaml_flow).unwrap();
5481 let output_flow = parsed_flow.to_string();
5483 assert_eq!(output_flow, yaml_flow);
5484 }
5485
5486 #[test]
5487 fn test_regex_parsing_edge_cases() {
5488 let yaml_quotes = r#"
5491patterns:
5492 single_quoted: !!regex 'pattern with spaces'
5493 double_quoted: !!regex "pattern_without_escapes"
5494 unquoted: !!regex simple_pattern
5495"#;
5496
5497 let parsed_quotes = YamlFile::from_str(yaml_quotes).unwrap();
5498
5499 let output_quotes = parsed_quotes.to_string();
5500 assert_eq!(output_quotes, yaml_quotes);
5501
5502 let yaml_empty = r#"
5504empty: !!regex ''
5505whitespace: !!regex ' '
5506tabs: !!regex ' '
5507"#;
5508
5509 let parsed_empty =
5510 YamlFile::from_str(yaml_empty).expect("Should parse empty/whitespace regex patterns");
5511
5512 let output_empty = parsed_empty.to_string();
5513 assert_eq!(output_empty, yaml_empty);
5514
5515 let yaml_special = r#"special: !!regex 'pattern_with_underscores_and_123'"#;
5517
5518 let parsed_special = YamlFile::from_str(yaml_special)
5519 .expect("Should parse regex with safe special characters");
5520
5521 let output_special = parsed_special.to_string();
5522 assert_eq!(output_special, yaml_special);
5523
5524 let yaml_verify = r#"test_pattern: !!regex '\d{4}-\d{2}-\d{2}'"#;
5526 let parsed_verify = YamlFile::from_str(yaml_verify).unwrap();
5527
5528 let output_verify = parsed_verify.to_string();
5529 assert_eq!(output_verify, yaml_verify);
5530
5531 let yaml_multiple = r#"
5533patterns:
5534 email: !!regex '^[^\s@]+@[^\s@]+\.[^\s@]+$'
5535 phone: !!regex '^\+?[\d\s\-\(\)]{10,}$'
5536 url: !!regex '^https?://[^\s]+$'
5537 ipv4: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5538 uuid: !!regex '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
5539"#;
5540
5541 let parsed_multiple =
5542 YamlFile::from_str(yaml_multiple).expect("Should parse multiple regex patterns");
5543
5544 let output_multiple = parsed_multiple.to_string();
5546 assert_eq!(output_multiple, yaml_multiple);
5547 }
5548
5549 #[test]
5550 fn test_enhanced_comment_support() {
5551 let yaml1 = r#"flow_seq: [
5556 item1, # comment after item1
5557 item2, # comment after item2
5558 item3 # comment after item3
5559]"#;
5560 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5561 let output1 = parsed1.to_string();
5562
5563 assert_eq!(output1, yaml1);
5564
5565 let yaml2 = r#"flow_map: {
5567 key1: val1, # comment after first pair
5568 key2: val2, # comment after second pair
5569 key3: val3 # comment after third pair
5570}"#;
5571 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5572 let output2 = parsed2.to_string();
5573
5574 assert_eq!(output2, yaml2);
5575
5576 let yaml3 = r#"config:
5578 servers: [
5579 {name: web1, port: 80}, # Web server 1
5580 {name: web2, port: 80}, # Web server 2
5581 {name: db1, port: 5432} # Database server
5582 ] # End servers array"#;
5583 let parsed3 = YamlFile::from_str(yaml3).unwrap();
5584 let output3 = parsed3.to_string();
5585
5586 assert_eq!(output3, yaml3);
5587
5588 let yaml4 = r#"items:
5590 - first # First item comment
5591 - second # Second item comment
5592 # Comment between items
5593 - third # Third item comment"#;
5594 let parsed4 = YamlFile::from_str(yaml4).unwrap();
5595 let output4 = parsed4.to_string();
5596
5597 assert_eq!(output4, yaml4);
5598
5599 for yaml in [yaml1, yaml2, yaml3, yaml4] {
5601 let parsed = YamlFile::from_str(yaml).unwrap();
5602 let output = parsed.to_string();
5603 let reparsed = YamlFile::from_str(&output);
5604 assert!(reparsed.is_ok(), "Round-trip parsing should succeed");
5605 }
5606 }
5607
5608 #[test]
5609 fn test_insert_after_with_sequence() {
5610 let yaml = "name: project\nversion: 1.0.0";
5611 let parsed = YamlFile::from_str(yaml).unwrap();
5612 let doc = parsed.document().expect("Should have a document");
5613
5614 let features = SequenceBuilder::new()
5616 .item("feature1")
5617 .item("feature2")
5618 .item("feature3")
5619 .build_document()
5620 .as_sequence()
5621 .unwrap();
5622 let success = doc.insert_after("name", "features", features);
5623 assert!(success, "insert_after should succeed");
5624
5625 let output = doc.to_string();
5626
5627 let expected = r#"name: project
5629features:
5630 - feature1
5631 - feature2
5632 - feature3
5633version: 1.0.0"#;
5634 assert_eq!(output.trim(), expected);
5635
5636 let reparsed = YamlFile::from_str(&output);
5637 assert!(reparsed.is_ok(), "Output should be valid YAML");
5638 }
5639
5640 #[test]
5641 fn test_insert_before_with_mapping() {
5642 let yaml = "name: project\nversion: 1.0.0";
5643 let parsed = YamlFile::from_str(yaml).unwrap();
5644 let doc = parsed.document().expect("Should have a document");
5645
5646 let database = MappingBuilder::new()
5648 .pair("host", "localhost")
5649 .pair("port", 5432)
5650 .pair("database", "mydb")
5651 .build_document()
5652 .as_mapping()
5653 .unwrap();
5654 let success = doc.insert_before("version", "database", database);
5655 assert!(success, "insert_before should succeed");
5656
5657 let output = doc.to_string();
5658
5659 let expected = r#"name: project
5661database:
5662 host: localhost
5663 port: 5432
5664 database: mydb
5665version: 1.0.0"#;
5666 assert_eq!(output.trim(), expected);
5667
5668 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5670 let reparsed_doc = reparsed.document().expect("Should have document");
5671 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5672
5673 let db_value = reparsed_mapping
5674 .get("database")
5675 .expect("Should have database key");
5676 let db_mapping = db_value.as_mapping().expect("database should be mapping");
5677 assert_eq!(
5678 db_mapping
5679 .get("host")
5680 .unwrap()
5681 .as_scalar()
5682 .unwrap()
5683 .as_string(),
5684 "localhost"
5685 );
5686 assert_eq!(
5687 db_mapping
5688 .get("port")
5689 .unwrap()
5690 .as_scalar()
5691 .unwrap()
5692 .as_string(),
5693 "5432"
5694 );
5695 }
5696
5697 #[test]
5698 fn test_insert_at_index_with_mixed_types() {
5699 let yaml = "name: project";
5700 let parsed = YamlFile::from_str(yaml).unwrap();
5701 let doc = parsed.document().expect("Should have a document");
5702
5703 doc.insert_at_index(1, "version", "1.0.0");
5705 doc.insert_at_index(2, "active", true);
5706 doc.insert_at_index(3, "count", 42);
5707
5708 let features = SequenceBuilder::new()
5709 .item("auth")
5710 .item("logging")
5711 .build_document()
5712 .as_sequence()
5713 .unwrap();
5714 doc.insert_at_index(4, "features", features);
5715
5716 let output = doc.to_string();
5717
5718 let expected = r#"name: project
5719version: 1.0.0
5720active: true
5721count: 42
5722features:
5723- auth
5724- logging"#;
5725 assert_eq!(output.trim(), expected);
5726
5727 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5728 let reparsed_doc = reparsed.document().expect("Should have document");
5729 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5730
5731 assert_eq!(
5732 reparsed_mapping
5733 .get("version")
5734 .unwrap()
5735 .as_scalar()
5736 .unwrap()
5737 .as_string(),
5738 "1.0.0"
5739 );
5740 assert_eq!(
5741 reparsed_mapping.get("active").unwrap().to_bool(),
5742 Some(true)
5743 );
5744 assert_eq!(reparsed_mapping.get("count").unwrap().to_i64(), Some(42));
5745
5746 let features_value = reparsed_mapping.get("features").unwrap();
5747 let features = features_value.as_sequence().unwrap();
5748 assert_eq!(
5749 features.get(0).unwrap().as_scalar().unwrap().as_string(),
5750 "auth"
5751 );
5752 assert_eq!(
5753 features.get(1).unwrap().as_scalar().unwrap().as_string(),
5754 "logging"
5755 );
5756 }
5757
5758 #[test]
5759 fn test_insert_with_null_and_special_scalars() {
5760 let yaml = "name: project";
5761 let parsed = YamlFile::from_str(yaml).unwrap();
5762 let doc = parsed.document().expect("Should have a document");
5763
5764 doc.insert_after("name", "null_value", ScalarValue::null());
5766 doc.insert_after("null_value", "empty_string", "");
5767 doc.insert_after("empty_string", "number", 1.234);
5768 doc.insert_after("number", "boolean", false);
5769
5770 let output = doc.to_string();
5771
5772 let expected = r#"name: project
5773null_value: null
5774empty_string: ''
5775number: 1.234
5776boolean: false"#;
5777 assert_eq!(output.trim(), expected);
5778
5779 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5780 let reparsed_doc = reparsed.document().expect("Should have document");
5781 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5782
5783 assert_eq!(
5784 reparsed_mapping
5785 .get("name")
5786 .unwrap()
5787 .as_scalar()
5788 .unwrap()
5789 .as_string(),
5790 "project"
5791 );
5792 assert!(reparsed_mapping
5793 .get("null_value")
5794 .unwrap()
5795 .as_scalar()
5796 .unwrap()
5797 .is_null());
5798 assert_eq!(
5799 reparsed_mapping
5800 .get("empty_string")
5801 .unwrap()
5802 .as_scalar()
5803 .unwrap()
5804 .as_string(),
5805 ""
5806 );
5807 assert_eq!(
5808 reparsed_mapping.get("number").unwrap().to_f64(),
5809 Some(1.234)
5810 );
5811 assert_eq!(
5812 reparsed_mapping.get("boolean").unwrap().to_bool(),
5813 Some(false)
5814 );
5815 }
5816
5817 #[test]
5818 fn test_insert_ordering_preservation() {
5819 let yaml = "first: 1\nthird: 3\nfifth: 5";
5820 let parsed = YamlFile::from_str(yaml).unwrap();
5821 let doc = parsed.document().expect("Should have a document");
5822
5823 doc.insert_after("first", "second", 2);
5825 doc.insert_before("fifth", "fourth", 4);
5826
5827 let output = doc.to_string();
5828
5829 let expected = r#"first: 1
5831second: 2
5832third: 3
5833fourth: 4
5834fifth: 5"#;
5835 assert_eq!(output.trim(), expected);
5836
5837 let reparsed = YamlFile::from_str(&output);
5838 assert!(reparsed.is_ok(), "Output should be valid YAML");
5839 }
5840
5841 #[test]
5842 fn test_insert_with_yamlvalue_positioning() {
5843 let yaml = "name: project\nversion: 1.0\nactive: true";
5844 let parsed = YamlFile::from_str(yaml).unwrap();
5845 let doc = parsed.document().expect("Should have a document");
5846
5847 let success = doc.insert_after("name", "description", "A sample project");
5851 assert!(success, "Should find string key");
5852
5853 let success = doc.insert_after(1.0, "build", "gradle");
5855 assert!(
5856 !success,
5857 "Should not find numeric key (1.0) when actual key is string 'version'"
5858 );
5859
5860 let success = doc.insert_after(true, "test", "enabled");
5862 assert!(
5863 !success,
5864 "Should not find boolean key (true) when actual key is string 'active'"
5865 );
5866
5867 let bool_string_key = "true";
5869 let success = doc.insert_after(bool_string_key, "test_mode", "development");
5870 assert!(!success, "Should not find 'true' key when value is true");
5871
5872 let output = doc.to_string();
5873
5874 let expected = "name: project\ndescription: A sample project\nversion: 1.0\nactive: true";
5876 assert_eq!(output, expected);
5877 }
5878
5879 #[test]
5880 fn test_insert_complex_nested_structure() {
5881 let yaml = "name: project";
5882 let parsed = YamlFile::from_str(yaml).unwrap();
5883 let doc = parsed.document().expect("Should have a document");
5884
5885 let config = MappingBuilder::new()
5887 .mapping("server", |m| m.pair("host", "0.0.0.0").pair("port", 8080))
5888 .pair("debug", true)
5889 .sequence("features", |s| s.item("api").item("web").item("cli"))
5890 .build_document()
5891 .as_mapping()
5892 .unwrap();
5893
5894 doc.insert_after("name", "config", config);
5895
5896 let output = doc.to_string();
5897
5898 let expected = "name: project\nconfig:\n server: \n host: 0.0.0.0\n port: 8080\n debug: true\n features: \n - api\n - web\n - cli\n";
5900 assert_eq!(output, expected);
5901
5902 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5903 let reparsed_doc = reparsed.document().expect("Should have document");
5904 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5905
5906 let config_value = reparsed_mapping
5907 .get("config")
5908 .expect("Should have config key");
5909 let config_mapping = config_value.as_mapping().expect("config should be mapping");
5910
5911 let server_value = config_mapping
5912 .get("server")
5913 .expect("Should have server key");
5914 let server = server_value.as_mapping().expect("server should be mapping");
5915 assert_eq!(
5916 server.get("host").unwrap().as_scalar().unwrap().as_string(),
5917 "0.0.0.0"
5918 );
5919 assert_eq!(server.get("port").unwrap().to_i64(), Some(8080));
5920
5921 assert_eq!(config_mapping.get("debug").unwrap().to_bool(), Some(true));
5922
5923 let features_value = config_mapping
5924 .get("features")
5925 .expect("Should have features key");
5926 let features = features_value
5927 .as_sequence()
5928 .expect("features should be sequence");
5929 assert_eq!(features.len(), 3);
5930 assert_eq!(
5931 features.get(0).unwrap().as_scalar().unwrap().as_string(),
5932 "api"
5933 );
5934 assert_eq!(
5935 features.get(1).unwrap().as_scalar().unwrap().as_string(),
5936 "web"
5937 );
5938 assert_eq!(
5939 features.get(2).unwrap().as_scalar().unwrap().as_string(),
5940 "cli"
5941 );
5942 }
5943
5944 #[test]
5945 fn test_insert_with_yaml_sets() {
5946 let yaml = "name: project";
5947 let parsed = YamlFile::from_str(yaml).unwrap();
5948 let doc = parsed.document().expect("Should have a document");
5949
5950 let mut tags = std::collections::BTreeSet::new();
5952 tags.insert("production".to_string());
5953 tags.insert("database".to_string());
5954 tags.insert("web".to_string());
5955
5956 let yaml_set = YamlValue::from_set(tags);
5957 doc.insert_after("name", "tags", yaml_set);
5958
5959 let output = doc.to_string();
5960
5961 let expected =
5963 "name: project\ntags: !!set\n database: null\n production: null\n web: null\n";
5964 assert_eq!(output, expected);
5965
5966 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5967 let reparsed_doc = reparsed.document().expect("Should have document");
5968 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5969
5970 let tags_value = reparsed_mapping.get("tags").expect("Should have tags key");
5971 assert!(tags_value.is_tagged(), "tags should be tagged");
5972 let tagged = tags_value.as_tagged().expect("Should be tagged node");
5973 assert_eq!(tagged.tag(), Some("!!set".to_string()));
5974
5975 let tags_syntax = tagged.syntax();
5978 let tags_mapping = tags_syntax
5979 .children()
5980 .find_map(Mapping::cast)
5981 .expect("Set should have mapping child");
5982
5983 assert!(tags_mapping
5984 .get("database")
5985 .unwrap()
5986 .as_scalar()
5987 .unwrap()
5988 .is_null());
5989 assert!(tags_mapping
5990 .get("production")
5991 .unwrap()
5992 .as_scalar()
5993 .unwrap()
5994 .is_null());
5995 assert!(tags_mapping
5996 .get("web")
5997 .unwrap()
5998 .as_scalar()
5999 .unwrap()
6000 .is_null());
6001 }
6002
6003 #[test]
6004 fn test_insert_with_ordered_mappings() {
6005 let yaml = "name: project";
6006 let parsed = YamlFile::from_str(yaml).unwrap();
6007 let doc = parsed.document().expect("Should have a document");
6008
6009 let ordered_steps = vec![
6011 ("compile".to_string(), YamlValue::from("gcc main.c")),
6012 ("test".to_string(), YamlValue::from("./a.out test")),
6013 (
6014 "package".to_string(),
6015 YamlValue::from("tar -czf app.tar.gz ."),
6016 ),
6017 ];
6018
6019 let yaml_omap = YamlValue::from_ordered_mapping(ordered_steps);
6020 doc.insert_after("name", "build_steps", yaml_omap);
6021
6022 let output = doc.to_string();
6023
6024 let expected = "name: project\nbuild_steps: !!omap\n - compile: gcc main.c\n - test: ./a.out test\n - package: tar -czf app.tar.gz .\n";
6026 assert_eq!(output, expected);
6027
6028 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6029 let reparsed_doc = reparsed.document().expect("Should have document");
6030 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6031
6032 let steps_value = reparsed_mapping
6033 .get("build_steps")
6034 .expect("Should have build_steps key");
6035 assert!(steps_value.is_tagged(), "build_steps should be tagged");
6036 let tagged = steps_value.as_tagged().expect("Should be tagged node");
6037 assert_eq!(tagged.tag(), Some("!!omap".to_string()));
6038
6039 let steps_syntax = tagged.syntax();
6041 let steps_seq = steps_syntax
6042 .children()
6043 .find_map(Sequence::cast)
6044 .expect("Omap should have sequence child");
6045
6046 assert_eq!(steps_seq.len(), 3);
6047 let compile_value = steps_seq.get(0).unwrap();
6049 let compile_item = compile_value.as_mapping().expect("Should be mapping");
6050 assert_eq!(
6051 compile_item
6052 .get("compile")
6053 .unwrap()
6054 .as_scalar()
6055 .unwrap()
6056 .as_string(),
6057 "gcc main.c"
6058 );
6059
6060 let test_value = steps_seq.get(1).unwrap();
6061 let test_item = test_value.as_mapping().expect("Should be mapping");
6062 assert_eq!(
6063 test_item
6064 .get("test")
6065 .unwrap()
6066 .as_scalar()
6067 .unwrap()
6068 .as_string(),
6069 "./a.out test"
6070 );
6071
6072 let package_value = steps_seq.get(2).unwrap();
6073 let package_item = package_value.as_mapping().expect("Should be mapping");
6074 assert_eq!(
6075 package_item
6076 .get("package")
6077 .unwrap()
6078 .as_scalar()
6079 .unwrap()
6080 .as_string(),
6081 "tar -czf app.tar.gz ."
6082 );
6083 }
6084
6085 #[test]
6086 fn test_insert_with_pairs() {
6087 let yaml = "name: project";
6088 let parsed = YamlFile::from_str(yaml).unwrap();
6089 let doc = parsed.document().expect("Should have a document");
6090
6091 let connection_attempts = vec![
6093 ("server".to_string(), YamlValue::from("primary.db")),
6094 ("server".to_string(), YamlValue::from("secondary.db")), ("server".to_string(), YamlValue::from("tertiary.db")), ("timeout".to_string(), YamlValue::from(30)),
6097 ];
6098
6099 let yaml_pairs = YamlValue::from_pairs(connection_attempts);
6100 doc.insert_after("name", "connections", yaml_pairs);
6101
6102 let output = doc.to_string();
6103
6104 let expected = "name: project\nconnections: !!pairs\n - server: primary.db\n - server: secondary.db\n - server: tertiary.db\n - timeout: 30\n";
6106 assert_eq!(output, expected);
6107
6108 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6109 let reparsed_doc = reparsed.document().expect("Should have document");
6110 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6111
6112 let connections_value = reparsed_mapping
6113 .get("connections")
6114 .expect("Should have connections key");
6115 assert!(
6116 connections_value.is_tagged(),
6117 "connections should be tagged"
6118 );
6119 let tagged = connections_value
6120 .as_tagged()
6121 .expect("Should be tagged node");
6122 assert_eq!(tagged.tag(), Some("!!pairs".to_string()));
6123
6124 let connections_syntax = tagged.syntax();
6126 let connections_seq = connections_syntax
6127 .children()
6128 .find_map(Sequence::cast)
6129 .expect("Pairs should have sequence child");
6130
6131 assert_eq!(connections_seq.len(), 4);
6132
6133 let server1_val = connections_seq.get(0).unwrap();
6134 let server1 = server1_val.as_mapping().expect("Should be mapping");
6135 assert_eq!(
6136 server1
6137 .get("server")
6138 .unwrap()
6139 .as_scalar()
6140 .unwrap()
6141 .as_string(),
6142 "primary.db"
6143 );
6144
6145 let server2_val = connections_seq.get(1).unwrap();
6146 let server2 = server2_val.as_mapping().expect("Should be mapping");
6147 assert_eq!(
6148 server2
6149 .get("server")
6150 .unwrap()
6151 .as_scalar()
6152 .unwrap()
6153 .as_string(),
6154 "secondary.db"
6155 );
6156
6157 let server3_val = connections_seq.get(2).unwrap();
6158 let server3 = server3_val.as_mapping().expect("Should be mapping");
6159 assert_eq!(
6160 server3
6161 .get("server")
6162 .unwrap()
6163 .as_scalar()
6164 .unwrap()
6165 .as_string(),
6166 "tertiary.db"
6167 );
6168
6169 let timeout_val = connections_seq.get(3).unwrap();
6170 let timeout = timeout_val.as_mapping().expect("Should be mapping");
6171 assert_eq!(timeout.get("timeout").unwrap().to_i64(), Some(30));
6172 }
6173
6174 #[test]
6175 fn test_insert_with_empty_collections() {
6176 let yaml1 = "name: project";
6180 let parsed1 = YamlFile::from_str(yaml1).unwrap();
6181 let doc1 = parsed1.document().expect("Should have a document");
6182
6183 let empty_seq_yaml = YamlFile::from_str("[]").unwrap();
6185 let empty_list = empty_seq_yaml.document().unwrap().as_sequence().unwrap();
6186
6187 doc1.insert_after("name", "empty_list", empty_list);
6188 let output1 = doc1.to_string();
6189 assert_eq!(output1, "name: project\nempty_list: []\n");
6190
6191 let yaml2 = "name: project";
6193 let parsed2 = YamlFile::from_str(yaml2).unwrap();
6194 let doc2 = parsed2.document().expect("Should have a document");
6195
6196 let empty_map_yaml = YamlFile::from_str("{}").unwrap();
6198 let empty_map = empty_map_yaml.document().unwrap().as_mapping().unwrap();
6199
6200 doc2.insert_after("name", "empty_map", empty_map);
6201 let output2 = doc2.to_string();
6202 assert_eq!(output2, "name: project\nempty_map: {}\n");
6203
6204 let yaml3 = "name: project";
6206 let parsed3 = YamlFile::from_str(yaml3).unwrap();
6207 let doc3 = parsed3.document().expect("Should have a document");
6208 doc3.insert_after("name", "empty_set", YamlValue::set());
6209 let output3 = doc3.to_string();
6210 assert_eq!(output3, "name: project\nempty_set: !!set {}\n");
6211
6212 assert!(
6214 YamlFile::from_str(&output1).is_ok(),
6215 "Empty sequence output should be valid YAML"
6216 );
6217 assert!(
6218 YamlFile::from_str(&output2).is_ok(),
6219 "Empty mapping output should be valid YAML"
6220 );
6221 assert!(
6222 YamlFile::from_str(&output3).is_ok(),
6223 "Empty set output should be valid YAML"
6224 );
6225 }
6226
6227 #[test]
6228 fn test_insert_with_deeply_nested_sequences() {
6229 let yaml = "name: project";
6230 let parsed = YamlFile::from_str(yaml).unwrap();
6231 let doc = parsed.document().expect("Should have a document");
6232
6233 let nested_data = SequenceBuilder::new()
6235 .item("item1")
6236 .mapping(|m| {
6237 m.sequence("config", |s| s.item("debug").item(true).item(8080))
6238 .pair("name", "service")
6239 })
6240 .item(42)
6241 .build_document()
6242 .as_sequence()
6243 .unwrap();
6244
6245 doc.insert_after("name", "nested_data", nested_data);
6246
6247 let output = doc.to_string();
6248
6249 let expected = "name: project\nnested_data:\n - item1\n - \n config: \n - debug\n - true\n - 8080\n name: service- 42\n";
6250 assert_eq!(output, expected);
6251
6252 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6253 let reparsed_doc = reparsed.document().expect("Should have document");
6254 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6255
6256 let nested_value = reparsed_mapping
6257 .get("nested_data")
6258 .expect("Should have nested_data key");
6259 let nested_seq = nested_value.as_sequence().expect("Should be sequence");
6260 assert_eq!(nested_seq.len(), 2);
6263
6264 assert_eq!(
6266 nested_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6267 "item1"
6268 );
6269
6270 let second_item = nested_seq.get(1).unwrap();
6272 let second_mapping = second_item.as_mapping().expect("Should be mapping");
6273
6274 let config_value = second_mapping.get("config").expect("Should have config");
6275 let config_seq = config_value
6276 .as_sequence()
6277 .expect("config should be sequence");
6278 assert_eq!(config_seq.len(), 3);
6279 assert_eq!(
6280 config_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6281 "debug"
6282 );
6283 assert_eq!(config_seq.get(1).unwrap().to_bool(), Some(true));
6284 assert_eq!(config_seq.get(2).unwrap().to_i64(), Some(8080));
6285
6286 assert_eq!(
6288 second_mapping
6289 .get("name")
6290 .unwrap()
6291 .as_scalar()
6292 .unwrap()
6293 .as_string(),
6294 "service- 42"
6295 );
6296 }
6297
6298 #[test]
6299 fn test_ast_preservation_comments_in_mapping() {
6300 let yaml = r#"---
6302# Header comment
6303key1: value1 # Inline comment 1
6304# Middle comment
6305key2: value2 # Inline comment 2
6306# Footer comment
6307"#;
6308 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6309
6310 if let Some(mapping) = doc.as_mapping() {
6312 mapping.move_after("key1", "new_key", "new_value");
6313 }
6314
6315 let result = doc.to_string();
6316
6317 let expected = r#"---
6318# Header comment
6319key1: value1 # Inline comment 1
6320new_key: new_value
6321# Middle comment
6322key2: value2 # Inline comment 2
6323# Footer comment
6324"#;
6325 assert_eq!(result, expected);
6326 }
6327
6328 #[test]
6329 fn test_ast_preservation_whitespace_in_mapping() {
6330 let yaml = r#"---
6332key1: value1
6333
6334
6335key2: value2
6336"#;
6337 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6338
6339 if let Some(mapping) = doc.as_mapping() {
6341 mapping.move_after("key1", "new_key", "new_value");
6342 }
6343
6344 let result = doc.to_string();
6345
6346 let expected = r#"---
6347key1: value1
6348new_key: new_value
6349
6350
6351key2: value2
6352"#;
6353 assert_eq!(result, expected);
6354 }
6355
6356 #[test]
6357 fn test_ast_preservation_complex_structure() {
6358 let yaml = r#"---
6360# Configuration file
6361database: # Database settings
6362 host: localhost # Default host
6363 port: 5432 # Default port
6364 # Connection pool settings
6365 pool:
6366 min: 1 # Minimum connections
6367 max: 10 # Maximum connections
6368
6369# Server configuration
6370server:
6371 port: 8080 # HTTP port
6372"#;
6373 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6374
6375 eprintln!("===========\n");
6376
6377 if let Some(mapping) = doc.as_mapping() {
6379 mapping.move_after("database", "logging", "debug");
6380 }
6381
6382 let result = doc.to_string();
6383
6384 let expected = r#"---
6386# Configuration file
6387database: # Database settings
6388 host: localhost # Default host
6389 port: 5432 # Default port
6390 # Connection pool settings
6391 pool:
6392 min: 1 # Minimum connections
6393 max: 10 # Maximum connections
6394
6395logging: debug
6396# Server configuration
6397server:
6398 port: 8080 # HTTP port
6399"#;
6400 assert_eq!(result, expected);
6401 }
6402
6403 mod lookahead_tests {
6405 use super::*;
6406 use crate::lex::lex;
6407
6408 fn check_implicit_mapping(yaml: &str) -> bool {
6410 let tokens: Vec<(SyntaxKind, &str)> = lex(yaml);
6411 let kinds: Vec<SyntaxKind> = tokens.iter().rev().map(|(kind, _)| *kind).collect();
6413 has_implicit_mapping_pattern(kinds.into_iter())
6414 }
6415
6416 #[test]
6417 fn test_simple_implicit_mapping() {
6418 assert!(check_implicit_mapping("'key' : value"));
6421 }
6422
6423 #[test]
6424 fn test_simple_value_no_mapping() {
6425 assert!(!check_implicit_mapping("value ,"));
6427 }
6428
6429 #[test]
6430 fn test_value_with_comma() {
6431 assert!(!check_implicit_mapping("value , more"));
6433 }
6434
6435 #[test]
6436 fn test_nested_sequence_as_key() {
6437 assert!(check_implicit_mapping("[a, b]: value"));
6439 }
6440
6441 #[test]
6442 fn test_nested_mapping_not_implicit() {
6443 assert!(!check_implicit_mapping("{a: 1}, b"));
6445 }
6446
6447 #[test]
6448 fn test_deeply_nested_key() {
6449 assert!(check_implicit_mapping("[[a]]: value"));
6451 }
6452
6453 #[test]
6454 fn test_with_whitespace() {
6455 assert!(check_implicit_mapping("'key' : value"));
6457 }
6458
6459 #[test]
6460 fn test_mapping_value_in_sequence() {
6461 assert!(!check_implicit_mapping("a, {key: value}"));
6464 }
6465
6466 #[test]
6467 fn test_multiple_colons() {
6468 assert!(check_implicit_mapping("key1: value1, key2: value2"));
6470 }
6471
6472 #[test]
6473 fn test_url_not_mapping() {
6474 assert!(!check_implicit_mapping("http://example.com"));
6477 }
6478 }
6479
6480 #[test]
6481 fn test_from_str_relaxed_valid() {
6482 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: value\n");
6483 assert!(errors.is_empty());
6484 let doc = yaml_file.document().unwrap();
6485 let mapping = doc.as_mapping().unwrap();
6486 assert_eq!(
6487 mapping.get("key").unwrap().as_scalar().unwrap().to_string(),
6488 "value"
6489 );
6490 }
6491
6492 #[test]
6493 fn test_from_str_relaxed_unclosed_flow_sequence() {
6494 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: [a, b");
6495 assert!(!errors.is_empty());
6496 let doc = yaml_file.document().unwrap();
6498 assert!(doc.as_mapping().is_some());
6499 }
6500
6501 #[test]
6502 fn test_from_str_relaxed_unclosed_flow_mapping() {
6503 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: {a: 1");
6504 assert!(!errors.is_empty());
6505 let doc = yaml_file.document().unwrap();
6506 assert!(doc.as_mapping().is_some());
6507 }
6508
6509 #[test]
6510 fn test_from_str_relaxed_preserves_valid_content() {
6511 let input = "good: value\nbad: [unclosed\nalso_good: 42\n";
6512 let (yaml_file, errors) = YamlFile::from_str_relaxed(input);
6513 assert!(!errors.is_empty());
6514 let doc = yaml_file.document().unwrap();
6515 let mapping = doc.as_mapping().unwrap();
6516 assert_eq!(
6517 mapping
6518 .get("good")
6519 .unwrap()
6520 .as_scalar()
6521 .unwrap()
6522 .to_string(),
6523 "value"
6524 );
6525 }
6526
6527 #[test]
6528 fn test_from_str_relaxed_empty_input() {
6529 let (yaml_file, errors) = YamlFile::from_str_relaxed("");
6530 assert!(errors.is_empty());
6531 assert!(yaml_file.document().is_none());
6532 }
6533
6534 #[test]
6535 fn test_parse_flow_sequence_with_stray_close_brace_does_not_hang() {
6536 let parse = crate::Parse::parse_yaml("[}9[Z");
6539 let _ = parse.tree();
6540 assert!(parse.has_errors());
6541 }
6542
6543 #[test]
6544 fn test_parse_flow_mapping_with_stray_close_bracket_does_not_hang() {
6545 let parse = crate::Parse::parse_yaml("{]");
6547 let _ = parse.tree();
6548 assert!(parse.has_errors());
6549 }
6550
6551 #[test]
6552 fn test_parse_explicit_key_multiline_with_comment_does_not_hang() {
6553 let input = "?\u{18}\0\0\0\0\0\0\n\t#\t\t?\t";
6557 let parse = crate::Parse::parse_yaml(input);
6558 let _ = parse.tree();
6559 }
6560
6561 #[test]
6562 fn test_parse_deeply_nested_flow_mapping_does_not_blow_up() {
6563 let input = "{".repeat(2000);
6566 let parse = crate::Parse::parse_yaml(&input);
6567 let _ = parse.tree();
6568 assert!(parse.has_errors());
6569 }
6570
6571 #[test]
6572 fn test_parse_deeply_nested_flow_sequence_does_not_blow_up() {
6573 let input = "[".repeat(2000);
6574 let parse = crate::Parse::parse_yaml(&input);
6575 let _ = parse.tree();
6576 assert!(parse.has_errors());
6577 }
6578
6579 #[test]
6580 fn test_parse_explicit_key_followed_by_question_in_value_does_not_hang() {
6581 let input = "<<?: ?: \n<<\n ?: : 0\n:@";
6585 let parse = crate::Parse::parse_yaml(input);
6586 let _ = parse.tree();
6587 }
6588
6589 #[test]
6590 fn test_parse_complex_key_mapping_with_stray_close_bracket_does_not_hang() {
6591 let input = "[%:%:]: \n]: \n\n";
6594 let parse = crate::Parse::parse_yaml(input);
6595 let _ = parse.tree();
6596 }
6597
6598 #[test]
6599 fn test_parse_mapping_with_stray_close_brace_does_not_hang() {
6600 let input = "\0:\n\n}:\n\n{";
6605 let parse = crate::Parse::parse_yaml(input);
6606 let _ = parse.tree();
6607 assert!(parse.has_errors());
6608 }
6609}