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 !self
111 .0
112 .children_with_tokens()
113 .any(|c| matches!(c.kind(), SyntaxKind::PIPE | SyntaxKind::GREATER))
114 }
115}
116
117impl ValueNode for TaggedNode {
118 fn is_inline(&self) -> bool {
119 let mut seen_newline = false;
123 for child in self.0.children_with_tokens() {
124 match child {
125 rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::NEWLINE => {
126 seen_newline = true;
127 }
128 rowan::NodeOrToken::Node(n) => {
129 if seen_newline {
130 return false;
131 }
132 return match n.kind() {
133 SyntaxKind::MAPPING => Mapping::cast(n).is_some_and(|m| m.is_inline()),
134 SyntaxKind::SEQUENCE => Sequence::cast(n).is_some_and(|s| s.is_inline()),
135 SyntaxKind::SCALAR => Scalar::cast(n).is_some_and(|s| s.is_inline()),
136 _ => true,
137 };
138 }
139 _ => {}
140 }
141 }
142 true
143 }
144}
145
146pub(crate) fn ends_with_newline(node: &SyntaxNode) -> bool {
150 node.last_token()
151 .map(|t| t.kind() == SyntaxKind::NEWLINE)
152 .unwrap_or(false)
153}
154
155pub(crate) fn add_newline_token(
157 elements: &mut Vec<rowan::NodeOrToken<rowan::SyntaxNode<Lang>, rowan::SyntaxToken<Lang>>>,
158) {
159 let mut nl_builder = rowan::GreenNodeBuilder::new();
160 nl_builder.start_node(SyntaxKind::ROOT.into());
161 nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
162 nl_builder.finish_node();
163 let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
164 if let Some(token) = nl_node.first_token() {
165 elements.push(token.into());
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Hash)]
171pub struct Set(SyntaxNode);
172
173impl Set {
174 pub fn cast(node: SyntaxNode) -> Option<Self> {
176 if node.kind() == SyntaxKind::TAGGED_NODE {
177 if let Some(tagged_node) = TaggedNode::cast(node.clone()) {
178 if tagged_node.tag().as_deref() == Some("!!set") {
179 return Some(Set(node));
180 }
181 }
182 }
183 None
184 }
185
186 fn inner_mapping(&self) -> Option<Mapping> {
188 self.0.children().find_map(Mapping::cast)
189 }
190
191 pub fn members(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
204 self.inner_mapping().into_iter().flat_map(|m| {
218 let mapping_node = m.syntax().clone();
219 let has_entries = mapping_node
220 .children()
221 .any(|n| n.kind() == SyntaxKind::MAPPING_ENTRY);
222
223 if has_entries {
224 mapping_node
226 .children()
227 .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
228 .filter_map(|entry| {
229 entry
230 .children()
231 .find(|n| n.kind() == SyntaxKind::KEY)
232 .and_then(|key| key.children().next())
233 .and_then(crate::as_yaml::YamlNode::from_syntax)
234 })
235 .collect::<Vec<_>>()
236 } else {
237 mapping_node
239 .children()
240 .filter(|n| n.kind() == SyntaxKind::KEY)
241 .filter_map(|key| {
242 key.children()
243 .next()
244 .and_then(crate::as_yaml::YamlNode::from_syntax)
245 })
246 .collect::<Vec<_>>()
247 }
248 })
249 }
250
251 pub fn len(&self) -> usize {
253 self.members().count()
254 }
255
256 pub fn is_empty(&self) -> bool {
258 self.members().next().is_none()
259 }
260
261 pub fn contains(&self, value: impl crate::AsYaml) -> bool {
263 self.members()
264 .any(|member| crate::as_yaml::yaml_eq(&member, &value))
265 }
266}
267
268fn extract_content_node(wrapper: &SyntaxNode) -> Option<SyntaxNode> {
272 use crate::lex::SyntaxKind;
273 match wrapper.kind() {
274 SyntaxKind::VALUE | SyntaxKind::KEY => wrapper.children().next(),
275 _ => Some(wrapper.clone()),
276 }
277}
278
279fn smart_cast<T: AstNode<Language = Lang>>(node: SyntaxNode) -> Option<T> {
281 if let Some(content) = extract_content_node(&node) {
282 T::cast(content)
283 } else {
284 None
285 }
286}
287
288pub(crate) fn extract_scalar(node: &SyntaxNode) -> Option<Scalar> {
290 smart_cast(node.clone())
291}
292
293pub(crate) fn extract_mapping(node: &SyntaxNode) -> Option<Mapping> {
295 smart_cast(node.clone())
296}
297
298pub(crate) fn extract_sequence(node: &SyntaxNode) -> Option<Sequence> {
300 smart_cast(node.clone())
301}
302
303pub(crate) fn extract_tagged_node(node: &SyntaxNode) -> Option<TaggedNode> {
305 smart_cast(node.clone())
306}
307
308pub(crate) fn copy_node_to_builder(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
310 builder.start_node(node.kind().into());
311 add_node_children_to(builder, node);
312 builder.finish_node();
313}
314
315pub(crate) fn add_node_children_to(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
317 for child in node.children_with_tokens() {
318 match child {
319 rowan::NodeOrToken::Node(child_node) => {
320 builder.start_node(child_node.kind().into());
321 add_node_children_to(builder, &child_node);
322 builder.finish_node();
323 }
324 rowan::NodeOrToken::Token(token) => {
325 builder.token(token.kind().into(), token.text());
326 }
327 }
328 }
329}
330
331pub(crate) fn dump_cst_to_string(node: &SyntaxNode, indent: usize) -> String {
333 let mut result = String::new();
334 let indent_str = " ".repeat(indent);
335
336 for child in node.children_with_tokens() {
337 match child {
338 rowan::NodeOrToken::Node(n) => {
339 result.push_str(&format!("{}{:?}\n", indent_str, n.kind()));
340 result.push_str(&dump_cst_to_string(&n, indent + 1));
341 }
342 rowan::NodeOrToken::Token(t) => {
343 result.push_str(&format!("{} {:?} {:?}\n", indent_str, t.kind(), t.text()));
344 }
345 }
346 }
347 result
348}
349
350impl Default for YamlFile {
351 fn default() -> Self {
352 Self::new()
353 }
354}
355
356impl YamlFile {
357 pub fn new() -> YamlFile {
359 let mut builder = GreenNodeBuilder::new();
360 builder.start_node(SyntaxKind::ROOT.into());
361 builder.finish_node();
362 YamlFile(SyntaxNode::new_root_mut(builder.finish()))
363 }
364
365 pub fn parse(text: &str) -> Parse<YamlFile> {
367 Parse::parse_yaml(text)
368 }
369
370 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<YamlFile, crate::YamlError> {
372 let contents = std::fs::read_to_string(path)?;
373 Self::from_str(&contents)
374 }
375
376 pub fn from_str_relaxed(s: &str) -> (YamlFile, Vec<String>) {
392 let parsed = YamlFile::parse(s);
393 let errors = parsed.errors();
394 (parsed.tree(), errors)
395 }
396
397 pub fn from_path_relaxed<P: AsRef<Path>>(
402 path: P,
403 ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
404 let contents = std::fs::read_to_string(path)?;
405 Ok(Self::from_str_relaxed(&contents))
406 }
407
408 pub fn read_relaxed<R: std::io::Read>(
413 mut r: R,
414 ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
415 let mut buf = String::new();
416 r.read_to_string(&mut buf)?;
417 Ok(Self::from_str_relaxed(&buf))
418 }
419
420 pub fn documents(&self) -> impl Iterator<Item = Document> {
422 self.0.children().filter_map(Document::cast)
423 }
424
425 pub fn comments(&self) -> impl Iterator<Item = Comment> {
440 crate::nodes::comment::comments(&self.0)
441 }
442
443 pub fn document(&self) -> Option<Document> {
448 self.documents().next()
449 }
450
451 pub fn ensure_document(&self) -> Document {
455 if self.documents().next().is_none() {
456 let doc = Document::new_mapping();
458 self.push_document(doc);
459 }
460 self.documents()
461 .next()
462 .expect("Document should exist after ensuring")
463 }
464
465 pub fn directives(&self) -> impl Iterator<Item = Directive> {
467 self.0.children().filter_map(Directive::cast)
468 }
469
470 pub fn add_directive(&self, directive_text: &str) {
479 let mut builder = GreenNodeBuilder::new();
481 builder.start_node(SyntaxKind::DIRECTIVE.into());
482 builder.token(SyntaxKind::DIRECTIVE.into(), directive_text);
483 builder.finish_node();
484 let directive_node = SyntaxNode::new_root_mut(builder.finish());
485
486 self.0.splice_children(0..0, vec![directive_node.into()]);
488 }
489
490 pub fn push_document(&self, document: Document) {
492 let children_count = self.0.children_with_tokens().count();
493
494 self.0
496 .splice_children(children_count..children_count, vec![document.0.into()]);
497 }
498
499 pub fn set(&self, key: impl crate::AsYaml, value: impl crate::AsYaml) {
508 if let Some(doc) = self.document() {
509 doc.set(key, value);
510 }
511 }
512
513 pub fn insert_after(
522 &self,
523 after_key: impl crate::AsYaml,
524 key: impl crate::AsYaml,
525 value: impl crate::AsYaml,
526 ) -> bool {
527 if let Some(doc) = self.document() {
528 doc.insert_after(after_key, key, value)
529 } else {
530 false
531 }
532 }
533
534 pub fn insert_before(
543 &self,
544 before_key: impl crate::AsYaml,
545 key: impl crate::AsYaml,
546 value: impl crate::AsYaml,
547 ) -> bool {
548 if let Some(doc) = self.document() {
549 doc.insert_before(before_key, key, value)
550 } else {
551 false
552 }
553 }
554
555 pub fn move_after(
566 &self,
567 after_key: impl crate::AsYaml,
568 key: impl crate::AsYaml,
569 value: impl crate::AsYaml,
570 ) -> bool {
571 if let Some(doc) = self.document() {
572 doc.move_after(after_key, key, value)
573 } else {
574 false
575 }
576 }
577
578 pub fn move_before(
589 &self,
590 before_key: impl crate::AsYaml,
591 key: impl crate::AsYaml,
592 value: impl crate::AsYaml,
593 ) -> bool {
594 if let Some(doc) = self.document() {
595 doc.move_before(before_key, key, value)
596 } else {
597 false
598 }
599 }
600
601 pub fn insert_at_index(
610 &self,
611 index: usize,
612 key: impl crate::AsYaml,
613 value: impl crate::AsYaml,
614 ) {
615 if let Some(doc) = self.document() {
616 doc.insert_at_index(index, key, value);
617 } else {
619 let mut builder = GreenNodeBuilder::new();
622 builder.start_node(SyntaxKind::DOCUMENT.into());
623 builder.start_node(SyntaxKind::MAPPING.into());
624 builder.finish_node(); builder.finish_node(); let doc = Document(SyntaxNode::new_root_mut(builder.finish()));
627
628 self.0.splice_children(0..0, vec![doc.0.into()]);
630
631 if let Some(doc) = self.document() {
633 doc.insert_at_index(index, key, value);
634 }
635 }
636 }
637}
638
639impl FromStr for YamlFile {
640 type Err = crate::YamlError;
641
642 fn from_str(s: &str) -> Result<Self, Self::Err> {
643 let parsed = YamlFile::parse(s);
644 if !parsed.positioned_errors().is_empty() {
645 let first = &parsed.positioned_errors()[0];
646 let lc = crate::byte_offset_to_line_column(s, first.range.start as usize);
647 return Err(crate::YamlError::Parse {
648 message: first.message.clone(),
649 line: Some(lc.line),
650 column: Some(lc.column),
651 });
652 }
653 Ok(parsed.tree())
654 }
655}
656
657const MAX_FLOW_DEPTH: usize = 256;
661
662struct Parser {
664 tokens: Vec<(SyntaxKind, String)>,
665 current_token_index: usize,
666 builder: GreenNodeBuilder<'static>,
667 errors: Vec<String>,
668 positioned_errors: Vec<PositionedParseError>,
669 in_flow_context: bool,
670 error_context: ErrorRecoveryContext,
672 in_value_context: bool,
674 current_line_indent: usize,
676 flow_depth: usize,
678}
679
680impl Parser {
681 fn new(text: &str) -> Self {
682 let lexed = lex(text);
683 let mut tokens = Vec::new();
684
685 for (kind, token_text) in lexed {
686 tokens.push((kind, token_text.to_string()));
687 }
688
689 let token_count = tokens.len();
691 tokens.reverse();
692
693 Self {
694 tokens,
695 current_token_index: token_count,
696 builder: GreenNodeBuilder::new(),
697 errors: Vec::new(),
698 positioned_errors: Vec::new(),
699 in_flow_context: false,
700 error_context: ErrorRecoveryContext::new(text.to_string()),
701 in_value_context: false,
702 current_line_indent: 0,
703 flow_depth: 0,
704 }
705 }
706
707 fn parse(mut self) -> ParsedYaml {
708 self.builder.start_node(SyntaxKind::ROOT.into());
709
710 if self.current() == Some(SyntaxKind::BOM) {
713 self.bump(); }
715
716 self.skip_ws_and_newlines();
717
718 while self.current() == Some(SyntaxKind::DIRECTIVE) {
720 self.parse_directive();
721 self.skip_ws_and_newlines();
722 }
723
724 if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
727 self.parse_document();
728 self.skip_ws_and_newlines();
729
730 while self.current() == Some(SyntaxKind::DOC_START)
732 || self.current() == Some(SyntaxKind::DIRECTIVE)
733 {
734 while self.current() == Some(SyntaxKind::DIRECTIVE) {
736 self.parse_directive();
737 self.skip_ws_and_newlines();
738 }
739
740 if self.current() == Some(SyntaxKind::DOC_START)
742 || (self.current().is_some() && self.current() != Some(SyntaxKind::EOF))
743 {
744 self.parse_document();
745 self.skip_ws_and_newlines();
746 } else {
747 break;
748 }
749 }
750 }
751
752 while self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
755 self.builder.start_node(SyntaxKind::ERROR.into());
756
757 while self.current().is_some()
759 && self.current() != Some(SyntaxKind::EOF)
760 && self.current() != Some(SyntaxKind::DOC_START)
761 && self.current() != Some(SyntaxKind::DIRECTIVE)
762 {
763 self.bump();
764 }
765
766 self.builder.finish_node();
767
768 if self.current() == Some(SyntaxKind::DOC_START)
770 || self.current() == Some(SyntaxKind::DIRECTIVE)
771 {
772 while self.current() == Some(SyntaxKind::DIRECTIVE) {
774 self.parse_directive();
775 self.skip_ws_and_newlines();
776 }
777
778 if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
780 self.parse_document();
781 self.skip_ws_and_newlines();
782 }
783 }
784 }
785
786 self.builder.finish_node();
787
788 ParsedYaml {
789 green_node: self.builder.finish(),
790 errors: self.errors,
791 positioned_errors: self.positioned_errors,
792 }
793 }
794
795 fn parse_document(&mut self) {
796 self.builder.start_node(SyntaxKind::DOCUMENT.into());
797
798 if self.current() == Some(SyntaxKind::DOC_START) {
800 self.bump();
801 self.skip_ws_and_newlines();
802 }
803
804 if self.current().is_some()
806 && self.current() != Some(SyntaxKind::DOC_END)
807 && self.current() != Some(SyntaxKind::DOC_START)
808 {
809 self.parse_value();
810 }
811
812 if self.current() == Some(SyntaxKind::DOC_END) {
814 self.bump();
815
816 self.skip_whitespace();
818 if self.current().is_some()
819 && self.current() != Some(SyntaxKind::NEWLINE)
820 && self.current() != Some(SyntaxKind::EOF)
821 && self.current() != Some(SyntaxKind::DOC_START)
822 && self.current() != Some(SyntaxKind::DIRECTIVE)
823 {
824 self.builder.start_node(SyntaxKind::ERROR.into());
826 while self.current().is_some()
827 && self.current() != Some(SyntaxKind::NEWLINE)
828 && self.current() != Some(SyntaxKind::EOF)
829 && self.current() != Some(SyntaxKind::DOC_START)
830 && self.current() != Some(SyntaxKind::DIRECTIVE)
831 {
832 self.bump();
833 }
834 self.builder.finish_node();
835 }
836 }
837
838 self.builder.finish_node();
839 }
840
841 fn parse_value(&mut self) {
842 self.parse_value_with_base_indent(0);
843 }
844
845 fn parse_value_with_base_indent(&mut self, base_indent: usize) {
846 match self.current() {
847 Some(SyntaxKind::COMMENT) => {
848 self.bump(); self.skip_ws_and_newlines(); self.parse_value_with_base_indent(base_indent);
853 }
854 Some(SyntaxKind::DASH) if !self.in_flow_context => {
855 self.parse_sequence_with_base_indent(base_indent)
856 }
857 Some(SyntaxKind::ANCHOR) => {
858 self.bump(); self.skip_whitespace();
860 self.parse_value_with_base_indent(base_indent);
861 }
862 Some(SyntaxKind::REFERENCE) => self.parse_alias(),
863 Some(SyntaxKind::TAG) => self.parse_tagged_value(),
864 Some(SyntaxKind::MERGE_KEY) => {
865 self.parse_mapping_with_base_indent(base_indent);
867 }
868 Some(SyntaxKind::QUESTION) => {
869 self.parse_explicit_key_mapping();
871 }
872 Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
873 Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
874 Some(kind) if is_plain_scalar_kind(kind) => {
875 if !self.in_flow_context && !self.in_value_context && self.is_mapping_key() {
879 self.parse_mapping_with_base_indent(base_indent);
880 } else {
881 self.parse_scalar();
882 }
883 }
884 Some(SyntaxKind::LEFT_BRACKET) => {
885 if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
888 {
889 self.parse_complex_key_mapping();
890 } else {
891 self.parse_flow_sequence();
892 }
893 }
894 Some(SyntaxKind::LEFT_BRACE) => {
895 if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
898 {
899 self.parse_complex_key_mapping();
900 } else {
901 self.parse_flow_mapping();
902 }
903 }
904 Some(SyntaxKind::INDENT) => {
905 self.bump(); self.parse_value(); }
909 Some(SyntaxKind::NEWLINE) => {
910 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
913 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
914 self.bump(); self.parse_value_with_base_indent(indent_level);
916 } else {
917 self.builder.start_node(SyntaxKind::SCALAR.into());
919 self.builder.finish_node();
920 }
921 }
922 _ => self.parse_scalar(),
923 }
924 }
925
926 fn parse_alias(&mut self) {
927 self.builder.start_node(SyntaxKind::ALIAS.into());
930 if self.current() == Some(SyntaxKind::REFERENCE) {
931 self.bump(); }
933 self.builder.finish_node();
934 }
935
936 fn parse_scalar(&mut self) {
937 self.builder.start_node(SyntaxKind::SCALAR.into());
938
939 if matches!(
941 self.current(),
942 Some(SyntaxKind::QUOTE | SyntaxKind::SINGLE_QUOTE)
943 ) {
944 let quote_type = self
945 .current()
946 .expect("current token is Some: checked by matches! guard above");
947 self.bump(); while self.current().is_some() && self.current() != Some(quote_type) {
951 self.bump();
952 }
953
954 if self.current() == Some(quote_type) {
955 self.bump(); } else {
957 let expected_quote = if quote_type == SyntaxKind::QUOTE {
958 "\""
959 } else {
960 "'"
961 };
962 let error_msg = self.create_detailed_error(
963 "Unterminated quoted string",
964 &format!("closing quote {}", expected_quote),
965 self.current_text(),
966 );
967 self.add_error_and_recover(
968 error_msg,
969 quote_type,
970 ParseErrorKind::UnterminatedString,
971 );
972 }
973 } else {
974 if matches!(
976 self.current(),
977 Some(
978 SyntaxKind::STRING
979 | SyntaxKind::UNTERMINATED_STRING
980 | SyntaxKind::INT
981 | SyntaxKind::FLOAT
982 | SyntaxKind::BOOL
983 | SyntaxKind::NULL
984 )
985 ) {
986 if self.current() == Some(SyntaxKind::UNTERMINATED_STRING) {
988 self.add_error(
989 "Unterminated quoted string".to_string(),
990 ParseErrorKind::UnterminatedString,
991 );
992 }
993 if !self.in_flow_context {
994 let scalar_indent = self.current_line_indent;
1002
1003 while let Some(kind) = self.current() {
1004 if kind == SyntaxKind::COMMENT {
1005 break;
1007 }
1008
1009 if kind == SyntaxKind::NEWLINE {
1010 if self.is_plain_scalar_continuation(scalar_indent) {
1012 self.bump(); while matches!(
1017 self.current(),
1018 Some(SyntaxKind::INDENT | SyntaxKind::WHITESPACE)
1019 ) {
1020 self.bump();
1021 }
1022
1023 continue;
1025 } else {
1026 break;
1028 }
1029 }
1030
1031 if matches!(
1033 kind,
1034 SyntaxKind::LEFT_BRACKET
1035 | SyntaxKind::LEFT_BRACE
1036 | SyntaxKind::RIGHT_BRACKET
1037 | SyntaxKind::RIGHT_BRACE
1038 | SyntaxKind::COMMA
1039 ) {
1040 break;
1041 }
1042
1043 if kind == SyntaxKind::WHITESPACE {
1045 if self.tokens.len() >= 2 {
1047 let next_kind = self.tokens[self.tokens.len() - 2].0;
1048 if next_kind == SyntaxKind::COMMENT {
1049 break;
1051 }
1052 }
1053 }
1054
1055 self.bump();
1056 }
1057 } else {
1058 let is_quoted_string = if let Some(SyntaxKind::STRING) = self.current() {
1066 self.current_text()
1067 .map(|text| text.starts_with('"') || text.starts_with('\''))
1068 .unwrap_or(false)
1069 } else {
1070 false
1071 };
1072
1073 self.bump(); if !is_quoted_string {
1078 while let Some(kind) = self.current() {
1079 if matches!(
1081 kind,
1082 SyntaxKind::COMMA
1083 | SyntaxKind::RIGHT_BRACE
1084 | SyntaxKind::RIGHT_BRACKET
1085 | SyntaxKind::COMMENT
1086 ) {
1087 break;
1088 }
1089
1090 if kind == SyntaxKind::NEWLINE {
1093 self.bump(); while matches!(
1096 self.current(),
1097 Some(SyntaxKind::WHITESPACE | SyntaxKind::INDENT)
1098 ) {
1099 self.bump();
1100 }
1101 continue;
1103 }
1104
1105 if kind == SyntaxKind::WHITESPACE {
1109 if self.tokens.len() >= 2 {
1112 let after_whitespace = self.tokens[self.tokens.len() - 2].0;
1114 if matches!(
1115 after_whitespace,
1116 SyntaxKind::COMMA
1117 | SyntaxKind::RIGHT_BRACE
1118 | SyntaxKind::RIGHT_BRACKET
1119 | SyntaxKind::NEWLINE
1120 | SyntaxKind::COMMENT
1121 ) {
1122 break;
1124 }
1125 }
1127 }
1128
1129 if kind == SyntaxKind::COLON && self.tokens.len() >= 2 {
1131 let next_kind = self.tokens[self.tokens.len() - 2].0;
1132 if matches!(
1133 next_kind,
1134 SyntaxKind::COMMA
1135 | SyntaxKind::RIGHT_BRACE
1136 | SyntaxKind::RIGHT_BRACKET
1137 | SyntaxKind::WHITESPACE
1138 | SyntaxKind::NEWLINE
1139 ) {
1140 break;
1142 }
1143 }
1144
1145 self.bump();
1146 }
1147 }
1148 }
1149 } else {
1150 while let Some(kind) = self.current() {
1152 if matches!(
1153 kind,
1154 SyntaxKind::NEWLINE
1155 | SyntaxKind::DASH
1156 | SyntaxKind::COMMENT
1157 | SyntaxKind::DOC_START
1158 | SyntaxKind::DOC_END
1159 ) {
1160 break;
1161 }
1162
1163 if kind == SyntaxKind::COLON {
1166 if self.in_flow_context {
1167 if self.tokens.len() >= 2 {
1170 let next_kind = self.tokens[self.tokens.len() - 2].0;
1171 if matches!(
1172 next_kind,
1173 SyntaxKind::COMMA
1174 | SyntaxKind::RIGHT_BRACE
1175 | SyntaxKind::RIGHT_BRACKET
1176 | SyntaxKind::WHITESPACE
1177 | SyntaxKind::NEWLINE
1178 ) {
1179 break;
1181 }
1182 }
1183 } else {
1185 break;
1187 }
1188 }
1189
1190 if self.in_flow_context
1192 && matches!(
1193 kind,
1194 SyntaxKind::LEFT_BRACKET
1195 | SyntaxKind::RIGHT_BRACKET
1196 | SyntaxKind::LEFT_BRACE
1197 | SyntaxKind::RIGHT_BRACE
1198 | SyntaxKind::COMMA
1199 )
1200 {
1201 break;
1202 }
1203 self.bump();
1204 }
1205 }
1206 }
1207
1208 self.builder.finish_node();
1209 }
1210
1211 fn parse_tagged_value(&mut self) {
1212 let tag_text = self.peek_tag_text();
1214
1215 match tag_text {
1216 Some("!!set") => self.parse_tagged_set(),
1217 Some("!!omap") => self.parse_tagged_omap(),
1218 Some("!!pairs") => self.parse_tagged_pairs(),
1219 _ => {
1220 self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1222 self.bump(); while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1226 self.bump();
1227 }
1228
1229 self.parse_value();
1231
1232 self.builder.finish_node();
1233 }
1234 }
1235 }
1236
1237 fn peek_tag_text(&self) -> Option<&str> {
1238 self.tokens
1239 .last()
1240 .filter(|(kind, _)| *kind == SyntaxKind::TAG)
1241 .map(|(_, text)| text.as_str())
1242 }
1243
1244 fn parse_tagged_set(&mut self) {
1245 self.parse_tagged_collection(true); }
1247
1248 fn parse_tagged_omap(&mut self) {
1249 self.parse_tagged_collection(false); }
1251
1252 fn parse_tagged_pairs(&mut self) {
1253 self.parse_tagged_collection(false); }
1255
1256 fn parse_tagged_collection(&mut self, is_mapping: bool) {
1257 self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1258
1259 self.bump(); while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1264 self.bump();
1265 }
1266
1267 match self.current() {
1269 Some(SyntaxKind::LEFT_BRACE) if is_mapping => self.parse_flow_mapping(),
1270 Some(SyntaxKind::LEFT_BRACKET) if !is_mapping => self.parse_flow_sequence(),
1271 Some(SyntaxKind::NEWLINE) => {
1272 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1275 self.bump(); }
1277 if is_mapping {
1278 self.parse_mapping();
1279 } else {
1280 self.parse_sequence();
1281 }
1282 }
1283 _ => {
1284 if is_mapping {
1285 self.parse_mapping();
1286 } else {
1287 self.parse_sequence();
1288 }
1289 }
1290 }
1291
1292 self.builder.finish_node();
1293 }
1294
1295 fn parse_literal_block_scalar(&mut self) {
1296 self.builder.start_node(SyntaxKind::SCALAR.into());
1297 self.bump(); self.parse_block_scalar_header();
1299 self.parse_block_scalar_content();
1300 self.builder.finish_node();
1301 }
1302
1303 fn parse_folded_block_scalar(&mut self) {
1304 self.builder.start_node(SyntaxKind::SCALAR.into());
1305 self.bump(); self.parse_block_scalar_header();
1307 self.parse_block_scalar_content();
1308 self.builder.finish_node();
1309 }
1310
1311 fn parse_block_scalar_header(&mut self) {
1312 while let Some(kind) = self.current() {
1317 match kind {
1318 SyntaxKind::NEWLINE | SyntaxKind::COMMENT => break,
1319 SyntaxKind::INT => {
1320 if let Some(text) = self.current_text() {
1322 if text.len() == 1
1323 && text
1324 .chars()
1325 .next()
1326 .expect("text is non-empty: len == 1 checked above")
1327 .is_ascii_digit()
1328 {
1329 self.bump(); } else {
1331 break;
1333 }
1334 } else {
1335 break;
1336 }
1337 }
1338 SyntaxKind::STRING => {
1339 if let Some(text) = self.current_text() {
1341 if text == "+" || text == "-" {
1342 self.bump(); } else {
1344 break;
1346 }
1347 } else {
1348 break;
1349 }
1350 }
1351 SyntaxKind::WHITESPACE => {
1352 self.bump();
1354 }
1355 _ => {
1356 break;
1358 }
1359 }
1360 }
1361
1362 if self.current() == Some(SyntaxKind::COMMENT) {
1364 self.bump();
1365 }
1366
1367 if self.current() == Some(SyntaxKind::NEWLINE) {
1369 self.bump();
1370 }
1371 }
1372
1373 fn parse_block_scalar_content(&mut self) {
1374 let mut last_was_newline = false;
1376 let mut base_indent: Option<usize> = None;
1377 let mut first_content_indent: Option<usize> = None;
1378
1379 while let Some(kind) = self.current() {
1380 if kind == SyntaxKind::INDENT && first_content_indent.is_none() {
1382 first_content_indent = self.current_text().map(|t| t.len());
1383 }
1384
1385 if base_indent.is_none() && first_content_indent.is_some() {
1387 base_indent = first_content_indent;
1388 }
1389
1390 if self.is_at_unindented_content_for_block_scalar(last_was_newline, base_indent) {
1392 break;
1393 }
1394
1395 match kind {
1396 SyntaxKind::DOC_START | SyntaxKind::DOC_END => break,
1398 SyntaxKind::NEWLINE => {
1400 self.bump();
1401 last_was_newline = true;
1402 continue;
1403 }
1404 _ => {
1406 self.bump();
1407 last_was_newline = false;
1408 }
1409 }
1410 }
1411 }
1412
1413 fn is_at_unindented_content_for_block_scalar(
1414 &self,
1415 after_newline: bool,
1416 base_indent: Option<usize>,
1417 ) -> bool {
1418 if after_newline {
1421 let current = self.current();
1423
1424 if matches!(
1426 current,
1427 Some(SyntaxKind::COLON) | Some(SyntaxKind::QUESTION)
1428 ) {
1429 return true;
1430 }
1431
1432 if let Some(base) = base_indent {
1434 if current == Some(SyntaxKind::INDENT) {
1435 if let Some(text) = self.current_text() {
1436 if text.len() < base {
1437 return true;
1439 }
1440 }
1441 }
1442 }
1443
1444 if current != Some(SyntaxKind::INDENT)
1446 && current != Some(SyntaxKind::WHITESPACE)
1447 && current != Some(SyntaxKind::NEWLINE)
1448 && current != Some(SyntaxKind::COMMENT)
1449 {
1450 return true;
1452 }
1453 }
1454 false
1455 }
1456
1457 fn parse_mapping(&mut self) {
1458 self.parse_mapping_with_base_indent(0);
1459 }
1460
1461 fn parse_mapping_with_base_indent(&mut self, base_indent: usize) {
1462 self.builder.start_node(SyntaxKind::MAPPING.into());
1463 self.error_context.push_context(ParseContext::Mapping);
1464
1465 while self.current().is_some() {
1466 let tokens_before_iter = self.tokens.len();
1467 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1469 break;
1470 }
1471
1472 loop {
1474 if self.current() == Some(SyntaxKind::COMMENT) {
1475 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1478 break;
1479 }
1480 self.bump();
1481 if self.current() == Some(SyntaxKind::NEWLINE) {
1482 self.bump();
1483 }
1484 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1485 break;
1486 }
1487 } else {
1488 break;
1489 }
1490 }
1491
1492 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1496 break;
1497 }
1498
1499 if !self.is_mapping_key() && !self.is_complex_mapping_key() {
1501 break;
1502 }
1503
1504 if self.current() == Some(SyntaxKind::LEFT_BRACKET)
1506 || self.current() == Some(SyntaxKind::LEFT_BRACE)
1507 {
1508 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1510
1511 self.builder.start_node(SyntaxKind::KEY.into());
1512 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
1513 self.parse_flow_sequence();
1514 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
1515 self.parse_flow_mapping();
1516 }
1517 self.builder.finish_node();
1518
1519 self.skip_ws_and_newlines();
1520
1521 if self.current() == Some(SyntaxKind::COLON) {
1522 self.bump();
1523 self.skip_whitespace();
1524
1525 self.builder.start_node(SyntaxKind::VALUE.into());
1526 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1527 self.parse_value();
1528 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1529 self.bump();
1530 if self.current() == Some(SyntaxKind::INDENT) {
1531 self.bump();
1532 self.parse_value();
1533 }
1534 }
1535 self.builder.finish_node();
1536 } else {
1537 let error_msg = self.create_detailed_error(
1538 "Missing colon in mapping",
1539 "':' after key",
1540 self.current_text(),
1541 );
1542 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1543 }
1544
1545 self.builder.finish_node();
1547 }
1548 else if self.current() == Some(SyntaxKind::QUESTION) {
1550 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1552
1553 self.bump(); self.skip_whitespace();
1556
1557 self.builder.start_node(SyntaxKind::KEY.into());
1558 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1559 self.parse_value();
1560 }
1561 self.builder.finish_node();
1562
1563 self.skip_ws_and_newlines();
1564
1565 if self.current() == Some(SyntaxKind::COLON) {
1567 self.bump(); self.skip_whitespace();
1569
1570 self.builder.start_node(SyntaxKind::VALUE.into());
1571 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1572 self.parse_value();
1573 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1574 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1576 self.bump(); self.parse_value();
1578 }
1579 }
1580 self.builder.finish_node();
1581 } else {
1582 self.builder.start_node(SyntaxKind::VALUE.into());
1584 self.builder.start_node(SyntaxKind::SCALAR.into());
1585 self.builder.token(SyntaxKind::NULL.into(), "");
1586 self.builder.finish_node();
1587 self.builder.finish_node();
1588 }
1589
1590 self.builder.finish_node();
1592 } else {
1593 self.parse_mapping_key_value_pair(base_indent);
1594 }
1595
1596 if self.tokens.len() == tokens_before_iter {
1601 let unexpected = self.current_text().unwrap_or("").to_string();
1602 self.add_error(
1603 format!("Unexpected token in mapping: {:?}", unexpected),
1604 ParseErrorKind::Other,
1605 );
1606 self.bump();
1607 }
1608 }
1609
1610 self.builder.finish_node();
1611 self.error_context.pop_context();
1612 }
1613
1614 fn parse_sequence(&mut self) {
1615 self.parse_sequence_with_base_indent(0);
1616 }
1617
1618 fn parse_sequence_with_base_indent(&mut self, base_indent: usize) {
1619 self.builder.start_node(SyntaxKind::SEQUENCE.into());
1620 self.error_context.push_context(ParseContext::Sequence);
1621
1622 while self.current().is_some() {
1623 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1625 break;
1626 }
1627
1628 loop {
1630 if self.current() == Some(SyntaxKind::COMMENT) {
1631 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1634 break;
1635 }
1636 self.bump();
1637 if self.current() == Some(SyntaxKind::NEWLINE) {
1638 self.bump();
1639 }
1640 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1641 break;
1642 }
1643 } else {
1644 break;
1645 }
1646 }
1647
1648 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1652 break;
1653 }
1654
1655 if self.current() != Some(SyntaxKind::DASH) {
1657 break;
1658 }
1659 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1661
1662 self.bump(); self.skip_whitespace();
1664
1665 let item_indent = self.current_line_indent;
1667
1668 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1669 self.parse_value_with_base_indent(item_indent);
1671 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1672 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1675 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
1676 self.bump(); self.parse_value_with_base_indent(indent_level);
1679 }
1680 }
1681
1682 if self.current() == Some(SyntaxKind::NEWLINE) {
1684 self.bump();
1685 }
1686
1687 self.builder.finish_node();
1689 }
1690
1691 self.builder.finish_node();
1692 self.error_context.pop_context();
1693 }
1694
1695 fn next_flow_element_is_implicit_mapping(&self) -> bool {
1708 let tokens = std::iter::once(self.current().unwrap_or(SyntaxKind::EOF))
1710 .chain(self.upcoming_tokens());
1711 has_implicit_mapping_pattern(tokens)
1712 }
1713
1714 fn parse_implicit_flow_mapping(&mut self) {
1717 self.builder.start_node(SyntaxKind::MAPPING.into());
1718 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1719
1720 self.builder.start_node(SyntaxKind::KEY.into());
1722 self.parse_value();
1723 self.builder.finish_node();
1724
1725 self.skip_ws_and_newlines();
1726
1727 if self.current() == Some(SyntaxKind::COLON) {
1729 self.bump();
1730 self.skip_ws_and_newlines();
1731 }
1732
1733 self.builder.start_node(SyntaxKind::VALUE.into());
1735 if matches!(
1737 self.current(),
1738 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACKET)
1739 ) {
1740 } else {
1742 self.parse_value();
1743 }
1744 self.builder.finish_node();
1745
1746 self.builder.finish_node(); self.builder.finish_node(); }
1749}
1750
1751fn is_plain_scalar_kind(kind: SyntaxKind) -> bool {
1753 matches!(
1754 kind,
1755 SyntaxKind::STRING
1756 | SyntaxKind::INT
1757 | SyntaxKind::FLOAT
1758 | SyntaxKind::BOOL
1759 | SyntaxKind::NULL
1760 )
1761}
1762
1763fn has_implicit_mapping_pattern(tokens: impl Iterator<Item = SyntaxKind>) -> bool {
1767 let mut depth = 0;
1768
1769 for kind in tokens {
1770 match kind {
1771 SyntaxKind::LEFT_BRACE | SyntaxKind::LEFT_BRACKET => {
1773 depth += 1;
1774 }
1775 SyntaxKind::RIGHT_BRACE | SyntaxKind::RIGHT_BRACKET => {
1777 if depth == 0 {
1778 return false;
1780 }
1781 depth -= 1;
1782 }
1783 SyntaxKind::COLON if depth == 0 => {
1785 return true;
1787 }
1788 SyntaxKind::COMMA if depth == 0 => {
1789 return false;
1791 }
1792 _ => {}
1794 }
1795 }
1796
1797 false
1799}
1800
1801impl Parser {
1802 fn parse_flow_sequence(&mut self) {
1803 self.builder.start_node(SyntaxKind::SEQUENCE.into());
1804 self.error_context.push_context(ParseContext::FlowSequence);
1805
1806 if self.flow_depth >= MAX_FLOW_DEPTH {
1807 self.add_error(
1808 format!(
1809 "Flow collection nested too deeply (limit {})",
1810 MAX_FLOW_DEPTH
1811 ),
1812 ParseErrorKind::Other,
1813 );
1814 while let Some(kind) = self.current() {
1817 if matches!(
1818 kind,
1819 SyntaxKind::RIGHT_BRACKET | SyntaxKind::DOC_START | SyntaxKind::DOC_END
1820 ) {
1821 break;
1822 }
1823 self.bump();
1824 }
1825 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1826 self.bump();
1827 }
1828 self.builder.finish_node();
1829 self.error_context.pop_context();
1830 return;
1831 }
1832 self.flow_depth += 1;
1833
1834 self.bump(); self.skip_ws_and_newlines(); let prev_flow = self.in_flow_context;
1838 self.in_flow_context = true;
1839
1840 while self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1841 let tokens_before = self.tokens.len();
1842
1843 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1845
1846 if self.next_flow_element_is_implicit_mapping() {
1849 self.parse_implicit_flow_mapping();
1851 } else {
1852 self.parse_value();
1854 }
1855
1856 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COMMA) {
1860 self.bump();
1861 self.skip_ws_and_newlines(); }
1863
1864 self.builder.finish_node(); if self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1867 if matches!(
1870 self.current(),
1871 Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1872 ) {
1873 break;
1875 }
1876 }
1877
1878 if self.tokens.len() == tokens_before {
1882 let unexpected = self.current_text().unwrap_or("").to_string();
1883 self.add_error(
1884 format!("Unexpected token in flow sequence: {:?}", unexpected),
1885 ParseErrorKind::Other,
1886 );
1887 self.bump();
1888 }
1889 }
1890
1891 self.in_flow_context = prev_flow;
1892
1893 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1894 self.bump();
1895 } else {
1896 let error_msg = self.create_detailed_error(
1897 "Unclosed flow sequence",
1898 "']' to close sequence",
1899 self.current_text(),
1900 );
1901 self.add_error_and_recover(
1902 error_msg,
1903 SyntaxKind::RIGHT_BRACKET,
1904 ParseErrorKind::UnclosedFlowSequence,
1905 );
1906 }
1907
1908 self.flow_depth -= 1;
1909 self.builder.finish_node();
1910 self.error_context.pop_context();
1911 }
1912
1913 fn parse_flow_mapping(&mut self) {
1914 self.builder.start_node(SyntaxKind::MAPPING.into());
1915 self.error_context.push_context(ParseContext::FlowMapping);
1916
1917 if self.flow_depth >= MAX_FLOW_DEPTH {
1918 self.add_error(
1919 format!(
1920 "Flow collection nested too deeply (limit {})",
1921 MAX_FLOW_DEPTH
1922 ),
1923 ParseErrorKind::Other,
1924 );
1925 while let Some(kind) = self.current() {
1926 if matches!(
1927 kind,
1928 SyntaxKind::RIGHT_BRACE | SyntaxKind::DOC_START | SyntaxKind::DOC_END
1929 ) {
1930 break;
1931 }
1932 self.bump();
1933 }
1934 if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
1935 self.bump();
1936 }
1937 self.builder.finish_node();
1938 self.error_context.pop_context();
1939 return;
1940 }
1941 self.flow_depth += 1;
1942
1943 self.bump(); self.skip_ws_and_newlines(); let prev_flow = self.in_flow_context;
1947 self.in_flow_context = true;
1948
1949 while self.current() != Some(SyntaxKind::RIGHT_BRACE) && self.current().is_some() {
1950 if matches!(
1952 self.current(),
1953 Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1954 ) {
1955 break;
1957 }
1958
1959 let tokens_before = self.tokens.len();
1960
1961 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1963
1964 self.builder.start_node(SyntaxKind::KEY.into());
1966
1967 if self.current() == Some(SyntaxKind::QUESTION) {
1969 self.bump(); self.skip_whitespace();
1971 }
1972
1973 self.parse_value();
1974 self.builder.finish_node();
1975
1976 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COLON) {
1979 self.bump();
1980 self.skip_ws_and_newlines(); if matches!(
1985 self.current(),
1986 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1987 ) {
1988 self.builder.start_node(SyntaxKind::VALUE.into());
1990 self.builder.start_node(SyntaxKind::SCALAR.into());
1991 self.builder.token(SyntaxKind::NULL.into(), "");
1992 self.builder.finish_node(); self.builder.finish_node(); } else {
1995 self.builder.start_node(SyntaxKind::VALUE.into());
1997 self.parse_value();
1998 self.builder.finish_node();
1999 }
2000 } else if matches!(
2001 self.current(),
2002 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
2003 ) {
2004 self.builder.start_node(SyntaxKind::VALUE.into());
2008 self.builder.start_node(SyntaxKind::SCALAR.into());
2009 self.builder.token(SyntaxKind::NULL.into(), "");
2010 self.builder.finish_node(); self.builder.finish_node(); } else {
2013 let error_msg = self.create_detailed_error(
2014 "Missing colon in flow mapping",
2015 "':' after key",
2016 self.current_text(),
2017 );
2018 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2019 }
2020
2021 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COMMA) {
2025 self.bump();
2026 self.skip_ws_and_newlines(); }
2028
2029 self.builder.finish_node();
2031
2032 if self.tokens.len() == tokens_before {
2036 let unexpected = self.current_text().unwrap_or("").to_string();
2037 self.add_error(
2038 format!("Unexpected token in flow mapping: {:?}", unexpected),
2039 ParseErrorKind::Other,
2040 );
2041 self.bump();
2042 }
2043 }
2044
2045 self.in_flow_context = prev_flow;
2046
2047 if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
2048 self.bump();
2049 } else {
2050 let error_msg = self.create_detailed_error(
2051 "Unclosed flow mapping",
2052 "'}' to close mapping",
2053 self.current_text(),
2054 );
2055 self.add_error_and_recover(
2056 error_msg,
2057 SyntaxKind::RIGHT_BRACE,
2058 ParseErrorKind::UnclosedFlowMapping,
2059 );
2060 }
2061
2062 self.flow_depth -= 1;
2063 self.builder.finish_node();
2064 self.error_context.pop_context();
2065 }
2066
2067 fn parse_directive(&mut self) {
2068 self.builder.start_node(SyntaxKind::DIRECTIVE.into());
2069
2070 if self.current() == Some(SyntaxKind::DIRECTIVE) {
2071 self.bump(); } else {
2073 self.add_error("Expected directive".to_string(), ParseErrorKind::Other);
2074 }
2075
2076 self.builder.finish_node();
2077 }
2078
2079 fn parse_explicit_key_mapping(&mut self) {
2080 self.builder.start_node(SyntaxKind::MAPPING.into());
2082
2083 while self.current() == Some(SyntaxKind::QUESTION) {
2084 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2086
2087 self.bump(); self.skip_whitespace();
2090
2091 self.builder.start_node(SyntaxKind::KEY.into());
2093
2094 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2096 self.parse_value();
2097 }
2098
2099 if self.current() == Some(SyntaxKind::NEWLINE) {
2102 if self.tokens.len() >= 2 {
2105 let (next_kind, _) = &self.tokens[self.tokens.len() - 2];
2106 if *next_kind == SyntaxKind::INDENT {
2107 if self.tokens.len() >= 3 {
2109 let (token_after_indent, _) = &self.tokens[self.tokens.len() - 3];
2110 if *token_after_indent != SyntaxKind::DASH {
2113 self.bump(); self.bump(); while self.current().is_some()
2119 && self.current() != Some(SyntaxKind::NEWLINE)
2120 && self.current() != Some(SyntaxKind::COLON)
2121 {
2122 let before = self.tokens.len();
2123 self.parse_scalar();
2124 if self.current() == Some(SyntaxKind::WHITESPACE) {
2125 self.bump(); }
2127 if self.tokens.len() == before {
2131 break;
2132 }
2133 }
2134 }
2135 }
2136 }
2137 }
2138 }
2139
2140 self.builder.finish_node();
2141
2142 self.skip_ws_and_newlines();
2143
2144 if self.current() == Some(SyntaxKind::COLON) {
2146 self.bump(); self.skip_whitespace();
2148
2149 self.builder.start_node(SyntaxKind::VALUE.into());
2150 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2151 self.parse_value();
2152 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2153 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2156 self.bump(); self.parse_value();
2158 }
2159 }
2160 self.builder.finish_node();
2161 } else {
2162 self.builder.start_node(SyntaxKind::VALUE.into());
2164 self.builder.start_node(SyntaxKind::SCALAR.into());
2165 self.builder.token(SyntaxKind::NULL.into(), "");
2166 self.builder.finish_node();
2167 self.builder.finish_node();
2168 }
2169
2170 self.builder.finish_node();
2172
2173 self.skip_ws_and_newlines();
2174
2175 if self.current() != Some(SyntaxKind::QUESTION) && !self.is_mapping_key() {
2177 break;
2178 }
2179 }
2180
2181 while self.current().is_some() && self.is_mapping_key() {
2183 let tokens_before_iter = self.tokens.len();
2184 if self.current() == Some(SyntaxKind::QUESTION) {
2188 self.parse_explicit_key_entries();
2189 break;
2190 }
2191 self.parse_mapping_key_value_pair(0);
2192 self.skip_ws_and_newlines();
2193 if self.tokens.len() == tokens_before_iter {
2196 let unexpected = self.current_text().unwrap_or("").to_string();
2197 self.add_error(
2198 format!("Unexpected token in explicit-key mapping: {:?}", unexpected),
2199 ParseErrorKind::Other,
2200 );
2201 self.bump();
2202 }
2203 }
2204
2205 self.builder.finish_node();
2206 }
2207
2208 fn parse_complex_key_mapping(&mut self) {
2209 self.builder.start_node(SyntaxKind::MAPPING.into());
2211
2212 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2214
2215 self.builder.start_node(SyntaxKind::KEY.into());
2217 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2218 self.parse_flow_sequence();
2219 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2220 self.parse_flow_mapping();
2221 }
2222 self.builder.finish_node();
2223
2224 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COLON) {
2228 self.bump();
2229 self.skip_whitespace();
2230
2231 self.builder.start_node(SyntaxKind::VALUE.into());
2233 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2234 self.parse_value();
2235 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2236 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2238 self.bump(); self.parse_value();
2240 }
2241 }
2242 self.builder.finish_node();
2243 } else {
2244 let error_msg = self.create_detailed_error(
2245 "Missing colon in complex mapping",
2246 "':' after complex key",
2247 self.current_text(),
2248 );
2249 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2250 }
2251
2252 self.builder.finish_node();
2254
2255 self.skip_ws_and_newlines();
2256
2257 while self.current().is_some() {
2259 let tokens_before_iter = self.tokens.len();
2260 if self.current() == Some(SyntaxKind::QUESTION) {
2261 self.parse_explicit_key_entries();
2263 break;
2264 } else if self.is_complex_mapping_key()
2265 || (self.is_mapping_key() && self.current() != Some(SyntaxKind::QUESTION))
2266 {
2267 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2269
2270 self.builder.start_node(SyntaxKind::KEY.into());
2272
2273 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2274 self.parse_flow_sequence();
2275 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2276 self.parse_flow_mapping();
2277 } else if matches!(
2278 self.current(),
2279 Some(
2280 SyntaxKind::STRING
2281 | SyntaxKind::INT
2282 | SyntaxKind::FLOAT
2283 | SyntaxKind::BOOL
2284 | SyntaxKind::NULL
2285 | SyntaxKind::MERGE_KEY
2286 )
2287 ) {
2288 self.bump();
2289 }
2290 self.builder.finish_node();
2291
2292 self.skip_whitespace();
2293
2294 if self.current() == Some(SyntaxKind::COLON) {
2295 self.bump();
2296 self.skip_whitespace();
2297
2298 self.builder.start_node(SyntaxKind::VALUE.into());
2299 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2300 self.parse_value();
2301 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2302 self.bump();
2303 if self.current() == Some(SyntaxKind::INDENT) {
2304 self.bump();
2305 self.parse_value();
2306 }
2307 }
2308 self.builder.finish_node();
2309 }
2310
2311 self.builder.finish_node();
2313
2314 self.skip_ws_and_newlines();
2315 } else {
2316 break;
2317 }
2318
2319 if self.tokens.len() == tokens_before_iter {
2323 let unexpected = self.current_text().unwrap_or("").to_string();
2324 self.add_error(
2325 format!("Unexpected token in complex mapping: {:?}", unexpected),
2326 ParseErrorKind::Other,
2327 );
2328 self.bump();
2329 }
2330 }
2331
2332 self.builder.finish_node();
2333 }
2334
2335 fn parse_explicit_key_entries(&mut self) {
2336 while self.current() == Some(SyntaxKind::QUESTION) {
2338 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2340
2341 self.bump(); self.skip_whitespace();
2343
2344 self.builder.start_node(SyntaxKind::KEY.into());
2345 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2346 self.parse_value();
2347 }
2348 self.builder.finish_node();
2349
2350 self.skip_ws_and_newlines();
2351
2352 if self.current() == Some(SyntaxKind::COLON) {
2353 self.bump();
2354 self.skip_whitespace();
2355
2356 self.builder.start_node(SyntaxKind::VALUE.into());
2357 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2358 self.parse_value();
2359 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2360 self.bump();
2361 if self.current() == Some(SyntaxKind::INDENT) {
2362 self.bump();
2363 self.parse_value();
2364 }
2365 }
2366 self.builder.finish_node();
2367 } else {
2368 self.builder.start_node(SyntaxKind::VALUE.into());
2370 self.builder.start_node(SyntaxKind::SCALAR.into());
2371 self.builder.token(SyntaxKind::NULL.into(), "");
2372 self.builder.finish_node();
2373 self.builder.finish_node();
2374 }
2375
2376 self.builder.finish_node();
2378
2379 self.skip_ws_and_newlines();
2380 }
2381 }
2382
2383 fn is_complex_mapping_key(&self) -> bool {
2384 if !matches!(
2386 self.current(),
2387 Some(SyntaxKind::LEFT_BRACKET) | Some(SyntaxKind::LEFT_BRACE)
2388 ) {
2389 return false;
2390 }
2391
2392 let mut depth = 0;
2394 let start_kind = self.current();
2395 let close_kind = match start_kind {
2396 Some(SyntaxKind::LEFT_BRACKET) => SyntaxKind::RIGHT_BRACKET,
2397 Some(SyntaxKind::LEFT_BRACE) => SyntaxKind::RIGHT_BRACE,
2398 _ => return false,
2399 };
2400
2401 let mut found_close = false;
2402 for kind in self.upcoming_tokens() {
2403 if !found_close {
2404 if Some(kind) == start_kind {
2405 depth += 1;
2406 } else if kind == close_kind {
2407 if depth == 0 {
2408 found_close = true;
2410 } else {
2411 depth -= 1;
2412 }
2413 }
2414 } else {
2415 match kind {
2417 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => continue,
2418 SyntaxKind::COLON => return true,
2419 _ => return false,
2420 }
2421 }
2422 }
2423 false
2424 }
2425
2426 fn parse_mapping_value(&mut self) {
2427 match self.current() {
2431 Some(SyntaxKind::DASH) if !self.in_flow_context => self.parse_sequence(),
2432 Some(SyntaxKind::ANCHOR) => {
2433 self.bump(); self.skip_whitespace();
2435 self.parse_value_with_base_indent(0);
2436 }
2437 Some(SyntaxKind::REFERENCE) => self.parse_alias(),
2438 Some(SyntaxKind::TAG) => self.parse_tagged_value(),
2439 Some(SyntaxKind::QUESTION) => {
2440 self.parse_explicit_key_mapping();
2442 }
2443 Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
2444 Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
2445 Some(SyntaxKind::LEFT_BRACKET) => {
2446 if !self.in_flow_context && self.is_complex_mapping_key() {
2448 self.parse_complex_key_mapping();
2449 } else {
2450 self.parse_flow_sequence();
2451 }
2452 }
2453 Some(SyntaxKind::LEFT_BRACE) => {
2454 if !self.in_flow_context && self.is_complex_mapping_key() {
2456 self.parse_complex_key_mapping();
2457 } else {
2458 self.parse_flow_mapping();
2459 }
2460 }
2461 _ => {
2462 self.parse_scalar();
2465 }
2466 }
2467 }
2468
2469 fn is_mapping_key(&self) -> bool {
2470 if self.current() == Some(SyntaxKind::QUESTION) {
2472 return true;
2473 }
2474
2475 if self.current() == Some(SyntaxKind::MERGE_KEY) {
2477 return true;
2478 }
2479
2480 if self.current() == Some(SyntaxKind::DASH) {
2482 return false;
2483 }
2484
2485 for kind in self.upcoming_tokens() {
2490 if kind == SyntaxKind::COLON {
2491 return true;
2492 }
2493 if kind != SyntaxKind::WHITESPACE && !is_plain_scalar_kind(kind) {
2494 return false;
2495 }
2496 }
2497 false
2498 }
2499
2500 fn skip_whitespace(&mut self) {
2501 self.skip_tokens(&[SyntaxKind::WHITESPACE]);
2502 }
2503
2504 fn skip_tokens(&mut self, kinds: &[SyntaxKind]) {
2505 while let Some(current) = self.current() {
2506 if kinds.contains(¤t) {
2507 self.bump();
2508 } else {
2509 break;
2510 }
2511 }
2512 }
2513
2514 fn is_plain_scalar_continuation(&self, scalar_indent: usize) -> bool {
2517 let current_idx = self.tokens.len().saturating_sub(1);
2520
2521 if current_idx == 0 {
2522 return false; }
2524
2525 let mut peek_idx = current_idx.saturating_sub(1);
2528
2529 let next_line_indent = self
2531 .tokens
2532 .get(peek_idx)
2533 .and_then(|(kind, text)| {
2534 if *kind == SyntaxKind::INDENT {
2535 peek_idx = peek_idx.saturating_sub(1);
2536 Some(text.len())
2537 } else {
2538 None
2539 }
2540 })
2541 .unwrap_or(0);
2542
2543 while self
2545 .tokens
2546 .get(peek_idx)
2547 .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2548 {
2549 peek_idx = peek_idx.saturating_sub(1);
2550 }
2551
2552 let has_content = self.tokens.get(peek_idx).is_some_and(|(kind, _)| {
2554 matches!(
2555 kind,
2556 SyntaxKind::STRING
2557 | SyntaxKind::INT
2558 | SyntaxKind::FLOAT
2559 | SyntaxKind::BOOL
2560 | SyntaxKind::NULL
2561 | SyntaxKind::UNTERMINATED_STRING
2562 )
2563 });
2564
2565 if !has_content || next_line_indent <= scalar_indent {
2566 return false;
2567 }
2568
2569 if peek_idx > 0 {
2572 let mut check_idx = peek_idx.saturating_sub(1);
2573
2574 while self
2576 .tokens
2577 .get(check_idx)
2578 .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2579 {
2580 if check_idx == 0 {
2581 break;
2582 }
2583 check_idx = check_idx.saturating_sub(1);
2584 }
2585
2586 if self
2588 .tokens
2589 .get(check_idx)
2590 .is_some_and(|(kind, _)| *kind == SyntaxKind::COLON)
2591 {
2592 return false;
2593 }
2594 }
2595
2596 true
2597 }
2598
2599 fn is_at_dedented_position(&self, base_indent: usize) -> bool {
2603 if base_indent == 0 {
2609 self.current_line_indent > 0
2611 } else {
2612 self.current_line_indent < base_indent
2614 }
2615 }
2616
2617 fn skip_whitespace_only_with_dedent_check(&mut self, base_indent: usize) -> bool {
2620 while self.current().is_some() {
2621 match self.current() {
2622 Some(SyntaxKind::WHITESPACE) => {
2623 self.bump();
2624 }
2625 Some(SyntaxKind::NEWLINE) => {
2626 self.bump();
2627 match self.current() {
2629 Some(SyntaxKind::INDENT) => {
2630 if let Some((_, text)) = self.tokens.last() {
2631 if text.len() < base_indent {
2632 return true;
2634 }
2635 if base_indent == 0 && !text.is_empty() {
2636 return true;
2638 }
2639 }
2640 self.bump(); }
2642 Some(SyntaxKind::COMMENT) => {
2643 if base_indent > 0 {
2645 return true;
2647 }
2648 return false;
2650 }
2651 Some(SyntaxKind::WHITESPACE) | Some(SyntaxKind::NEWLINE) => {
2652 }
2654 None => {
2655 return false;
2657 }
2658 _ => {
2659 if base_indent > 0 {
2661 return true; }
2663 return false;
2665 }
2666 }
2667 }
2668 Some(SyntaxKind::INDENT) => {
2669 if let Some((_, text)) = self.tokens.last() {
2671 if text.len() < base_indent {
2672 return true; }
2674 }
2675 self.bump();
2676 }
2677 _ => {
2678 return false;
2680 }
2681 }
2682 }
2683 false
2684 }
2685
2686 fn skip_ws_and_newlines(&mut self) {
2687 self.skip_tokens(&[
2688 SyntaxKind::WHITESPACE,
2689 SyntaxKind::NEWLINE,
2690 SyntaxKind::INDENT,
2691 SyntaxKind::COMMENT,
2692 ]);
2693 }
2694
2695 fn parse_mapping_key_value_pair(&mut self, base_indent: usize) {
2696 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2698
2699 self.builder.start_node(SyntaxKind::KEY.into());
2701
2702 if self.current() == Some(SyntaxKind::ANCHOR) {
2704 self.bump(); self.skip_whitespace();
2706 }
2707
2708 if self.current() == Some(SyntaxKind::MERGE_KEY) {
2709 self.builder.start_node(SyntaxKind::SCALAR.into());
2710 self.bump(); self.builder.finish_node(); } else if self.current() == Some(SyntaxKind::REFERENCE) {
2713 self.parse_alias();
2715 } else if self.current().is_some_and(is_plain_scalar_kind) {
2716 self.builder.start_node(SyntaxKind::SCALAR.into());
2717 self.bump();
2718 while self.current() == Some(SyntaxKind::WHITESPACE)
2722 && self
2723 .upcoming_tokens()
2724 .next()
2725 .is_some_and(is_plain_scalar_kind)
2726 {
2727 self.bump(); self.bump(); }
2730 self.builder.finish_node(); }
2732 self.builder.finish_node(); self.skip_whitespace();
2735
2736 if self.current() == Some(SyntaxKind::COLON) {
2738 self.bump();
2739 self.skip_whitespace();
2740
2741 self.builder.start_node(SyntaxKind::VALUE.into());
2743 let mut has_value = false;
2744 if self.current().is_some()
2745 && self.current() != Some(SyntaxKind::NEWLINE)
2746 && self.current() != Some(SyntaxKind::COMMENT)
2747 {
2748 self.parse_mapping_value();
2750 has_value = true;
2751
2752 if self.current() == Some(SyntaxKind::WHITESPACE) {
2755 self.bump(); }
2757 if self.current() == Some(SyntaxKind::COMMENT) {
2758 self.bump(); }
2760 } else if self.current() == Some(SyntaxKind::COMMENT) {
2761 self.bump(); if self.current() == Some(SyntaxKind::NEWLINE) {
2767 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2770 let indent_level =
2771 self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2772 self.bump(); self.parse_value_with_base_indent(indent_level);
2775 has_value = true;
2776 }
2777 }
2778 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2780 self.skip_ws_and_newlines();
2781 if self.current_line_indent > base_indent {
2782 self.parse_value_with_base_indent(self.current_line_indent);
2785 has_value = true;
2786 } else if self.current_line_indent == base_indent
2787 && self.current() == Some(SyntaxKind::DASH)
2788 {
2789 self.parse_sequence_with_base_indent(base_indent);
2792 has_value = true;
2793 }
2794 }
2798
2799 if !has_value {
2801 self.builder.start_node(SyntaxKind::SCALAR.into());
2802 self.builder.token(SyntaxKind::NULL.into(), "");
2803 self.builder.finish_node();
2804 }
2805
2806 self.builder.finish_node(); } else {
2808 let error_msg = self.create_detailed_error(
2809 "Missing colon in mapping",
2810 "':' after key",
2811 self.current_text(),
2812 );
2813 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2814 }
2815
2816 while self.current() == Some(SyntaxKind::WHITESPACE) {
2821 self.bump();
2822 }
2823
2824 if self.current() == Some(SyntaxKind::NEWLINE) {
2826 self.bump();
2827 }
2828
2829 self.builder.finish_node();
2831 }
2832
2833 fn bump(&mut self) {
2834 if let Some((kind, text)) = self.tokens.pop() {
2835 match kind {
2837 SyntaxKind::INDENT => {
2838 self.current_line_indent = text.len();
2839 }
2840 SyntaxKind::NEWLINE => {
2841 self.current_line_indent = 0;
2843 }
2844 SyntaxKind::DASH => {
2845 self.current_line_indent += text.len();
2846 }
2847 _ => {}
2848 }
2849
2850 self.builder.token(kind.into(), &text);
2851 if self.current_token_index > 0 {
2852 self.current_token_index -= 1;
2853 }
2854 self.error_context.advance(text.len());
2856 }
2857 }
2858
2859 fn current(&self) -> Option<SyntaxKind> {
2860 self.tokens.last().map(|(kind, _)| *kind)
2861 }
2862
2863 fn current_text(&self) -> Option<&str> {
2864 self.tokens.last().map(|(_, text)| text.as_str())
2865 }
2866
2867 fn upcoming_tokens(&self) -> impl Iterator<Item = SyntaxKind> + '_ {
2869 let len = self.tokens.len();
2872 (0..len.saturating_sub(1))
2873 .rev()
2874 .map(move |i| self.tokens[i].0)
2875 }
2876
2877 fn add_error(&mut self, message: String, kind: ParseErrorKind) {
2878 let token_len = self.current_text().map(|s| s.len()).unwrap_or(1);
2880 let positioned_error = self.error_context.create_error(message, token_len, kind);
2881
2882 self.errors.push(positioned_error.message.clone());
2883 self.positioned_errors.push(positioned_error);
2884 }
2885
2886 fn add_error_and_recover(
2888 &mut self,
2889 message: String,
2890 expected: SyntaxKind,
2891 kind: ParseErrorKind,
2892 ) {
2893 self.add_error(message, kind);
2894
2895 let found = self.current();
2897 let strategy = self.error_context.suggest_recovery(expected, found);
2898
2899 match strategy {
2900 RecoveryStrategy::SkipToken => {
2901 if self.current().is_some() {
2903 self.bump();
2904 }
2905 }
2906 RecoveryStrategy::SkipToEndOfLine => {
2907 while self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2909 self.bump();
2910 }
2911 }
2912 RecoveryStrategy::InsertToken(kind) => {
2913 self.builder.token(kind.into(), "");
2915 }
2916 RecoveryStrategy::SyncToSafePoint => {
2917 let sync_point = self
2919 .error_context
2920 .find_sync_point(&self.tokens, self.tokens.len() - self.current_token_index);
2921 let tokens_to_skip = sync_point - (self.tokens.len() - self.current_token_index);
2922 for _ in 0..tokens_to_skip {
2923 if self.current().is_some() {
2924 self.bump();
2925 }
2926 }
2927 }
2928 }
2929 }
2930
2931 fn create_detailed_error(
2933 &self,
2934 base_message: &str,
2935 expected: &str,
2936 found: Option<&str>,
2937 ) -> String {
2938 let mut builder = ErrorBuilder::new(base_message);
2939 builder = builder.expected(expected);
2940
2941 if let Some(found_str) = found {
2942 builder = builder.found(found_str);
2943 } else if let Some(token) = self.current_text() {
2944 builder = builder.found(format!("'{}'", token));
2945 } else {
2946 builder = builder.found("end of input");
2947 }
2948
2949 let context = match self.error_context.current_context() {
2951 ParseContext::Mapping => "in mapping",
2952 ParseContext::Sequence => "in sequence",
2953 ParseContext::FlowMapping => "in flow mapping",
2954 ParseContext::FlowSequence => "in flow sequence",
2955 ParseContext::BlockScalar => "in block scalar",
2956 ParseContext::QuotedString => "in quoted string",
2957 _ => "at document level",
2958 };
2959 builder = builder.context(context);
2960
2961 let suggestion = self.get_error_suggestion(base_message, expected, found);
2963 if let Some(suggestion_text) = suggestion {
2964 builder = builder.suggestion(suggestion_text);
2965 }
2966
2967 builder.build()
2968 }
2969
2970 fn get_error_suggestion(
2972 &self,
2973 base_message: &str,
2974 expected: &str,
2975 found: Option<&str>,
2976 ) -> Option<String> {
2977 if base_message.contains("Unterminated quoted string") {
2978 return Some(
2979 "Add closing quote or check for unescaped quotes within the string".to_string(),
2980 );
2981 }
2982
2983 if base_message.contains("Missing colon") || expected.contains("':'") {
2984 return Some("Add ':' after the key, or check for proper indentation".to_string());
2985 }
2986
2987 if base_message.contains("Unclosed flow sequence") {
2988 return Some(
2989 "Add ']' to close the array, or check for missing commas between elements"
2990 .to_string(),
2991 );
2992 }
2993
2994 if base_message.contains("Unclosed flow mapping") {
2995 return Some(
2996 "Add '}' to close the object, or check for missing commas between key-value pairs"
2997 .to_string(),
2998 );
2999 }
3000
3001 if let Some(found_text) = found {
3002 if found_text.contains('\n') {
3003 return Some(
3004 "Unexpected newline - check indentation and YAML structure".to_string(),
3005 );
3006 }
3007
3008 if found_text.contains('\t') {
3009 return Some(
3010 "Tabs are not allowed in YAML - use spaces for indentation".to_string(),
3011 );
3012 }
3013 }
3014
3015 None
3016 }
3017}
3018
3019pub(crate) fn parse(text: &str) -> ParsedYaml {
3021 let parser = Parser::new(text);
3022 parser.parse()
3023}
3024
3025#[cfg(test)]
3028mod tests {
3029 use super::*;
3030 use crate::builder::{MappingBuilder, SequenceBuilder};
3031 use crate::scalar::ScalarValue;
3032 use crate::value::YamlValue; #[test]
3035 fn test_simple_mapping() {
3036 let yaml = "key: value";
3037 let parsed = YamlFile::from_str(yaml).unwrap();
3038 let doc = parsed.document().unwrap();
3039 let mapping = doc.as_mapping().unwrap();
3040
3041 assert_eq!(parsed.to_string().trim(), "key: value");
3043
3044 let value = mapping.get("key");
3046 assert!(value.is_some());
3047 }
3048
3049 #[test]
3050 fn test_simple_sequence() {
3051 let yaml = "- item1\n- item2";
3052 let parsed = YamlFile::from_str(yaml);
3053 assert!(parsed.is_ok());
3054 }
3055
3056 #[test]
3057 fn test_complex_yaml() {
3058 let yaml = r#"
3059name: my-app
3060version: 1.0.0
3061dependencies:
3062 - serde
3063 - tokio
3064config:
3065 port: 8080
3066 enabled: true
3067"#;
3068 let parsed = YamlFile::from_str(yaml).unwrap();
3069 assert_eq!(parsed.documents().count(), 1);
3070
3071 let doc = parsed.document().unwrap();
3072 assert!(doc.as_mapping().is_some());
3073 }
3074
3075 #[test]
3076 fn test_multiple_documents() {
3077 let yaml = r#"---
3078doc: first
3079---
3080doc: second
3081...
3082"#;
3083 let parsed = YamlFile::from_str(yaml).unwrap();
3084 assert_eq!(parsed.documents().count(), 2);
3085 }
3086
3087 #[test]
3088 fn test_flow_styles() {
3089 let yaml = r#"
3090array: [1, 2, 3]
3091object: {key: value, another: 42}
3092"#;
3093 let parsed = YamlFile::from_str(yaml).unwrap();
3094 assert!(parsed.document().is_some());
3095 }
3096
3097 #[test]
3098 fn test_scalar_types_parsing() {
3099 let yaml = r#"
3100string: hello
3101integer: 42
3102float: 3.14
3103bool_true: true
3104bool_false: false
3105null_value: null
3106tilde: ~
3107"#;
3108 let parsed = YamlFile::from_str(yaml).unwrap();
3109 let doc = parsed.document().unwrap();
3110 let mapping = doc.as_mapping().unwrap();
3111
3112 assert!(mapping.get("string").is_some());
3114 assert!(mapping.get("integer").is_some());
3115 assert!(mapping.get("float").is_some());
3116 assert!(mapping.get("bool_true").is_some());
3117 assert!(mapping.get("bool_false").is_some());
3118 assert!(mapping.get("null_value").is_some());
3119 assert!(mapping.get("tilde").is_some());
3120 }
3121
3122 #[test]
3123 fn test_preserve_formatting() {
3124 let yaml = r#"# Comment at start
3125key: value # inline comment
3126
3127# Another comment
3128list:
3129 - item1
3130 - item2
3131"#;
3132 let parsed = YamlFile::from_str(yaml).unwrap();
3133
3134 let doc = parsed.document().unwrap();
3135 let mapping = doc.as_mapping().unwrap();
3136 assert_eq!(
3137 mapping.get("key").unwrap().as_scalar().unwrap().as_string(),
3138 "value"
3139 );
3140 let list = mapping.get_sequence("list").unwrap();
3141 assert_eq!(list.len(), 2);
3142 let items: Vec<String> = list
3144 .values()
3145 .map(|v| v.to_string().trim().to_string())
3146 .collect();
3147 assert_eq!(items, vec!["item1", "item2"]);
3148
3149 let output = parsed.to_string();
3151 assert_eq!(output, yaml);
3152 }
3153
3154 #[test]
3155 fn test_quoted_strings() {
3156 let yaml = r#"
3157single: 'single quoted'
3158double: "double quoted"
3159plain: unquoted
3160"#;
3161 let parsed = YamlFile::from_str(yaml).unwrap();
3162 let doc = parsed.document().unwrap();
3163 let mapping = doc.as_mapping().unwrap();
3164
3165 assert!(mapping.get("single").is_some());
3166 assert!(mapping.get("double").is_some());
3167 assert!(mapping.get("plain").is_some());
3168 }
3169
3170 #[test]
3171 fn test_nested_structures() {
3172 let yaml = r#"
3173root:
3174 nested:
3175 deeply:
3176 value: 42
3177 list:
3178 - item1
3179 - item2
3180"#;
3181 let parsed = YamlFile::from_str(yaml).unwrap();
3182 assert!(parsed.document().is_some());
3183 }
3184
3185 #[test]
3186 fn test_empty_values() {
3187 let yaml = r#"
3188empty_string: ""
3189empty_after_colon:
3190another_key: value
3191"#;
3192 let parsed = YamlFile::from_str(yaml).unwrap();
3193 let doc = parsed.document().unwrap();
3194 let mapping = doc.as_mapping().unwrap();
3195
3196 assert!(mapping.get("empty_string").is_some());
3197 assert!(mapping.get("another_key").is_some());
3198 }
3199
3200 #[test]
3201 fn test_special_characters() {
3202 let yaml = r#"
3203special: "line1\nline2"
3204unicode: "emoji 😀"
3205escaped: 'it\'s escaped'
3206"#;
3207 let result = YamlFile::from_str(yaml);
3208 assert!(result.is_ok());
3210 }
3211
3212 #[test]
3215 fn test_error_handling() {
3216 let yaml = "key: value\n invalid indentation for key";
3218 let result = YamlFile::from_str(yaml);
3219 let _ = result;
3221 }
3222
3223 #[test]
3226 fn test_anchor_exact_output() {
3227 let yaml = "key: &anchor value\nref: *anchor";
3228 let parsed = YamlFile::from_str(yaml).unwrap();
3229 let output = parsed.to_string();
3230
3231 assert_eq!(output, "key: &anchor value\nref: *anchor");
3233 }
3234
3235 #[test]
3236 fn test_anchor_with_different_value_types() {
3237 let yaml = r#"string_anchor: &str_val "hello"
3238int_anchor: &int_val 42
3239bool_anchor: &bool_val true
3240null_anchor: &null_val null
3241str_ref: *str_val
3242int_ref: *int_val
3243bool_ref: *bool_val
3244null_ref: *null_val"#;
3245
3246 let parsed = YamlFile::from_str(yaml);
3247 assert!(
3248 parsed.is_ok(),
3249 "Should parse anchors with different value types"
3250 );
3251
3252 let yaml_doc = parsed.unwrap();
3253
3254 let doc = yaml_doc.document().unwrap();
3255 let mapping = doc.as_mapping().unwrap();
3256
3257 assert_eq!(
3259 mapping
3260 .get("string_anchor")
3261 .unwrap()
3262 .as_scalar()
3263 .unwrap()
3264 .as_string(),
3265 "hello"
3266 );
3267 assert_eq!(mapping.get("int_anchor").unwrap().to_i64(), Some(42));
3268 assert_eq!(mapping.get("bool_anchor").unwrap().to_bool(), Some(true));
3269 assert!(mapping.get("null_anchor").unwrap().as_scalar().is_some());
3270
3271 let str_ref = mapping.get("str_ref").unwrap();
3273 assert!(str_ref.is_alias());
3274 assert_eq!(str_ref.as_alias().unwrap().name(), "str_val");
3275
3276 let int_ref = mapping.get("int_ref").unwrap();
3277 assert!(int_ref.is_alias());
3278 assert_eq!(int_ref.as_alias().unwrap().name(), "int_val");
3279
3280 let bool_ref = mapping.get("bool_ref").unwrap();
3281 assert!(bool_ref.is_alias());
3282 assert_eq!(bool_ref.as_alias().unwrap().name(), "bool_val");
3283
3284 let null_ref = mapping.get("null_ref").unwrap();
3285 assert!(null_ref.is_alias());
3286 assert_eq!(null_ref.as_alias().unwrap().name(), "null_val");
3287
3288 let output = yaml_doc.to_string();
3290 assert_eq!(output, yaml);
3291 }
3292
3293 #[test]
3294 fn test_undefined_alias_parses_successfully() {
3295 let yaml = "key: *undefined";
3296 let parse_result = Parse::parse_yaml(yaml);
3297
3298 assert!(
3301 !parse_result.has_errors(),
3302 "Parser should not validate undefined aliases"
3303 );
3304
3305 let output = parse_result.tree().to_string();
3307 assert_eq!(output.trim(), "key: *undefined");
3308 }
3309
3310 #[test]
3311 fn test_anchor_names_with_alphanumeric_chars() {
3312 let yaml1 = "key1: &anchor_123 val1\nref1: *anchor_123";
3314 let parsed1 = YamlFile::from_str(yaml1);
3315 assert!(
3316 parsed1.is_ok(),
3317 "Should parse anchors with underscores and numbers"
3318 );
3319
3320 let file1 = parsed1.unwrap();
3321 let doc1 = file1.document().unwrap();
3322 let map1 = doc1.as_mapping().unwrap();
3323 assert_eq!(
3324 map1.get("key1").unwrap().as_scalar().unwrap().as_string(),
3325 "val1"
3326 );
3327 assert!(map1.get("ref1").unwrap().is_alias());
3328 assert_eq!(
3329 map1.get("ref1").unwrap().as_alias().unwrap().name(),
3330 "anchor_123"
3331 );
3332 assert_eq!(file1.to_string(), yaml1);
3333
3334 let yaml2 = "key2: &AnchorName val2\nref2: *AnchorName";
3335 let parsed2 = YamlFile::from_str(yaml2);
3336 assert!(parsed2.is_ok(), "Should parse anchors with mixed case");
3337
3338 let file2 = parsed2.unwrap();
3339 let doc2 = file2.document().unwrap();
3340 let map2 = doc2.as_mapping().unwrap();
3341 assert_eq!(
3342 map2.get("key2").unwrap().as_scalar().unwrap().as_string(),
3343 "val2"
3344 );
3345 assert!(map2.get("ref2").unwrap().is_alias());
3346 assert_eq!(
3347 map2.get("ref2").unwrap().as_alias().unwrap().name(),
3348 "AnchorName"
3349 );
3350 assert_eq!(file2.to_string(), yaml2);
3351
3352 let yaml3 = "key3: &anchor123abc val3\nref3: *anchor123abc";
3353 let parsed3 = YamlFile::from_str(yaml3);
3354 assert!(
3355 parsed3.is_ok(),
3356 "Should parse anchors with letters and numbers"
3357 );
3358
3359 let file3 = parsed3.unwrap();
3360 let doc3 = file3.document().unwrap();
3361 let map3 = doc3.as_mapping().unwrap();
3362 assert_eq!(
3363 map3.get("key3").unwrap().as_scalar().unwrap().as_string(),
3364 "val3"
3365 );
3366 assert!(map3.get("ref3").unwrap().is_alias());
3367 assert_eq!(
3368 map3.get("ref3").unwrap().as_alias().unwrap().name(),
3369 "anchor123abc"
3370 );
3371 assert_eq!(file3.to_string(), yaml3);
3372 }
3373
3374 #[test]
3375 fn test_anchor_in_sequence_detailed() {
3376 let yaml = r#"items:
3377 - &first_item value1
3378 - second_item
3379 - *first_item"#;
3380
3381 let parsed = YamlFile::from_str(yaml);
3382 assert!(parsed.is_ok(), "Should parse anchors in sequences");
3383
3384 let yaml_doc = parsed.unwrap();
3385
3386 let doc = yaml_doc.document().unwrap();
3387 let mapping = doc.as_mapping().unwrap();
3388 let seq = mapping.get_sequence("items").unwrap();
3389 assert_eq!(seq.len(), 3);
3390
3391 let item0 = seq.get(0).unwrap();
3393 let item1 = seq.get(1).unwrap();
3394 let item2 = seq.get(2).unwrap();
3395
3396 assert_eq!(item0.as_scalar().unwrap().as_string(), "value1");
3398
3399 assert_eq!(item1.as_scalar().unwrap().as_string(), "second_item");
3401
3402 assert!(item2.is_alias(), "Third item should be an alias");
3404 assert_eq!(item2.as_alias().unwrap().name(), "first_item");
3405
3406 let output = yaml_doc.to_string();
3407 assert_eq!(output, yaml);
3408 }
3409
3410 #[test]
3411 fn test_preserve_whitespace_around_anchors() {
3412 let yaml = "key: &anchor value \nref: *anchor ";
3413 let parsed = YamlFile::from_str(yaml).unwrap();
3414
3415 let doc = parsed.document().unwrap();
3416 let mapping = doc.as_mapping().unwrap();
3417 assert_eq!(
3418 mapping
3419 .get("key")
3420 .unwrap()
3421 .as_scalar()
3422 .unwrap()
3423 .as_string()
3424 .trim(),
3425 "value"
3426 );
3427 let ref_node = mapping.get("ref").unwrap();
3428 assert!(ref_node.is_alias());
3429 assert_eq!(ref_node.as_alias().unwrap().name(), "anchor");
3430
3431 let output = parsed.to_string();
3433 assert_eq!(output, yaml);
3434 }
3435
3436 #[test]
3437 fn test_literal_block_scalar_basic() {
3438 let yaml = r#"literal: |
3439 Line 1
3440 Line 2
3441 Line 3
3442"#;
3443 let parsed = YamlFile::from_str(yaml);
3444 assert!(parsed.is_ok(), "Should parse basic literal block scalar");
3445
3446 let yaml_doc = parsed.unwrap();
3447 let output = yaml_doc.to_string();
3448
3449 assert_eq!(output, yaml);
3451 }
3452
3453 #[test]
3454 fn test_folded_block_scalar_basic() {
3455 let yaml = r#"folded: >
3456 This is a very long line that will be folded
3457 into a single line in the output
3458 but preserves paragraph breaks.
3459
3460 This is a new paragraph.
3461"#;
3462 let parsed = YamlFile::from_str(yaml);
3463 assert!(parsed.is_ok(), "Should parse basic folded block scalar");
3464
3465 let yaml_doc = parsed.unwrap();
3466 let output = yaml_doc.to_string();
3467
3468 assert_eq!(output, yaml);
3470 }
3471
3472 #[test]
3473 fn test_literal_block_scalar_with_chomping_indicators() {
3474 let yaml1 = r#"strip: |-
3476 Line 1
3477 Line 2
3478
3479"#;
3480 let parsed1 = YamlFile::from_str(yaml1);
3481 assert!(
3482 parsed1.is_ok(),
3483 "Should parse literal block scalar with strip indicator"
3484 );
3485
3486 let file1 = parsed1.unwrap();
3487 let doc1 = file1.document().unwrap();
3488 let mapping1 = doc1.as_mapping().unwrap();
3489 let value1 = mapping1
3490 .get("strip")
3491 .unwrap()
3492 .as_scalar()
3493 .unwrap()
3494 .as_string();
3495 assert_eq!(value1, "Line 1\nLine 2");
3496
3497 let output1 = file1.to_string();
3498 assert_eq!(output1, yaml1);
3499
3500 let yaml2 = r#"keep: |+
3502 Line 1
3503 Line 2
3504
3505"#;
3506 let parsed2 = YamlFile::from_str(yaml2);
3507 assert!(
3508 parsed2.is_ok(),
3509 "Should parse literal block scalar with keep indicator"
3510 );
3511
3512 let file2 = parsed2.unwrap();
3513 let doc2 = file2.document().unwrap();
3514 let mapping2 = doc2.as_mapping().unwrap();
3515 let value2 = mapping2
3516 .get("keep")
3517 .unwrap()
3518 .as_scalar()
3519 .unwrap()
3520 .as_string();
3521 assert_eq!(value2, "Line 1\nLine 2\n\n");
3522
3523 let output2 = file2.to_string();
3524 assert_eq!(output2, yaml2);
3525 }
3526
3527 #[test]
3528 fn test_folded_block_scalar_with_chomping_indicators() {
3529 let yaml1 = r#"strip: >-
3531 Folded content that should
3532 be stripped of final newlines
3533"#;
3534 let parsed1 = YamlFile::from_str(yaml1);
3535 assert!(
3536 parsed1.is_ok(),
3537 "Should parse folded block scalar with strip indicator"
3538 );
3539
3540 let file1 = parsed1.unwrap();
3541 let doc1 = file1.document().unwrap();
3542 let mapping1 = doc1.as_mapping().unwrap();
3543 let value1 = mapping1
3544 .get("strip")
3545 .unwrap()
3546 .as_scalar()
3547 .unwrap()
3548 .as_string();
3549 assert_eq!(
3550 value1,
3551 "Folded content that should be stripped of final newlines"
3552 );
3553
3554 let output1 = file1.to_string();
3555 assert_eq!(output1, yaml1);
3556
3557 let yaml2 = r#"keep: >+
3559 Folded content that should
3560 keep all final newlines
3561
3562"#;
3563 let parsed2 = YamlFile::from_str(yaml2);
3564 assert!(
3565 parsed2.is_ok(),
3566 "Should parse folded block scalar with keep indicator"
3567 );
3568
3569 let file2 = parsed2.unwrap();
3570 let doc2 = file2.document().unwrap();
3571 let mapping2 = doc2.as_mapping().unwrap();
3572 let value2 = mapping2
3573 .get("keep")
3574 .unwrap()
3575 .as_scalar()
3576 .unwrap()
3577 .as_string();
3578 assert_eq!(
3579 value2,
3580 "Folded content that should keep all final newlines\n\n"
3581 );
3582
3583 let output2 = file2.to_string();
3584 assert_eq!(output2, yaml2);
3585 }
3586
3587 #[test]
3588 fn test_block_scalar_with_explicit_indentation() {
3589 let yaml1 = r#"explicit: |2
3590 Two space indent
3591 Another line
3592"#;
3593 let parsed1 = YamlFile::from_str(yaml1)
3594 .expect("Should parse literal block scalar with explicit indentation");
3595
3596 let doc1 = parsed1.document().expect("Should have document");
3597 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3598 let scalar1 = mapping1
3599 .get("explicit")
3600 .expect("Should have 'explicit' key");
3601 assert_eq!(
3602 scalar1.as_scalar().unwrap().as_string(),
3603 "Two space indent\nAnother line\n"
3604 );
3605
3606 let output1 = parsed1.to_string();
3607 assert_eq!(output1, yaml1);
3608
3609 let yaml2 = r#"folded_explicit: >3
3610 Three space indent
3611 Another folded line
3612"#;
3613 let parsed2 = YamlFile::from_str(yaml2)
3614 .expect("Should parse folded block scalar with explicit indentation");
3615
3616 let doc2 = parsed2.document().expect("Should have document");
3617 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
3618 let scalar2 = mapping2
3619 .get("folded_explicit")
3620 .expect("Should have 'folded_explicit' key");
3621 assert_eq!(
3622 scalar2.as_scalar().unwrap().as_string(),
3623 "Three space indent Another folded line\n"
3624 );
3625
3626 let output2 = parsed2.to_string();
3627 assert_eq!(output2, yaml2);
3628 }
3629
3630 #[test]
3631 fn test_block_scalar_in_mapping() {
3632 let yaml = r#"description: |
3633 This is a multi-line
3634 description that should
3635 preserve line breaks.
3636
3637 It can have multiple paragraphs too.
3638
3639summary: >
3640 This is a summary that
3641 should be folded into
3642 a single line.
3643
3644version: "1.0"
3645"#;
3646 let parsed =
3647 YamlFile::from_str(yaml).expect("Should parse block scalars in mapping context");
3648
3649 let doc = parsed.document().expect("Should have document");
3650 let mapping = doc.as_mapping().expect("Should be a mapping");
3651
3652 let description = mapping
3653 .get("description")
3654 .expect("Should have 'description' key");
3655 assert_eq!(
3656 description.as_scalar().unwrap().as_string(),
3657 "This is a multi-line\ndescription that should\npreserve line breaks.\n\nIt can have multiple paragraphs too.\n"
3658 );
3659
3660 let summary = mapping.get("summary").expect("Should have 'summary' key");
3661 assert_eq!(
3662 summary.as_scalar().unwrap().as_string(),
3663 "This is a summary that should be folded into a single line.\n"
3664 );
3665
3666 let version = mapping.get("version").expect("Should have 'version' key");
3667 assert_eq!(version.as_scalar().unwrap().as_string(), "1.0");
3668
3669 let output = parsed.to_string();
3670 assert_eq!(output, yaml);
3671 }
3672
3673 #[test]
3674 fn test_mixed_block_and_regular_scalars() {
3675 let yaml = r#"config:
3676 name: "My App"
3677 description: |
3678 This application does many things:
3679 - Feature 1
3680 - Feature 2
3681 - Feature 3
3682 summary: >
3683 A brief summary that spans
3684 multiple lines but should
3685 be folded together.
3686 version: 1.0
3687 enabled: true
3688"#;
3689 let parsed =
3690 YamlFile::from_str(yaml).expect("Should parse mixed block and regular scalars");
3691
3692 let doc = parsed.document().expect("Should have document");
3693 let mapping = doc.as_mapping().expect("Should be a mapping");
3694 let config_node = mapping.get("config").expect("Should have 'config' key");
3695 let config = config_node
3696 .as_mapping()
3697 .expect("Should be a nested mapping");
3698
3699 assert_eq!(
3700 config.get("name").unwrap().as_scalar().unwrap().as_string(),
3701 "My App"
3702 );
3703 assert_eq!(
3704 config
3705 .get("description")
3706 .unwrap()
3707 .as_scalar()
3708 .unwrap()
3709 .as_string(),
3710 "This application does many things:\n- Feature 1\n- Feature 2\n- Feature 3\n"
3711 );
3712 assert_eq!(
3713 config
3714 .get("summary")
3715 .unwrap()
3716 .as_scalar()
3717 .unwrap()
3718 .as_string(),
3719 "A brief summary that spans multiple lines but should be folded together.\n"
3720 );
3721 assert_eq!(
3722 config
3723 .get("version")
3724 .unwrap()
3725 .as_scalar()
3726 .unwrap()
3727 .as_string(),
3728 "1.0"
3729 );
3730 assert_eq!(config.get("enabled").unwrap().to_bool(), Some(true));
3731
3732 let output = parsed.to_string();
3733 assert_eq!(output, yaml);
3734 }
3735
3736 #[test]
3737 fn test_block_scalar_edge_cases() {
3738 let yaml1 = r#"empty_literal: |
3742empty_folded: >
3743"#;
3744 let parsed1 = YamlFile::from_str(yaml1).expect("Should parse this edge case");
3745
3746 let doc1 = parsed1.document().expect("Should have document");
3748 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3749 assert_eq!(mapping1.len(), 1, "Should have only one key");
3750 assert_eq!(
3751 mapping1
3752 .get("empty_literal")
3753 .unwrap()
3754 .as_scalar()
3755 .unwrap()
3756 .as_string(),
3757 "empty_folded: >\n"
3758 );
3759
3760 assert_eq!(parsed1.to_string(), yaml1);
3761
3762 let yaml2 = r#"whitespace: |
3764
3765
3766"#;
3767 let parsed2 =
3768 YamlFile::from_str(yaml2).expect("Should parse block scalar with only whitespace");
3769
3770 assert_eq!(parsed2.to_string(), yaml2);
3771
3772 let yaml3 = r#"first: |
3774 Content
3775second: value
3776"#;
3777 let parsed3 =
3778 YamlFile::from_str(yaml3).expect("Should parse block scalar followed by other keys");
3779
3780 let doc3 = parsed3.document().expect("Should have document");
3781 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
3782 assert_eq!(
3783 mapping3
3784 .get("first")
3785 .unwrap()
3786 .as_scalar()
3787 .unwrap()
3788 .as_string(),
3789 "Content\n"
3790 );
3791 assert_eq!(
3792 mapping3
3793 .get("second")
3794 .unwrap()
3795 .as_scalar()
3796 .unwrap()
3797 .as_string(),
3798 "value"
3799 );
3800
3801 let output3 = parsed3.to_string();
3802 assert_eq!(output3, yaml3);
3803 }
3804
3805 #[test]
3806 fn test_literal_block_scalar_advanced_formatting() {
3807 let yaml = r#"poem: |
3808 Roses are red,
3809 Violets are blue,
3810 YAML is great,
3811 And so are you!
3812
3813 This is another stanza
3814 with different content.
3815 And this line has extra indentation.
3816 Back to normal indentation.
3817
3818 Final stanza.
3819"#;
3820 let parsed = YamlFile::from_str(yaml).expect("Should parse complex literal block scalar");
3821
3822 let doc = parsed.document().expect("Should have document");
3823 let mapping = doc.as_mapping().expect("Should be a mapping");
3824 let poem = mapping.get("poem").expect("Should have 'poem' key");
3825 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";
3826 assert_eq!(poem.as_scalar().unwrap().as_string(), expected_content);
3827
3828 let output = parsed.to_string();
3829 assert_eq!(output, yaml);
3830 }
3831
3832 #[test]
3833 fn test_folded_block_scalar_paragraph_handling() {
3834 let yaml = r#"description: >
3835 This is the first paragraph that should
3836 be folded into a single line when processed
3837 by a YAML parser.
3838
3839 This is a second paragraph that should
3840 also be folded but kept separate from
3841 the first paragraph.
3842
3843
3844 This is a third paragraph after
3845 multiple blank lines.
3846
3847 Final paragraph.
3848"#;
3849 let parsed =
3850 YamlFile::from_str(yaml).expect("Should parse folded block scalar with paragraphs");
3851
3852 let doc = parsed.document().expect("Should have document");
3853 let mapping = doc.as_mapping().expect("Should be a mapping");
3854 let description = mapping
3855 .get("description")
3856 .expect("Should have 'description' key");
3857 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";
3858 assert_eq!(
3859 description.as_scalar().unwrap().as_string(),
3860 expected_content
3861 );
3862
3863 let output = parsed.to_string();
3864 assert_eq!(output, yaml);
3865 }
3866
3867 #[test]
3868 fn test_block_scalars_with_special_characters() {
3869 let yaml = r#"special_chars: |
3870 Line with colons: key: value
3871 Line with dashes - and more - dashes
3872 Line with quotes "double" and 'single'
3873 Line with brackets [array] and braces {object}
3874 Line with pipes | and greater than >
3875 Line with at @ and hash # symbols
3876 Line with percent % and exclamation !
3877
3878backslash_test: >
3879 This line has a backslash \ in it
3880 And this line has multiple \\ backslashes
3881
3882unicode_test: |
3883 This line has unicode: 你好世界
3884 And emojis: 🚀 🎉 ✨
3885"#;
3886 let parsed =
3887 YamlFile::from_str(yaml).expect("Should parse block scalars with special characters");
3888
3889 let doc = parsed.document().expect("Should have document");
3890 let mapping = doc.as_mapping().expect("Should be a mapping");
3891
3892 let special_chars = mapping
3893 .get("special_chars")
3894 .expect("Should have 'special_chars' key");
3895 assert_eq!(
3896 special_chars.as_scalar().unwrap().as_string(),
3897 "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"
3898 );
3899
3900 let backslash_test = mapping
3901 .get("backslash_test")
3902 .expect("Should have 'backslash_test' key");
3903 assert_eq!(
3904 backslash_test.as_scalar().unwrap().as_string(),
3905 "This line has a backslash \\ in it And this line has multiple \\\\ backslashes\n"
3906 );
3907
3908 let unicode_test = mapping
3909 .get("unicode_test")
3910 .expect("Should have 'unicode_test' key");
3911 assert_eq!(
3912 unicode_test.as_scalar().unwrap().as_string(),
3913 "This line has unicode: 你好世界\nAnd emojis: 🚀 🎉 ✨\n"
3914 );
3915
3916 let output = parsed.to_string();
3917 assert_eq!(output, yaml);
3918 }
3919
3920 #[test]
3921 fn test_block_scalar_chomping_detailed() {
3922 let yaml_clip = r#"clip: |
3924 Line 1
3925 Line 2
3926
3927"#;
3928 let parsed_clip =
3929 YamlFile::from_str(yaml_clip).expect("Should parse block scalar with default clipping");
3930
3931 let doc_clip = parsed_clip.document().expect("Should have document");
3933 let mapping_clip = doc_clip.as_mapping().expect("Should be a mapping");
3934 assert_eq!(
3935 mapping_clip
3936 .get("clip")
3937 .unwrap()
3938 .as_scalar()
3939 .unwrap()
3940 .as_string(),
3941 "Line 1\nLine 2\n"
3942 );
3943
3944 assert_eq!(parsed_clip.to_string(), yaml_clip);
3945
3946 let yaml_strip = r#"strip: |-
3948 Line 1
3949 Line 2
3950
3951
3952
3953"#;
3954 let parsed_strip =
3955 YamlFile::from_str(yaml_strip).expect("Should parse block scalar with strip indicator");
3956
3957 let doc_strip = parsed_strip.document().expect("Should have document");
3959 let mapping_strip = doc_strip.as_mapping().expect("Should be a mapping");
3960 assert_eq!(
3961 mapping_strip
3962 .get("strip")
3963 .unwrap()
3964 .as_scalar()
3965 .unwrap()
3966 .as_string(),
3967 "Line 1\nLine 2"
3968 );
3969
3970 assert_eq!(parsed_strip.to_string(), yaml_strip);
3971
3972 let yaml_keep = r#"keep: |+
3974 Line 1
3975 Line 2
3976
3977
3978
3979"#;
3980 let parsed_keep =
3981 YamlFile::from_str(yaml_keep).expect("Should parse block scalar with keep indicator");
3982
3983 let doc_keep = parsed_keep.document().expect("Should have document");
3985 let mapping_keep = doc_keep.as_mapping().expect("Should be a mapping");
3986 assert_eq!(
3987 mapping_keep
3988 .get("keep")
3989 .unwrap()
3990 .as_scalar()
3991 .unwrap()
3992 .as_string(),
3993 "Line 1\nLine 2\n\n\n\n"
3994 );
3995
3996 assert_eq!(parsed_keep.to_string(), yaml_keep);
3997 }
3998
3999 #[test]
4000 fn test_block_scalar_explicit_indentation_detailed() {
4001 let yaml1 = r#"indent1: |1
4003 Single space indent
4004"#;
4005 let parsed1 = YamlFile::from_str(yaml1);
4006 assert!(parsed1.is_ok(), "Should parse |1 block scalar");
4007 let output1 = parsed1.unwrap().to_string();
4008 assert_eq!(output1, yaml1);
4009
4010 let yaml2 = r#"indent2: |2
4011 Two space indent
4012"#;
4013 let parsed2 = YamlFile::from_str(yaml2);
4014 assert!(parsed2.is_ok(), "Should parse |2 block scalar");
4015 let output2 = parsed2.unwrap().to_string();
4016 assert_eq!(output2, yaml2);
4017
4018 let yaml3 = r#"folded_indent: >2
4019 Two space folded
4020 content spans lines
4021"#;
4022 let parsed3 = YamlFile::from_str(yaml3);
4023 assert!(parsed3.is_ok(), "Should parse >2 folded block scalar");
4024 let output3 = parsed3.unwrap().to_string();
4025 assert_eq!(output3, yaml3);
4026 }
4027
4028 #[test]
4029 fn test_block_scalar_combined_indicators() {
4030 let yaml = r#"strip_with_indent: |2-
4031 Content with explicit indent
4032 and strip chomping
4033
4034
4035keep_with_indent: >3+
4036 Content with explicit indent
4037 and keep chomping
4038
4039
4040
4041folded_strip: >-
4042 Folded content
4043 with strip indicator
4044
4045literal_keep: |+
4046 Literal content
4047 with keep indicator
4048
4049
4050"#;
4051 let parsed =
4052 YamlFile::from_str(yaml).expect("Should parse block scalars with combined indicators");
4053
4054 let doc = parsed.document().expect("Should have document");
4055 let mapping = doc.as_mapping().expect("Should be a mapping");
4056
4057 assert_eq!(
4058 mapping
4059 .get("strip_with_indent")
4060 .unwrap()
4061 .as_scalar()
4062 .unwrap()
4063 .as_string(),
4064 "Content with explicit indent\nand strip chomping"
4065 );
4066 assert_eq!(
4067 mapping
4068 .get("keep_with_indent")
4069 .unwrap()
4070 .as_scalar()
4071 .unwrap()
4072 .as_string(),
4073 "Content with explicit indent and keep chomping\n\n\n\n"
4074 );
4075 assert_eq!(
4076 mapping
4077 .get("folded_strip")
4078 .unwrap()
4079 .as_scalar()
4080 .unwrap()
4081 .as_string(),
4082 "Folded content with strip indicator"
4083 );
4084 assert_eq!(
4085 mapping
4086 .get("literal_keep")
4087 .unwrap()
4088 .as_scalar()
4089 .unwrap()
4090 .as_string(),
4091 "Literal content\nwith keep indicator\n\n\n"
4092 );
4093
4094 let output = parsed.to_string();
4095 assert_eq!(output, yaml);
4096 }
4097
4098 #[test]
4099 fn test_block_scalar_whitespace_and_empty() {
4100 let yaml1 = r#"whitespace_only: |
4102
4103
4104
4105"#;
4106 let parsed1 =
4107 YamlFile::from_str(yaml1).expect("Should handle block scalar with only whitespace");
4108
4109 let doc1 = parsed1.document().expect("Should have document");
4110 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
4111 assert_eq!(
4112 mapping1
4113 .get("whitespace_only")
4114 .unwrap()
4115 .as_scalar()
4116 .unwrap()
4117 .as_string(),
4118 "\n"
4119 );
4120
4121 assert_eq!(parsed1.to_string(), yaml1);
4122
4123 let yaml2 = r#"mixed_indent: |
4125 Normal line
4126 Indented line
4127 Back to normal
4128 More indented
4129 Normal again
4130"#;
4131 let parsed2 = YamlFile::from_str(yaml2).expect("Should handle mixed indentation levels");
4132
4133 let doc2 = parsed2.document().expect("Should have document");
4134 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
4135 assert_eq!(
4136 mapping2
4137 .get("mixed_indent")
4138 .unwrap()
4139 .as_scalar()
4140 .unwrap()
4141 .as_string(),
4142 "Normal line\n Indented line\nBack to normal\n More indented\nNormal again\n"
4143 );
4144
4145 assert_eq!(parsed2.to_string(), yaml2);
4146
4147 let yaml3 = r#"first: |
4149 Content
4150immediate: value
4151another: |
4152 More content
4153final: end
4154"#;
4155 let parsed3 =
4156 YamlFile::from_str(yaml3).expect("Should handle multiple block scalars in mapping");
4157
4158 let doc3 = parsed3.document().expect("Should have document");
4159 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
4160 assert_eq!(
4161 mapping3
4162 .get("first")
4163 .unwrap()
4164 .as_scalar()
4165 .unwrap()
4166 .as_string(),
4167 "Content\n"
4168 );
4169 assert_eq!(
4170 mapping3
4171 .get("immediate")
4172 .unwrap()
4173 .as_scalar()
4174 .unwrap()
4175 .as_string(),
4176 "value"
4177 );
4178 assert_eq!(
4179 mapping3
4180 .get("another")
4181 .unwrap()
4182 .as_scalar()
4183 .unwrap()
4184 .as_string(),
4185 "More content\n"
4186 );
4187 assert_eq!(
4188 mapping3
4189 .get("final")
4190 .unwrap()
4191 .as_scalar()
4192 .unwrap()
4193 .as_string(),
4194 "end"
4195 );
4196
4197 let output3 = parsed3.to_string();
4198 assert_eq!(output3, yaml3);
4199 }
4200
4201 #[test]
4202 fn test_block_scalar_with_comments() {
4203 let yaml = r#"# Main configuration
4204config: | # This is a literal block
4205 # This comment is inside the block
4206 line1: value1
4207 # Another internal comment
4208 line2: value2
4209
4210# Outside comment
4211other: > # Folded block comment
4212 This content spans
4213 # This hash is part of the content, not a comment
4214 multiple lines
4215"#;
4216 let parsed = YamlFile::from_str(yaml).expect("Should parse block scalars with comments");
4217
4218 let doc = parsed.document().expect("Should have document");
4219 let mapping = doc.as_mapping().expect("Should be a mapping");
4220
4221 assert_eq!(
4228 mapping.get("config").unwrap().as_scalar().unwrap().as_string(),
4229 "# This comment is inside the block\nline1: value1\n# Another internal comment\nline2: value2\n\n# Outside comment\n"
4230 );
4231
4232 assert_eq!(
4233 mapping
4234 .get("other")
4235 .unwrap()
4236 .as_scalar()
4237 .unwrap()
4238 .as_string(),
4239 "This content spans # This hash is part of the content, not a comment multiple lines\n"
4240 );
4241
4242 let output = parsed.to_string();
4243 assert_eq!(output, yaml);
4244 }
4245
4246 #[test]
4247 fn test_block_scalar_empty_and_minimal() {
4248 let yaml = r#"empty_literal: |
4249
4250empty_folded: >
4251
4252minimal_literal: |
4253 x
4254
4255minimal_folded: >
4256 y
4257
4258just_newlines: |
4259
4260
4261
4262just_spaces: |
4263
4264
4265
4266"#;
4267 let parsed =
4268 YamlFile::from_str(yaml).expect("Should handle empty and minimal block scalars");
4269
4270 let doc = parsed.document().expect("Should have document");
4271 let mapping = doc.as_mapping().expect("Should be a mapping");
4272
4273 assert_eq!(
4274 mapping
4275 .get("empty_literal")
4276 .unwrap()
4277 .as_scalar()
4278 .unwrap()
4279 .as_string(),
4280 "\n"
4281 );
4282 assert_eq!(
4283 mapping
4284 .get("empty_folded")
4285 .unwrap()
4286 .as_scalar()
4287 .unwrap()
4288 .as_string(),
4289 "\n"
4290 );
4291 assert_eq!(
4292 mapping
4293 .get("minimal_literal")
4294 .unwrap()
4295 .as_scalar()
4296 .unwrap()
4297 .as_string(),
4298 "x\n"
4299 );
4300 assert_eq!(
4301 mapping
4302 .get("minimal_folded")
4303 .unwrap()
4304 .as_scalar()
4305 .unwrap()
4306 .as_string(),
4307 "y\n"
4308 );
4309 assert_eq!(
4310 mapping
4311 .get("just_newlines")
4312 .unwrap()
4313 .as_scalar()
4314 .unwrap()
4315 .as_string(),
4316 "\n"
4317 );
4318 assert_eq!(
4319 mapping
4320 .get("just_spaces")
4321 .unwrap()
4322 .as_scalar()
4323 .unwrap()
4324 .as_string(),
4325 "\n"
4326 );
4327
4328 let output = parsed.to_string();
4329 assert_eq!(output, yaml);
4330 }
4331
4332 #[test]
4333 fn test_block_scalar_with_document_markers() {
4334 let yaml = r#"---
4335doc1: |
4336 This is the first document
4337 with a literal block scalar.
4338
4339next_key: value
4340---
4341doc2: >
4342 This is the second document
4343 with a folded block scalar.
4344
4345another_key: another_value
4346...
4347"#;
4348 let parsed =
4349 YamlFile::from_str(yaml).expect("Should parse block scalars with document markers");
4350
4351 let doc = parsed.document().expect("Should have first document");
4353 let mapping = doc.as_mapping().expect("Should be a mapping");
4354
4355 assert_eq!(
4356 mapping
4357 .get("doc1")
4358 .unwrap()
4359 .as_scalar()
4360 .unwrap()
4361 .as_string(),
4362 "This is the first document\nwith a literal block scalar.\n"
4363 );
4364 assert_eq!(
4365 mapping
4366 .get("next_key")
4367 .unwrap()
4368 .as_scalar()
4369 .unwrap()
4370 .as_string(),
4371 "value"
4372 );
4373
4374 let output = parsed.to_string();
4376 assert_eq!(output, yaml);
4377 }
4378
4379 #[test]
4380 fn test_block_scalar_formatting_preservation() {
4381 let original = r#"preserve_me: |
4382 Line with multiple spaces
4383 Line with tabs here
4384 Line with trailing spaces
4385
4386 Empty line above and below
4387
4388 Final line
4389"#;
4390 let parsed = YamlFile::from_str(original).expect("Should preserve exact formatting");
4391
4392 let doc = parsed.document().expect("Should have document");
4393 let mapping = doc.as_mapping().expect("Should be a mapping");
4394
4395 let preserve_me = mapping
4396 .get("preserve_me")
4397 .expect("Should have 'preserve_me' key");
4398 let expected = "Line with multiple spaces\nLine with\ttabs\there\nLine with trailing spaces\n\nEmpty line above and below\n\nFinal line\n";
4399 assert_eq!(preserve_me.as_scalar().unwrap().as_string(), expected);
4400
4401 let output = parsed.to_string();
4403 assert_eq!(output, original);
4404 }
4405
4406 #[test]
4407 fn test_block_scalar_complex_yaml_content() {
4408 let yaml = r#"yaml_content: |
4409 # This block contains YAML-like content
4410 nested:
4411 - item: value
4412 - item: another
4413
4414 mapping:
4415 key1: |
4416 Even more nested literal content
4417 key2: value
4418
4419 anchors: &anchor
4420 anchor_content: data
4421
4422 reference: *anchor
4423
4424quoted_yaml: >
4425 This folded block contains
4426 YAML structures: {key: value, array: [1, 2, 3]}
4427 that should be treated as plain text.
4428"#;
4429 let parsed = YamlFile::from_str(yaml)
4430 .expect("Should parse block scalars containing YAML-like structures");
4431
4432 let doc = parsed.document().expect("Should have document");
4433 let mapping = doc.as_mapping().expect("Should be a mapping");
4434
4435 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";
4436 assert_eq!(
4437 mapping
4438 .get("yaml_content")
4439 .unwrap()
4440 .as_scalar()
4441 .unwrap()
4442 .as_string(),
4443 expected_yaml_content
4444 );
4445
4446 let expected_quoted_yaml = "This folded block contains YAML structures: {key: value, array: [1, 2, 3]} that should be treated as plain text.\n";
4447 assert_eq!(
4448 mapping
4449 .get("quoted_yaml")
4450 .unwrap()
4451 .as_scalar()
4452 .unwrap()
4453 .as_string(),
4454 expected_quoted_yaml
4455 );
4456
4457 let output = parsed.to_string();
4458 assert_eq!(output, yaml);
4459 }
4460
4461 #[test]
4462 fn test_block_scalar_performance_large_content() {
4463 let mut large_content = String::new();
4465 for i in 1..=100 {
4466 large_content.push_str(&format!(
4467 " Line number {} with some content that makes it longer\n",
4468 i
4469 ));
4470 }
4471
4472 let yaml = format!(
4473 "large_literal: |\n{}\nlarge_folded: >\n{}\n",
4474 large_content, large_content
4475 );
4476
4477 let parsed =
4478 YamlFile::from_str(&yaml).expect("Should parse large block scalars without errors");
4479
4480 let doc = parsed.document().expect("Should have document");
4481 let mapping = doc.as_mapping().expect("Should be a mapping");
4482
4483 let literal_value = mapping
4484 .get("large_literal")
4485 .expect("Should have large_literal key")
4486 .as_scalar()
4487 .expect("Should be scalar")
4488 .as_string();
4489
4490 let mut expected_literal = String::new();
4492 for i in 1..=100 {
4493 expected_literal.push_str(&format!(
4494 "Line number {} with some content that makes it longer\n",
4495 i
4496 ));
4497 }
4498 assert_eq!(literal_value, expected_literal);
4499
4500 let folded_value = mapping
4501 .get("large_folded")
4502 .expect("Should have large_folded key")
4503 .as_scalar()
4504 .expect("Should be scalar")
4505 .as_string();
4506
4507 let mut expected_folded = String::new();
4509 for i in 1..=100 {
4510 if i > 1 {
4511 expected_folded.push(' ');
4512 }
4513 expected_folded.push_str(&format!(
4514 "Line number {} with some content that makes it longer",
4515 i
4516 ));
4517 }
4518 expected_folded.push('\n');
4519 assert_eq!(folded_value, expected_folded);
4520
4521 let output = parsed.to_string();
4522 assert_eq!(output, yaml);
4523 }
4524
4525 #[test]
4526 fn test_block_scalar_error_recovery() {
4527 let yaml = r#"good_key: value
4529bad_block: |
4530incomplete_key
4531another_good: works
4532"#;
4533 let parsed = YamlFile::from_str(yaml).expect("Should parse");
4534
4535 let doc = parsed.document().expect("Should have document");
4536 let mapping = doc.as_mapping().expect("Should be a mapping");
4537
4538 assert_eq!(
4540 mapping
4541 .get("good_key")
4542 .unwrap()
4543 .as_scalar()
4544 .unwrap()
4545 .as_string(),
4546 "value"
4547 );
4548
4549 assert_eq!(
4551 mapping
4552 .get("bad_block")
4553 .unwrap()
4554 .as_scalar()
4555 .unwrap()
4556 .as_string(),
4557 "incomplete_key\n"
4558 );
4559
4560 assert_eq!(
4562 mapping
4563 .get("another_good")
4564 .unwrap()
4565 .as_scalar()
4566 .unwrap()
4567 .as_string(),
4568 "works"
4569 );
4570
4571 let output = parsed.to_string();
4572 assert_eq!(output, yaml);
4573 }
4574
4575 #[test]
4576 fn test_block_scalar_with_flow_structures() {
4577 let yaml = r#"mixed_styles: |
4578 This literal block contains:
4579 - A flow sequence: [1, 2, 3]
4580 - A flow mapping: {key: value, other: data}
4581 - Mixed content: [a, {nested: true}, c]
4582
4583flow_then_block:
4584 flow_seq: [item1, item2]
4585 block_literal: |
4586 This comes after flow style
4587 and should work fine.
4588 flow_map: {after: block}
4589"#;
4590 let parsed = YamlFile::from_str(yaml).expect("Should parse mixed flow and block styles");
4591
4592 let doc = parsed.document().expect("Should have document");
4593 let mapping = doc.as_mapping().expect("Should be a mapping");
4594
4595 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";
4597 assert_eq!(
4598 mapping
4599 .get("mixed_styles")
4600 .unwrap()
4601 .as_scalar()
4602 .unwrap()
4603 .as_string(),
4604 expected_mixed
4605 );
4606
4607 let flow_then_block_value = mapping.get("flow_then_block").unwrap();
4609 let flow_then_block = flow_then_block_value.as_mapping().unwrap();
4610
4611 let flow_seq_value = flow_then_block.get("flow_seq").unwrap();
4613 let flow_seq = flow_seq_value.as_sequence().unwrap();
4614 assert_eq!(flow_seq.len(), 2);
4615 assert_eq!(
4616 flow_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
4617 "item1"
4618 );
4619 assert_eq!(
4620 flow_seq.get(1).unwrap().as_scalar().unwrap().as_string(),
4621 "item2"
4622 );
4623
4624 assert_eq!(
4626 flow_then_block
4627 .get("block_literal")
4628 .unwrap()
4629 .as_scalar()
4630 .unwrap()
4631 .as_string(),
4632 "This comes after flow style\nand should work fine.\n"
4633 );
4634
4635 let flow_map_value = flow_then_block.get("flow_map").unwrap();
4637 let flow_map = flow_map_value.as_mapping().unwrap();
4638 assert_eq!(
4639 flow_map
4640 .get("after")
4641 .unwrap()
4642 .as_scalar()
4643 .unwrap()
4644 .as_string(),
4645 "block"
4646 );
4647
4648 let output = parsed.to_string();
4649 assert_eq!(output, yaml);
4650 }
4651
4652 #[test]
4653 fn test_block_scalar_indentation_edge_cases() {
4654 let yaml1 = r#"empty: |
4656next: value"#;
4657 let parsed1 = YamlFile::from_str(yaml1);
4658 assert!(parsed1.is_ok(), "Should handle empty block followed by key");
4659
4660 let yaml2 = r#"inconsistent: |
4662 normal indent
4663 more indent
4664 back to normal
4665 even more
4666 normal
4667"#;
4668 let parsed2 = YamlFile::from_str(yaml2);
4669 assert!(
4670 parsed2.is_ok(),
4671 "Should handle inconsistent but valid indentation"
4672 );
4673
4674 let yaml3 = "tabs: |\n\tTab indented line\n\tAnother tab line\n";
4676 let parsed3 = YamlFile::from_str(yaml3);
4677 assert!(
4678 parsed3.is_ok(),
4679 "Should handle tab characters in block scalars"
4680 );
4681 }
4682
4683 #[test]
4684 fn test_block_scalar_with_anchors_and_aliases() {
4685 let yaml = r#"template: &template |
4686 This is a template
4687 with multiple lines
4688 that can be referenced.
4689
4690instance1: *template
4691
4692instance2:
4693 content: *template
4694 other: value
4695
4696modified: |
4697 <<: *template
4698 Additional content here
4699"#;
4700 let parsed =
4701 YamlFile::from_str(yaml).expect("Should parse block scalars with anchors and aliases");
4702
4703 let doc = parsed.document().expect("Should have document");
4704 let mapping = doc.as_mapping().expect("Should be a mapping");
4705
4706 let expected_template =
4708 "This is a template\nwith multiple lines\nthat can be referenced.\n";
4709 let template_value = mapping.get("template").unwrap();
4710 assert_eq!(
4711 template_value.as_scalar().unwrap().as_string(),
4712 expected_template
4713 );
4714
4715 let instance1 = mapping.get("instance1").unwrap();
4717 assert!(
4718 instance1.is_alias(),
4719 "instance1 should be an alias, not a scalar"
4720 );
4721 assert_eq!(instance1.as_alias().unwrap().name(), "template");
4722
4723 let instance2_value = mapping.get("instance2").unwrap();
4725 let instance2 = instance2_value.as_mapping().unwrap();
4726
4727 let content = instance2.get("content").unwrap();
4729 assert!(
4730 content.is_alias(),
4731 "content should be an alias, not a scalar"
4732 );
4733 assert_eq!(content.as_alias().unwrap().name(), "template");
4734
4735 assert_eq!(
4737 instance2
4738 .get("other")
4739 .unwrap()
4740 .as_scalar()
4741 .unwrap()
4742 .as_string(),
4743 "value"
4744 );
4745
4746 let modified = mapping.get("modified").unwrap();
4749 assert!(
4750 modified.is_scalar(),
4751 "modified should be a scalar, not an alias"
4752 );
4753 assert_eq!(
4754 modified.as_scalar().unwrap().as_string(),
4755 "<<: *template\nAdditional content here\n"
4756 );
4757
4758 let output = parsed.to_string();
4759 assert_eq!(output, yaml);
4760 }
4761
4762 #[test]
4763 fn test_block_scalar_newline_variations() {
4764 let yaml_unix = "unix: |\n Line 1\n Line 2\n";
4766 let parsed_unix = YamlFile::from_str(yaml_unix).expect("Should handle Unix newlines");
4767
4768 let yaml_windows = "windows: |\r\n Line 1\r\n Line 2\r\n";
4769 let parsed_windows =
4770 YamlFile::from_str(yaml_windows).expect("Should handle Windows newlines");
4771
4772 let doc_unix = parsed_unix.document().expect("Should have document");
4774 let mapping_unix = doc_unix.as_mapping().expect("Should be a mapping");
4775 assert_eq!(
4776 mapping_unix
4777 .get("unix")
4778 .unwrap()
4779 .as_scalar()
4780 .unwrap()
4781 .as_string(),
4782 "Line 1\nLine 2\n"
4783 );
4784
4785 let doc_windows = parsed_windows.document().expect("Should have document");
4787 let mapping_windows = doc_windows.as_mapping().expect("Should be a mapping");
4788 assert_eq!(
4789 mapping_windows
4790 .get("windows")
4791 .unwrap()
4792 .as_scalar()
4793 .unwrap()
4794 .as_string(),
4795 "Line 1\nLine 2\n"
4796 );
4797
4798 assert_eq!(parsed_unix.to_string(), yaml_unix);
4799 assert_eq!(parsed_windows.to_string(), yaml_windows);
4800 }
4801
4802 #[test]
4803 fn test_block_scalar_boundary_detection() {
4804 let yaml = r#"config:
4806 description: |
4807 This is a configuration
4808 with multiple lines.
4809
4810 name: "MyApp"
4811 version: 1.0
4812
4813 settings: >
4814 These are settings that
4815 span multiple lines too.
4816
4817 debug: true
4818"#;
4819 let parsed =
4820 YamlFile::from_str(yaml).expect("Should properly detect block scalar boundaries");
4821
4822 let doc = parsed.document().expect("Should have document");
4823 let mapping = doc.as_mapping().expect("Should be a mapping");
4824 let config_value = mapping.get("config").unwrap();
4825 let config = config_value.as_mapping().unwrap();
4826
4827 assert_eq!(
4829 config
4830 .get("description")
4831 .unwrap()
4832 .as_scalar()
4833 .unwrap()
4834 .as_string(),
4835 "This is a configuration\nwith multiple lines.\n"
4836 );
4837
4838 assert_eq!(
4840 config.get("name").unwrap().as_scalar().unwrap().as_string(),
4841 "MyApp"
4842 );
4843
4844 assert_eq!(
4846 config
4847 .get("version")
4848 .unwrap()
4849 .as_scalar()
4850 .unwrap()
4851 .as_string(),
4852 "1.0"
4853 );
4854
4855 assert_eq!(
4857 config
4858 .get("settings")
4859 .unwrap()
4860 .as_scalar()
4861 .unwrap()
4862 .as_string(),
4863 "These are settings that span multiple lines too.\n"
4864 );
4865
4866 assert_eq!(
4868 config
4869 .get("debug")
4870 .unwrap()
4871 .as_scalar()
4872 .unwrap()
4873 .as_string(),
4874 "true"
4875 );
4876
4877 let output = parsed.to_string();
4878 assert_eq!(output, yaml);
4879 }
4880
4881 #[test]
4882 fn test_block_scalar_with_numeric_content() {
4883 let yaml = r#"numbers_as_text: |
4884 123
4885 45.67
4886 -89
4887 +100
4888 0xFF
4889 1e5
4890 true
4891 false
4892 null
4893
4894calculations: >
4895 The result is: 2 + 2 = 4
4896 And 10 * 5 = 50
4897 Also: 100% complete
4898"#;
4899 let parsed = YamlFile::from_str(yaml)
4900 .expect("Should parse numeric content as text in block scalars");
4901
4902 let doc = parsed.document().expect("Should have document");
4903 let mapping = doc.as_mapping().expect("Should be a mapping");
4904
4905 let expected_numbers = "123\n45.67\n-89\n+100\n0xFF\n1e5\ntrue\nfalse\nnull\n";
4907 assert_eq!(
4908 mapping
4909 .get("numbers_as_text")
4910 .unwrap()
4911 .as_scalar()
4912 .unwrap()
4913 .as_string(),
4914 expected_numbers
4915 );
4916
4917 let expected_calculations =
4919 "The result is: 2 + 2 = 4 And 10 * 5 = 50 Also: 100% complete\n";
4920 assert_eq!(
4921 mapping
4922 .get("calculations")
4923 .unwrap()
4924 .as_scalar()
4925 .unwrap()
4926 .as_string(),
4927 expected_calculations
4928 );
4929
4930 let output = parsed.to_string();
4931 assert_eq!(output, yaml);
4932 }
4933
4934 #[test]
4935 fn test_block_scalar_exact_preservation() {
4936 let test_cases = [
4938 r#"simple: |
4940 Hello World
4941"#,
4942 r#"folded: >
4944 Hello World
4945"#,
4946 r#"strip: |-
4948 Content
4949
4950keep: |+
4951 Content
4952
4953"#,
4954 r#"explicit: |2
4956 Two space indent
4957"#,
4958 r#"config:
4960 script: |
4961 #!/bin/bash
4962 echo "Starting deployment"
4963
4964 for service in api web worker; do
4965 echo "Deploying $service"
4966 kubectl apply -f $service.yaml
4967 done
4968
4969 description: >
4970 This configuration defines a deployment
4971 script that will be executed during
4972 the CI/CD pipeline.
4973"#,
4974 ];
4975
4976 for (i, yaml) in test_cases.iter().enumerate() {
4977 let parsed = YamlFile::from_str(yaml);
4978 assert!(parsed.is_ok(), "Test case {} should parse successfully", i);
4979
4980 let output = parsed.unwrap().to_string();
4981 assert_eq!(
4982 output, *yaml,
4983 "Test case {} should preserve exact formatting",
4984 i
4985 );
4986 }
4987 }
4988
4989 #[test]
4990 fn test_block_scalar_chomping_exact() {
4991 let yaml_strip = r#"strip: |-
4992 Content
4993"#;
4994 let parsed_strip = YamlFile::from_str(yaml_strip).unwrap();
4995 assert_eq!(parsed_strip.to_string(), yaml_strip);
4996
4997 let yaml_keep = r#"keep: |+
4998 Content
4999
5000"#;
5001 let parsed_keep = YamlFile::from_str(yaml_keep).unwrap();
5002 assert_eq!(parsed_keep.to_string(), yaml_keep);
5003
5004 let yaml_folded_strip = r#"folded: >-
5005 Content
5006"#;
5007 let parsed_folded_strip = YamlFile::from_str(yaml_folded_strip).unwrap();
5008 assert_eq!(parsed_folded_strip.to_string(), yaml_folded_strip);
5009 }
5010
5011 #[test]
5012 fn test_block_scalar_indentation_exact() {
5013 let yaml1 = r#"indent1: |1
5014 Single space
5015"#;
5016 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5017 assert_eq!(parsed1.to_string(), yaml1);
5018
5019 let yaml2 = r#"indent2: |2
5020 Two spaces
5021"#;
5022 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5023 assert_eq!(parsed2.to_string(), yaml2);
5024
5025 let yaml3 = r#"combined: |3+
5026 Content with keep
5027
5028"#;
5029 let parsed3 = YamlFile::from_str(yaml3).unwrap();
5030 assert_eq!(parsed3.to_string(), yaml3);
5031 }
5032
5033 #[test]
5034 fn test_block_scalar_mapping_exact() {
5035 let yaml = r#"description: |
5036 Line 1
5037 Line 2
5038
5039summary: >
5040 Folded content
5041
5042version: "1.0"
5043"#;
5044 let parsed = YamlFile::from_str(yaml).unwrap();
5045 assert_eq!(parsed.to_string(), yaml);
5046 }
5047
5048 #[test]
5049 fn test_block_scalar_sequence_exact() {
5050 let yaml = r#"items:
5051 - |
5052 First item content
5053 with multiple lines
5054
5055 - >
5056 Second item folded
5057 content
5058
5059 - regular_item
5060"#;
5061 let parsed = YamlFile::from_str(yaml).unwrap();
5062 assert_eq!(parsed.to_string(), yaml);
5063 }
5064
5065 #[test]
5066 fn test_block_scalar_empty_exact() {
5067 let yaml1 = r#"empty: |
5068
5069"#;
5070 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5071 assert_eq!(parsed1.to_string(), yaml1);
5072
5073 let yaml2 = r#"empty_folded: >
5074
5075"#;
5076 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5077 assert_eq!(parsed2.to_string(), yaml2);
5078 }
5079
5080 #[test]
5081 fn test_empty_documents_in_stream() {
5082 let yaml = "---\n---\nkey: value\n---\n...\n";
5084 let parsed = YamlFile::from_str(yaml).unwrap();
5085 assert_eq!(parsed.documents().count(), 3);
5086 assert_eq!(parsed.to_string(), yaml);
5087 }
5088
5089 #[test]
5090 fn test_mixed_document_end_markers() {
5091 let yaml = "---\nfirst: doc\n...\n---\nsecond: doc\n---\nthird: doc\n...\n";
5093 let parsed = YamlFile::from_str(yaml).unwrap();
5094 assert_eq!(parsed.documents().count(), 3);
5095 assert_eq!(parsed.to_string(), yaml);
5096 }
5097
5098 #[test]
5099 fn test_complex_document_stream() {
5100 let yaml = r#"%YAML 1.2
5101%TAG ! tag:example.com,2000:app/
5102---
5103template: &anchor
5104 key: !custom value
5105instance:
5106 <<: *anchor
5107 extra: data
5108...
5109%YAML 1.2
5110---
5111- item1
5112- item2: nested
5113...
5114---
5115literal: |
5116 Block content
5117 Multiple lines
5118folded: >
5119 Folded content
5120 on multiple lines
5121...
5122"#;
5123 let parsed = YamlFile::from_str(yaml).unwrap();
5124 assert_eq!(parsed.documents().count(), 3);
5125 assert_eq!(parsed.to_string(), yaml);
5126 }
5127
5128 #[test]
5129 fn test_number_format_parsing() {
5130 let yaml = YamlFile::from_str("value: 0b1010").unwrap();
5132 assert_eq!(yaml.to_string().trim(), "value: 0b1010");
5133
5134 let yaml = YamlFile::from_str("value: 0B1111").unwrap();
5135 assert_eq!(yaml.to_string().trim(), "value: 0B1111");
5136
5137 let yaml = YamlFile::from_str("value: 0o755").unwrap();
5139 assert_eq!(yaml.to_string().trim(), "value: 0o755");
5140
5141 let yaml = YamlFile::from_str("value: 0O644").unwrap();
5142 assert_eq!(yaml.to_string().trim(), "value: 0O644");
5143
5144 let yaml = YamlFile::from_str("value: -0b1010").unwrap();
5146 assert_eq!(yaml.to_string().trim(), "value: -0b1010");
5147
5148 let yaml = YamlFile::from_str("value: +0o755").unwrap();
5149 assert_eq!(yaml.to_string().trim(), "value: +0o755");
5150
5151 let yaml = YamlFile::from_str("value: 0755").unwrap();
5153 assert_eq!(yaml.to_string().trim(), "value: 0755");
5154
5155 let yaml = YamlFile::from_str("value: 0xFF").unwrap();
5156 assert_eq!(yaml.to_string().trim(), "value: 0xFF");
5157 }
5158
5159 #[test]
5160 fn test_invalid_number_formats_as_strings() {
5161 let yaml = YamlFile::from_str("value: 0b2").unwrap();
5163 assert_eq!(yaml.to_string().trim(), "value: 0b2");
5164
5165 let yaml = YamlFile::from_str("value: 0o9").unwrap();
5166 assert_eq!(yaml.to_string().trim(), "value: 0o9");
5167
5168 let yaml = YamlFile::from_str("value: 0xGH").unwrap();
5169 assert_eq!(yaml.to_string().trim(), "value: 0xGH");
5170 }
5171
5172 #[test]
5173 fn test_number_formats_in_complex_structures() {
5174 let input = r#"
5175config:
5176 permissions: 0o755
5177 flags: 0b11010
5178 color: 0xFF00FF
5179 count: 42"#;
5180
5181 let yaml = YamlFile::from_str(input).unwrap();
5182
5183 let doc = yaml.document().expect("Should have document");
5184 let mapping = doc.as_mapping().expect("Should be a mapping");
5185 let config_value = mapping.get("config").unwrap();
5186 let config = config_value.as_mapping().unwrap();
5187
5188 assert_eq!(
5189 config
5190 .get("permissions")
5191 .unwrap()
5192 .as_scalar()
5193 .unwrap()
5194 .as_string(),
5195 "0o755"
5196 );
5197 assert_eq!(
5198 config
5199 .get("flags")
5200 .unwrap()
5201 .as_scalar()
5202 .unwrap()
5203 .as_string(),
5204 "0b11010"
5205 );
5206 assert_eq!(
5207 config
5208 .get("color")
5209 .unwrap()
5210 .as_scalar()
5211 .unwrap()
5212 .as_string(),
5213 "0xFF00FF"
5214 );
5215 assert_eq!(
5216 config
5217 .get("count")
5218 .unwrap()
5219 .as_scalar()
5220 .unwrap()
5221 .as_string(),
5222 "42"
5223 );
5224
5225 let output = yaml.to_string();
5226 assert_eq!(output, input);
5227 }
5228
5229 #[test]
5230 fn test_editing_operations() {
5231 let yaml = YamlFile::from_str("name: old-name\nversion: 1.0.0").unwrap();
5233 if let Some(doc) = yaml.document() {
5234 doc.set("name", "new-name");
5235 doc.set("version", "2.0.0");
5236
5237 assert_eq!(doc.get_string("name"), Some("new-name".to_string()));
5239 assert_eq!(doc.get_string("version"), Some("2.0.0".to_string()));
5240
5241 let output = doc.to_string();
5243 assert_eq!(output, "name: new-name\nversion: 2.0.0");
5244 }
5245 }
5246
5247 #[test]
5248 fn test_timestamp_parsing_and_validation() {
5249 use crate::scalar::{ScalarType, ScalarValue};
5250
5251 let test_cases = vec![
5253 ("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), ];
5263
5264 for (timestamp_str, should_be_valid) in test_cases {
5265 let scalar = ScalarValue::parse(timestamp_str);
5266
5267 if should_be_valid {
5268 assert_eq!(
5269 scalar.scalar_type(),
5270 ScalarType::Timestamp,
5271 "Failed to recognize '{}' as timestamp",
5272 timestamp_str
5273 );
5274 assert!(scalar.is_timestamp());
5275
5276 assert_eq!(scalar.value(), timestamp_str);
5278
5279 let yaml = format!("timestamp: {}", timestamp_str);
5281 let parsed = YamlFile::from_str(&yaml).unwrap();
5282
5283 let doc = parsed.document().expect("Should have document");
5284 let mapping = doc.as_mapping().expect("Should be a mapping");
5285 assert_eq!(
5286 mapping
5287 .get("timestamp")
5288 .unwrap()
5289 .as_scalar()
5290 .unwrap()
5291 .as_string(),
5292 timestamp_str,
5293 "Timestamp '{}' not preserved",
5294 timestamp_str
5295 );
5296
5297 let output = parsed.to_string();
5298 assert_eq!(output, yaml);
5299 } else {
5300 assert_ne!(
5301 scalar.scalar_type(),
5302 ScalarType::Timestamp,
5303 "'{}' should not be recognized as timestamp",
5304 timestamp_str
5305 );
5306 }
5307 }
5308
5309 let yaml_with_timestamps = r#"
5311created_at: 2001-12-14 21:59:43.10 -5
5312updated_at: 2001-12-15T02:59:43.1Z
5313date_only: 2002-12-14
5314timestamps_in_array:
5315 - 2001-12-14 21:59:43.10 -5
5316 - 2001-12-15T02:59:43.1Z
5317 - 2002-12-14"#;
5318
5319 let parsed = YamlFile::from_str(yaml_with_timestamps).unwrap();
5320
5321 let doc = parsed.document().expect("Should have document");
5322 let mapping = doc.as_mapping().expect("Should be a mapping");
5323 assert_eq!(
5324 mapping
5325 .get("created_at")
5326 .unwrap()
5327 .as_scalar()
5328 .unwrap()
5329 .as_string(),
5330 "2001-12-14 21:59:43.10 -5"
5331 );
5332 assert_eq!(
5333 mapping
5334 .get("updated_at")
5335 .unwrap()
5336 .as_scalar()
5337 .unwrap()
5338 .as_string(),
5339 "2001-12-15T02:59:43.1Z"
5340 );
5341 assert_eq!(
5342 mapping
5343 .get("date_only")
5344 .unwrap()
5345 .as_scalar()
5346 .unwrap()
5347 .as_string(),
5348 "2002-12-14"
5349 );
5350
5351 let array_value = mapping.get("timestamps_in_array").unwrap();
5352 let array = array_value.as_sequence().unwrap();
5353 assert_eq!(
5354 array.get(0).unwrap().as_scalar().unwrap().as_string(),
5355 "2001-12-14 21:59:43.10 -5"
5356 );
5357 assert_eq!(
5358 array.get(1).unwrap().as_scalar().unwrap().as_string(),
5359 "2001-12-15T02:59:43.1Z"
5360 );
5361 assert_eq!(
5362 array.get(2).unwrap().as_scalar().unwrap().as_string(),
5363 "2002-12-14"
5364 );
5365
5366 let output = parsed.to_string();
5367 assert_eq!(output, yaml_with_timestamps);
5368 }
5369
5370 #[test]
5371 fn test_regex_support_in_yaml() {
5372 use crate::scalar::{ScalarType, ScalarValue};
5373
5374 let yaml_with_regex = r#"
5376patterns:
5377 digits: !!regex '\d+'
5378 word: !!regex '\w+'
5379 simple: !!regex 'test'"#;
5380
5381 let parsed = YamlFile::from_str(yaml_with_regex).unwrap();
5382
5383 let output = parsed.to_string();
5385 assert_eq!(output, yaml_with_regex);
5386
5387 let regex_scalar = ScalarValue::regex(r"^\d{4}-\d{2}-\d{2}$");
5389 assert_eq!(regex_scalar.scalar_type(), ScalarType::Regex);
5390 assert!(regex_scalar.is_regex());
5391 assert_eq!(regex_scalar.value(), r"^\d{4}-\d{2}-\d{2}$");
5392 assert_eq!(
5393 regex_scalar.to_yaml_string(),
5394 r"!!regex ^\d{4}-\d{2}-\d{2}$"
5395 );
5396
5397 let yaml_simple = "pattern: !!regex '\\d+'";
5399 let parsed_simple = YamlFile::from_str(yaml_simple).unwrap();
5400
5401 let doc_simple = parsed_simple.document().expect("Should have document");
5402 let mapping_simple = doc_simple.as_mapping().expect("Should be a mapping");
5403 let pattern_value = mapping_simple
5404 .get("pattern")
5405 .expect("Should have pattern key");
5406
5407 assert!(pattern_value.is_tagged(), "pattern should be a tagged node");
5409 let tagged = pattern_value.as_tagged().expect("Should be tagged");
5410 assert_eq!(tagged.tag(), Some("!!regex".to_string()));
5411 assert_eq!(tagged.as_string(), Some("\\d+".to_string()));
5412
5413 let output_simple = parsed_simple.to_string();
5414 assert_eq!(output_simple, yaml_simple);
5415
5416 let complex_regex = r#"validation: !!regex '^https?://(?:[-\w.])+(?:\:[0-9]+)?'"#;
5418 let parsed_complex = YamlFile::from_str(complex_regex).unwrap();
5419
5420 let doc_complex = parsed_complex.document().expect("Should have document");
5421 let mapping_complex = doc_complex.as_mapping().expect("Should be a mapping");
5422 let validation_value = mapping_complex
5423 .get("validation")
5424 .expect("Should have validation key");
5425
5426 assert!(
5427 validation_value.is_tagged(),
5428 "validation should be a tagged node"
5429 );
5430 let tagged_complex = validation_value.as_tagged().expect("Should be tagged");
5431 assert_eq!(tagged_complex.tag(), Some("!!regex".to_string()));
5432 assert_eq!(
5433 tagged_complex.as_string(),
5434 Some("^https?://(?:[-\\w.])+(?:\\:[0-9]+)?".to_string())
5435 );
5436
5437 let output_complex = parsed_complex.to_string();
5438 assert_eq!(output_complex, complex_regex);
5439 }
5440
5441 #[test]
5442 fn test_regex_in_different_contexts() {
5443 let yaml_sequence = r#"
5445patterns:
5446 - !!regex '\d+'
5447 - !!regex '[a-z]+'
5448 - normal_string
5449 - !!regex '.*@.*\..*'
5450"#;
5451
5452 let parsed_seq = YamlFile::from_str(yaml_sequence).unwrap();
5453
5454 let doc_seq = parsed_seq.document().expect("Should have document");
5455 let mapping_seq = doc_seq.as_mapping().expect("Should be a mapping");
5456 let patterns_value = mapping_seq
5457 .get("patterns")
5458 .expect("Should have patterns key");
5459 let patterns = patterns_value.as_sequence().expect("Should be a sequence");
5460
5461 assert!(patterns.get(0).unwrap().is_tagged());
5463 assert_eq!(
5464 patterns.get(0).unwrap().as_tagged().unwrap().tag(),
5465 Some("!!regex".to_string())
5466 );
5467
5468 assert!(patterns.get(1).unwrap().is_tagged());
5470 assert_eq!(
5471 patterns.get(1).unwrap().as_tagged().unwrap().tag(),
5472 Some("!!regex".to_string())
5473 );
5474
5475 assert!(!patterns.get(2).unwrap().is_tagged());
5477 assert_eq!(
5478 patterns.get(2).unwrap().as_scalar().unwrap().as_string(),
5479 "normal_string"
5480 );
5481
5482 assert!(patterns.get(3).unwrap().is_tagged());
5484 assert_eq!(
5485 patterns.get(3).unwrap().as_tagged().unwrap().tag(),
5486 Some("!!regex".to_string())
5487 );
5488
5489 let output_seq = parsed_seq.to_string();
5490 assert_eq!(output_seq, yaml_sequence);
5491
5492 let yaml_nested = r#"
5494validation:
5495 email: !!regex '[^@]+@[^@]+\.[a-z]+'
5496 phone: !!regex '\d{3}-\d{3}-\d{4}'
5497 config:
5498 debug_pattern: !!regex 'DEBUG:.*'
5499 nested:
5500 deep_pattern: !!regex 'ERROR'
5501"#;
5502
5503 let parsed_nested = YamlFile::from_str(yaml_nested).unwrap();
5504 let output_nested = parsed_nested.to_string();
5506 assert_eq!(output_nested, yaml_nested);
5507
5508 let yaml_mixed = r#"
5510mixed_collection:
5511 - name: "test"
5512 patterns: [!!regex '\d+', !!regex '\w+']
5513 - patterns:
5514 simple: !!regex 'test'
5515 complex: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5516"#;
5517
5518 let parsed_mixed = YamlFile::from_str(yaml_mixed).unwrap();
5519 let output_mixed = parsed_mixed.to_string();
5521 assert_eq!(output_mixed, yaml_mixed);
5522
5523 let yaml_flow =
5525 r#"inline_patterns: {email: !!regex '[^@]+@[^@]+', phone: !!regex '\d{3}-\d{4}'}"#;
5526
5527 let parsed_flow = YamlFile::from_str(yaml_flow).unwrap();
5528 let output_flow = parsed_flow.to_string();
5530 assert_eq!(output_flow, yaml_flow);
5531 }
5532
5533 #[test]
5534 fn test_regex_parsing_edge_cases() {
5535 let yaml_quotes = r#"
5538patterns:
5539 single_quoted: !!regex 'pattern with spaces'
5540 double_quoted: !!regex "pattern_without_escapes"
5541 unquoted: !!regex simple_pattern
5542"#;
5543
5544 let parsed_quotes = YamlFile::from_str(yaml_quotes).unwrap();
5545
5546 let output_quotes = parsed_quotes.to_string();
5547 assert_eq!(output_quotes, yaml_quotes);
5548
5549 let yaml_empty = r#"
5551empty: !!regex ''
5552whitespace: !!regex ' '
5553tabs: !!regex ' '
5554"#;
5555
5556 let parsed_empty =
5557 YamlFile::from_str(yaml_empty).expect("Should parse empty/whitespace regex patterns");
5558
5559 let output_empty = parsed_empty.to_string();
5560 assert_eq!(output_empty, yaml_empty);
5561
5562 let yaml_special = r#"special: !!regex 'pattern_with_underscores_and_123'"#;
5564
5565 let parsed_special = YamlFile::from_str(yaml_special)
5566 .expect("Should parse regex with safe special characters");
5567
5568 let output_special = parsed_special.to_string();
5569 assert_eq!(output_special, yaml_special);
5570
5571 let yaml_verify = r#"test_pattern: !!regex '\d{4}-\d{2}-\d{2}'"#;
5573 let parsed_verify = YamlFile::from_str(yaml_verify).unwrap();
5574
5575 let output_verify = parsed_verify.to_string();
5576 assert_eq!(output_verify, yaml_verify);
5577
5578 let yaml_multiple = r#"
5580patterns:
5581 email: !!regex '^[^\s@]+@[^\s@]+\.[^\s@]+$'
5582 phone: !!regex '^\+?[\d\s\-\(\)]{10,}$'
5583 url: !!regex '^https?://[^\s]+$'
5584 ipv4: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5585 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}$'
5586"#;
5587
5588 let parsed_multiple =
5589 YamlFile::from_str(yaml_multiple).expect("Should parse multiple regex patterns");
5590
5591 let output_multiple = parsed_multiple.to_string();
5593 assert_eq!(output_multiple, yaml_multiple);
5594 }
5595
5596 #[test]
5597 fn test_enhanced_comment_support() {
5598 let yaml1 = r#"flow_seq: [
5603 item1, # comment after item1
5604 item2, # comment after item2
5605 item3 # comment after item3
5606]"#;
5607 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5608 let output1 = parsed1.to_string();
5609
5610 assert_eq!(output1, yaml1);
5611
5612 let yaml2 = r#"flow_map: {
5614 key1: val1, # comment after first pair
5615 key2: val2, # comment after second pair
5616 key3: val3 # comment after third pair
5617}"#;
5618 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5619 let output2 = parsed2.to_string();
5620
5621 assert_eq!(output2, yaml2);
5622
5623 let yaml3 = r#"config:
5625 servers: [
5626 {name: web1, port: 80}, # Web server 1
5627 {name: web2, port: 80}, # Web server 2
5628 {name: db1, port: 5432} # Database server
5629 ] # End servers array"#;
5630 let parsed3 = YamlFile::from_str(yaml3).unwrap();
5631 let output3 = parsed3.to_string();
5632
5633 assert_eq!(output3, yaml3);
5634
5635 let yaml4 = r#"items:
5637 - first # First item comment
5638 - second # Second item comment
5639 # Comment between items
5640 - third # Third item comment"#;
5641 let parsed4 = YamlFile::from_str(yaml4).unwrap();
5642 let output4 = parsed4.to_string();
5643
5644 assert_eq!(output4, yaml4);
5645
5646 for yaml in [yaml1, yaml2, yaml3, yaml4] {
5648 let parsed = YamlFile::from_str(yaml).unwrap();
5649 let output = parsed.to_string();
5650 let reparsed = YamlFile::from_str(&output);
5651 assert!(reparsed.is_ok(), "Round-trip parsing should succeed");
5652 }
5653 }
5654
5655 #[test]
5656 fn test_insert_after_with_sequence() {
5657 let yaml = "name: project\nversion: 1.0.0";
5658 let parsed = YamlFile::from_str(yaml).unwrap();
5659 let doc = parsed.document().expect("Should have a document");
5660
5661 let features = SequenceBuilder::new()
5663 .item("feature1")
5664 .item("feature2")
5665 .item("feature3")
5666 .build_document()
5667 .as_sequence()
5668 .unwrap();
5669 let success = doc.insert_after("name", "features", features);
5670 assert!(success, "insert_after should succeed");
5671
5672 let output = doc.to_string();
5673
5674 let expected = r#"name: project
5676features:
5677 - feature1
5678 - feature2
5679 - feature3
5680version: 1.0.0"#;
5681 assert_eq!(output.trim(), expected);
5682
5683 let reparsed = YamlFile::from_str(&output);
5684 assert!(reparsed.is_ok(), "Output should be valid YAML");
5685 }
5686
5687 #[test]
5688 fn test_insert_before_with_mapping() {
5689 let yaml = "name: project\nversion: 1.0.0";
5690 let parsed = YamlFile::from_str(yaml).unwrap();
5691 let doc = parsed.document().expect("Should have a document");
5692
5693 let database = MappingBuilder::new()
5695 .pair("host", "localhost")
5696 .pair("port", 5432)
5697 .pair("database", "mydb")
5698 .build_document()
5699 .as_mapping()
5700 .unwrap();
5701 let success = doc.insert_before("version", "database", database);
5702 assert!(success, "insert_before should succeed");
5703
5704 let output = doc.to_string();
5705
5706 let expected = r#"name: project
5708database:
5709 host: localhost
5710 port: 5432
5711 database: mydb
5712version: 1.0.0"#;
5713 assert_eq!(output.trim(), expected);
5714
5715 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5717 let reparsed_doc = reparsed.document().expect("Should have document");
5718 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5719
5720 let db_value = reparsed_mapping
5721 .get("database")
5722 .expect("Should have database key");
5723 let db_mapping = db_value.as_mapping().expect("database should be mapping");
5724 assert_eq!(
5725 db_mapping
5726 .get("host")
5727 .unwrap()
5728 .as_scalar()
5729 .unwrap()
5730 .as_string(),
5731 "localhost"
5732 );
5733 assert_eq!(
5734 db_mapping
5735 .get("port")
5736 .unwrap()
5737 .as_scalar()
5738 .unwrap()
5739 .as_string(),
5740 "5432"
5741 );
5742 }
5743
5744 #[test]
5745 fn test_insert_at_index_with_mixed_types() {
5746 let yaml = "name: project";
5747 let parsed = YamlFile::from_str(yaml).unwrap();
5748 let doc = parsed.document().expect("Should have a document");
5749
5750 doc.insert_at_index(1, "version", "1.0.0");
5752 doc.insert_at_index(2, "active", true);
5753 doc.insert_at_index(3, "count", 42);
5754
5755 let features = SequenceBuilder::new()
5756 .item("auth")
5757 .item("logging")
5758 .build_document()
5759 .as_sequence()
5760 .unwrap();
5761 doc.insert_at_index(4, "features", features);
5762
5763 let output = doc.to_string();
5764
5765 let expected = r#"name: project
5766version: 1.0.0
5767active: true
5768count: 42
5769features:
5770- auth
5771- logging"#;
5772 assert_eq!(output.trim(), expected);
5773
5774 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5775 let reparsed_doc = reparsed.document().expect("Should have document");
5776 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5777
5778 assert_eq!(
5779 reparsed_mapping
5780 .get("version")
5781 .unwrap()
5782 .as_scalar()
5783 .unwrap()
5784 .as_string(),
5785 "1.0.0"
5786 );
5787 assert_eq!(
5788 reparsed_mapping.get("active").unwrap().to_bool(),
5789 Some(true)
5790 );
5791 assert_eq!(reparsed_mapping.get("count").unwrap().to_i64(), Some(42));
5792
5793 let features_value = reparsed_mapping.get("features").unwrap();
5794 let features = features_value.as_sequence().unwrap();
5795 assert_eq!(
5796 features.get(0).unwrap().as_scalar().unwrap().as_string(),
5797 "auth"
5798 );
5799 assert_eq!(
5800 features.get(1).unwrap().as_scalar().unwrap().as_string(),
5801 "logging"
5802 );
5803 }
5804
5805 #[test]
5806 fn test_insert_with_null_and_special_scalars() {
5807 let yaml = "name: project";
5808 let parsed = YamlFile::from_str(yaml).unwrap();
5809 let doc = parsed.document().expect("Should have a document");
5810
5811 doc.insert_after("name", "null_value", ScalarValue::null());
5813 doc.insert_after("null_value", "empty_string", "");
5814 doc.insert_after("empty_string", "number", 1.234);
5815 doc.insert_after("number", "boolean", false);
5816
5817 let output = doc.to_string();
5818
5819 let expected = r#"name: project
5820null_value: null
5821empty_string: ''
5822number: 1.234
5823boolean: false"#;
5824 assert_eq!(output.trim(), expected);
5825
5826 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5827 let reparsed_doc = reparsed.document().expect("Should have document");
5828 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5829
5830 assert_eq!(
5831 reparsed_mapping
5832 .get("name")
5833 .unwrap()
5834 .as_scalar()
5835 .unwrap()
5836 .as_string(),
5837 "project"
5838 );
5839 assert!(reparsed_mapping
5840 .get("null_value")
5841 .unwrap()
5842 .as_scalar()
5843 .unwrap()
5844 .is_null());
5845 assert_eq!(
5846 reparsed_mapping
5847 .get("empty_string")
5848 .unwrap()
5849 .as_scalar()
5850 .unwrap()
5851 .as_string(),
5852 ""
5853 );
5854 assert_eq!(
5855 reparsed_mapping.get("number").unwrap().to_f64(),
5856 Some(1.234)
5857 );
5858 assert_eq!(
5859 reparsed_mapping.get("boolean").unwrap().to_bool(),
5860 Some(false)
5861 );
5862 }
5863
5864 #[test]
5865 fn test_insert_ordering_preservation() {
5866 let yaml = "first: 1\nthird: 3\nfifth: 5";
5867 let parsed = YamlFile::from_str(yaml).unwrap();
5868 let doc = parsed.document().expect("Should have a document");
5869
5870 doc.insert_after("first", "second", 2);
5872 doc.insert_before("fifth", "fourth", 4);
5873
5874 let output = doc.to_string();
5875
5876 let expected = r#"first: 1
5878second: 2
5879third: 3
5880fourth: 4
5881fifth: 5"#;
5882 assert_eq!(output.trim(), expected);
5883
5884 let reparsed = YamlFile::from_str(&output);
5885 assert!(reparsed.is_ok(), "Output should be valid YAML");
5886 }
5887
5888 #[test]
5889 fn test_insert_with_yamlvalue_positioning() {
5890 let yaml = "name: project\nversion: 1.0\nactive: true";
5891 let parsed = YamlFile::from_str(yaml).unwrap();
5892 let doc = parsed.document().expect("Should have a document");
5893
5894 let success = doc.insert_after("name", "description", "A sample project");
5898 assert!(success, "Should find string key");
5899
5900 let success = doc.insert_after(1.0, "build", "gradle");
5902 assert!(
5903 !success,
5904 "Should not find numeric key (1.0) when actual key is string 'version'"
5905 );
5906
5907 let success = doc.insert_after(true, "test", "enabled");
5909 assert!(
5910 !success,
5911 "Should not find boolean key (true) when actual key is string 'active'"
5912 );
5913
5914 let bool_string_key = "true";
5916 let success = doc.insert_after(bool_string_key, "test_mode", "development");
5917 assert!(!success, "Should not find 'true' key when value is true");
5918
5919 let output = doc.to_string();
5920
5921 let expected = "name: project\ndescription: A sample project\nversion: 1.0\nactive: true";
5923 assert_eq!(output, expected);
5924 }
5925
5926 #[test]
5927 fn test_insert_complex_nested_structure() {
5928 let yaml = "name: project";
5929 let parsed = YamlFile::from_str(yaml).unwrap();
5930 let doc = parsed.document().expect("Should have a document");
5931
5932 let config = MappingBuilder::new()
5934 .mapping("server", |m| m.pair("host", "0.0.0.0").pair("port", 8080))
5935 .pair("debug", true)
5936 .sequence("features", |s| s.item("api").item("web").item("cli"))
5937 .build_document()
5938 .as_mapping()
5939 .unwrap();
5940
5941 doc.insert_after("name", "config", config);
5942
5943 let output = doc.to_string();
5944
5945 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";
5947 assert_eq!(output, expected);
5948
5949 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5950 let reparsed_doc = reparsed.document().expect("Should have document");
5951 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5952
5953 let config_value = reparsed_mapping
5954 .get("config")
5955 .expect("Should have config key");
5956 let config_mapping = config_value.as_mapping().expect("config should be mapping");
5957
5958 let server_value = config_mapping
5959 .get("server")
5960 .expect("Should have server key");
5961 let server = server_value.as_mapping().expect("server should be mapping");
5962 assert_eq!(
5963 server.get("host").unwrap().as_scalar().unwrap().as_string(),
5964 "0.0.0.0"
5965 );
5966 assert_eq!(server.get("port").unwrap().to_i64(), Some(8080));
5967
5968 assert_eq!(config_mapping.get("debug").unwrap().to_bool(), Some(true));
5969
5970 let features_value = config_mapping
5971 .get("features")
5972 .expect("Should have features key");
5973 let features = features_value
5974 .as_sequence()
5975 .expect("features should be sequence");
5976 assert_eq!(features.len(), 3);
5977 assert_eq!(
5978 features.get(0).unwrap().as_scalar().unwrap().as_string(),
5979 "api"
5980 );
5981 assert_eq!(
5982 features.get(1).unwrap().as_scalar().unwrap().as_string(),
5983 "web"
5984 );
5985 assert_eq!(
5986 features.get(2).unwrap().as_scalar().unwrap().as_string(),
5987 "cli"
5988 );
5989 }
5990
5991 #[test]
5992 fn test_insert_with_yaml_sets() {
5993 let yaml = "name: project";
5994 let parsed = YamlFile::from_str(yaml).unwrap();
5995 let doc = parsed.document().expect("Should have a document");
5996
5997 let mut tags = std::collections::BTreeSet::new();
5999 tags.insert("production".to_string());
6000 tags.insert("database".to_string());
6001 tags.insert("web".to_string());
6002
6003 let yaml_set = YamlValue::from_set(tags);
6004 doc.insert_after("name", "tags", yaml_set);
6005
6006 let output = doc.to_string();
6007
6008 let expected =
6010 "name: project\ntags: !!set\n database: null\n production: null\n web: null\n";
6011 assert_eq!(output, expected);
6012
6013 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6014 let reparsed_doc = reparsed.document().expect("Should have document");
6015 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6016
6017 let tags_value = reparsed_mapping.get("tags").expect("Should have tags key");
6018 assert!(tags_value.is_tagged(), "tags should be tagged");
6019 let tagged = tags_value.as_tagged().expect("Should be tagged node");
6020 assert_eq!(tagged.tag(), Some("!!set".to_string()));
6021
6022 let tags_syntax = tagged.syntax();
6025 let tags_mapping = tags_syntax
6026 .children()
6027 .find_map(Mapping::cast)
6028 .expect("Set should have mapping child");
6029
6030 assert!(tags_mapping
6031 .get("database")
6032 .unwrap()
6033 .as_scalar()
6034 .unwrap()
6035 .is_null());
6036 assert!(tags_mapping
6037 .get("production")
6038 .unwrap()
6039 .as_scalar()
6040 .unwrap()
6041 .is_null());
6042 assert!(tags_mapping
6043 .get("web")
6044 .unwrap()
6045 .as_scalar()
6046 .unwrap()
6047 .is_null());
6048 }
6049
6050 #[test]
6051 fn test_insert_with_ordered_mappings() {
6052 let yaml = "name: project";
6053 let parsed = YamlFile::from_str(yaml).unwrap();
6054 let doc = parsed.document().expect("Should have a document");
6055
6056 let ordered_steps = vec![
6058 ("compile".to_string(), YamlValue::from("gcc main.c")),
6059 ("test".to_string(), YamlValue::from("./a.out test")),
6060 (
6061 "package".to_string(),
6062 YamlValue::from("tar -czf app.tar.gz ."),
6063 ),
6064 ];
6065
6066 let yaml_omap = YamlValue::from_ordered_mapping(ordered_steps);
6067 doc.insert_after("name", "build_steps", yaml_omap);
6068
6069 let output = doc.to_string();
6070
6071 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";
6073 assert_eq!(output, expected);
6074
6075 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6076 let reparsed_doc = reparsed.document().expect("Should have document");
6077 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6078
6079 let steps_value = reparsed_mapping
6080 .get("build_steps")
6081 .expect("Should have build_steps key");
6082 assert!(steps_value.is_tagged(), "build_steps should be tagged");
6083 let tagged = steps_value.as_tagged().expect("Should be tagged node");
6084 assert_eq!(tagged.tag(), Some("!!omap".to_string()));
6085
6086 let steps_syntax = tagged.syntax();
6088 let steps_seq = steps_syntax
6089 .children()
6090 .find_map(Sequence::cast)
6091 .expect("Omap should have sequence child");
6092
6093 assert_eq!(steps_seq.len(), 3);
6094 let compile_value = steps_seq.get(0).unwrap();
6096 let compile_item = compile_value.as_mapping().expect("Should be mapping");
6097 assert_eq!(
6098 compile_item
6099 .get("compile")
6100 .unwrap()
6101 .as_scalar()
6102 .unwrap()
6103 .as_string(),
6104 "gcc main.c"
6105 );
6106
6107 let test_value = steps_seq.get(1).unwrap();
6108 let test_item = test_value.as_mapping().expect("Should be mapping");
6109 assert_eq!(
6110 test_item
6111 .get("test")
6112 .unwrap()
6113 .as_scalar()
6114 .unwrap()
6115 .as_string(),
6116 "./a.out test"
6117 );
6118
6119 let package_value = steps_seq.get(2).unwrap();
6120 let package_item = package_value.as_mapping().expect("Should be mapping");
6121 assert_eq!(
6122 package_item
6123 .get("package")
6124 .unwrap()
6125 .as_scalar()
6126 .unwrap()
6127 .as_string(),
6128 "tar -czf app.tar.gz ."
6129 );
6130 }
6131
6132 #[test]
6133 fn test_insert_with_pairs() {
6134 let yaml = "name: project";
6135 let parsed = YamlFile::from_str(yaml).unwrap();
6136 let doc = parsed.document().expect("Should have a document");
6137
6138 let connection_attempts = vec![
6140 ("server".to_string(), YamlValue::from("primary.db")),
6141 ("server".to_string(), YamlValue::from("secondary.db")), ("server".to_string(), YamlValue::from("tertiary.db")), ("timeout".to_string(), YamlValue::from(30)),
6144 ];
6145
6146 let yaml_pairs = YamlValue::from_pairs(connection_attempts);
6147 doc.insert_after("name", "connections", yaml_pairs);
6148
6149 let output = doc.to_string();
6150
6151 let expected = "name: project\nconnections: !!pairs\n - server: primary.db\n - server: secondary.db\n - server: tertiary.db\n - timeout: 30\n";
6153 assert_eq!(output, expected);
6154
6155 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6156 let reparsed_doc = reparsed.document().expect("Should have document");
6157 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6158
6159 let connections_value = reparsed_mapping
6160 .get("connections")
6161 .expect("Should have connections key");
6162 assert!(
6163 connections_value.is_tagged(),
6164 "connections should be tagged"
6165 );
6166 let tagged = connections_value
6167 .as_tagged()
6168 .expect("Should be tagged node");
6169 assert_eq!(tagged.tag(), Some("!!pairs".to_string()));
6170
6171 let connections_syntax = tagged.syntax();
6173 let connections_seq = connections_syntax
6174 .children()
6175 .find_map(Sequence::cast)
6176 .expect("Pairs should have sequence child");
6177
6178 assert_eq!(connections_seq.len(), 4);
6179
6180 let server1_val = connections_seq.get(0).unwrap();
6181 let server1 = server1_val.as_mapping().expect("Should be mapping");
6182 assert_eq!(
6183 server1
6184 .get("server")
6185 .unwrap()
6186 .as_scalar()
6187 .unwrap()
6188 .as_string(),
6189 "primary.db"
6190 );
6191
6192 let server2_val = connections_seq.get(1).unwrap();
6193 let server2 = server2_val.as_mapping().expect("Should be mapping");
6194 assert_eq!(
6195 server2
6196 .get("server")
6197 .unwrap()
6198 .as_scalar()
6199 .unwrap()
6200 .as_string(),
6201 "secondary.db"
6202 );
6203
6204 let server3_val = connections_seq.get(2).unwrap();
6205 let server3 = server3_val.as_mapping().expect("Should be mapping");
6206 assert_eq!(
6207 server3
6208 .get("server")
6209 .unwrap()
6210 .as_scalar()
6211 .unwrap()
6212 .as_string(),
6213 "tertiary.db"
6214 );
6215
6216 let timeout_val = connections_seq.get(3).unwrap();
6217 let timeout = timeout_val.as_mapping().expect("Should be mapping");
6218 assert_eq!(timeout.get("timeout").unwrap().to_i64(), Some(30));
6219 }
6220
6221 #[test]
6222 fn test_insert_with_empty_collections() {
6223 let yaml1 = "name: project";
6227 let parsed1 = YamlFile::from_str(yaml1).unwrap();
6228 let doc1 = parsed1.document().expect("Should have a document");
6229
6230 let empty_seq_yaml = YamlFile::from_str("[]").unwrap();
6232 let empty_list = empty_seq_yaml.document().unwrap().as_sequence().unwrap();
6233
6234 doc1.insert_after("name", "empty_list", empty_list);
6235 let output1 = doc1.to_string();
6236 assert_eq!(output1, "name: project\nempty_list: []\n");
6237
6238 let yaml2 = "name: project";
6240 let parsed2 = YamlFile::from_str(yaml2).unwrap();
6241 let doc2 = parsed2.document().expect("Should have a document");
6242
6243 let empty_map_yaml = YamlFile::from_str("{}").unwrap();
6245 let empty_map = empty_map_yaml.document().unwrap().as_mapping().unwrap();
6246
6247 doc2.insert_after("name", "empty_map", empty_map);
6248 let output2 = doc2.to_string();
6249 assert_eq!(output2, "name: project\nempty_map: {}\n");
6250
6251 let yaml3 = "name: project";
6253 let parsed3 = YamlFile::from_str(yaml3).unwrap();
6254 let doc3 = parsed3.document().expect("Should have a document");
6255 doc3.insert_after("name", "empty_set", YamlValue::set());
6256 let output3 = doc3.to_string();
6257 assert_eq!(output3, "name: project\nempty_set: !!set {}\n");
6258
6259 assert!(
6261 YamlFile::from_str(&output1).is_ok(),
6262 "Empty sequence output should be valid YAML"
6263 );
6264 assert!(
6265 YamlFile::from_str(&output2).is_ok(),
6266 "Empty mapping output should be valid YAML"
6267 );
6268 assert!(
6269 YamlFile::from_str(&output3).is_ok(),
6270 "Empty set output should be valid YAML"
6271 );
6272 }
6273
6274 #[test]
6275 fn test_insert_with_deeply_nested_sequences() {
6276 let yaml = "name: project";
6277 let parsed = YamlFile::from_str(yaml).unwrap();
6278 let doc = parsed.document().expect("Should have a document");
6279
6280 let nested_data = SequenceBuilder::new()
6282 .item("item1")
6283 .mapping(|m| {
6284 m.sequence("config", |s| s.item("debug").item(true).item(8080))
6285 .pair("name", "service")
6286 })
6287 .item(42)
6288 .build_document()
6289 .as_sequence()
6290 .unwrap();
6291
6292 doc.insert_after("name", "nested_data", nested_data);
6293
6294 let output = doc.to_string();
6295
6296 let expected = "name: project\nnested_data:\n - item1\n - \n config: \n - debug\n - true\n - 8080\n name: service- 42\n";
6297 assert_eq!(output, expected);
6298
6299 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6300 let reparsed_doc = reparsed.document().expect("Should have document");
6301 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6302
6303 let nested_value = reparsed_mapping
6304 .get("nested_data")
6305 .expect("Should have nested_data key");
6306 let nested_seq = nested_value.as_sequence().expect("Should be sequence");
6307 assert_eq!(nested_seq.len(), 2);
6310
6311 assert_eq!(
6313 nested_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6314 "item1"
6315 );
6316
6317 let second_item = nested_seq.get(1).unwrap();
6319 let second_mapping = second_item.as_mapping().expect("Should be mapping");
6320
6321 let config_value = second_mapping.get("config").expect("Should have config");
6322 let config_seq = config_value
6323 .as_sequence()
6324 .expect("config should be sequence");
6325 assert_eq!(config_seq.len(), 3);
6326 assert_eq!(
6327 config_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6328 "debug"
6329 );
6330 assert_eq!(config_seq.get(1).unwrap().to_bool(), Some(true));
6331 assert_eq!(config_seq.get(2).unwrap().to_i64(), Some(8080));
6332
6333 assert_eq!(
6335 second_mapping
6336 .get("name")
6337 .unwrap()
6338 .as_scalar()
6339 .unwrap()
6340 .as_string(),
6341 "service- 42"
6342 );
6343 }
6344
6345 #[test]
6346 fn test_ast_preservation_comments_in_mapping() {
6347 let yaml = r#"---
6349# Header comment
6350key1: value1 # Inline comment 1
6351# Middle comment
6352key2: value2 # Inline comment 2
6353# Footer comment
6354"#;
6355 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6356
6357 if let Some(mapping) = doc.as_mapping() {
6359 mapping.move_after("key1", "new_key", "new_value");
6360 }
6361
6362 let result = doc.to_string();
6363
6364 let expected = r#"---
6365# Header comment
6366key1: value1 # Inline comment 1
6367new_key: new_value
6368# Middle comment
6369key2: value2 # Inline comment 2
6370# Footer comment
6371"#;
6372 assert_eq!(result, expected);
6373 }
6374
6375 #[test]
6376 fn test_ast_preservation_whitespace_in_mapping() {
6377 let yaml = r#"---
6379key1: value1
6380
6381
6382key2: value2
6383"#;
6384 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6385
6386 if let Some(mapping) = doc.as_mapping() {
6388 mapping.move_after("key1", "new_key", "new_value");
6389 }
6390
6391 let result = doc.to_string();
6392
6393 let expected = r#"---
6394key1: value1
6395new_key: new_value
6396
6397
6398key2: value2
6399"#;
6400 assert_eq!(result, expected);
6401 }
6402
6403 #[test]
6404 fn test_ast_preservation_complex_structure() {
6405 let yaml = r#"---
6407# Configuration file
6408database: # Database settings
6409 host: localhost # Default host
6410 port: 5432 # Default port
6411 # Connection pool settings
6412 pool:
6413 min: 1 # Minimum connections
6414 max: 10 # Maximum connections
6415
6416# Server configuration
6417server:
6418 port: 8080 # HTTP port
6419"#;
6420 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6421
6422 eprintln!("===========\n");
6423
6424 if let Some(mapping) = doc.as_mapping() {
6426 mapping.move_after("database", "logging", "debug");
6427 }
6428
6429 let result = doc.to_string();
6430
6431 let expected = r#"---
6433# Configuration file
6434database: # Database settings
6435 host: localhost # Default host
6436 port: 5432 # Default port
6437 # Connection pool settings
6438 pool:
6439 min: 1 # Minimum connections
6440 max: 10 # Maximum connections
6441
6442logging: debug
6443# Server configuration
6444server:
6445 port: 8080 # HTTP port
6446"#;
6447 assert_eq!(result, expected);
6448 }
6449
6450 mod lookahead_tests {
6452 use super::*;
6453 use crate::lex::lex;
6454
6455 fn check_implicit_mapping(yaml: &str) -> bool {
6457 let tokens: Vec<(SyntaxKind, &str)> = lex(yaml);
6458 let kinds: Vec<SyntaxKind> = tokens.iter().rev().map(|(kind, _)| *kind).collect();
6460 has_implicit_mapping_pattern(kinds.into_iter())
6461 }
6462
6463 #[test]
6464 fn test_simple_implicit_mapping() {
6465 assert!(check_implicit_mapping("'key' : value"));
6468 }
6469
6470 #[test]
6471 fn test_simple_value_no_mapping() {
6472 assert!(!check_implicit_mapping("value ,"));
6474 }
6475
6476 #[test]
6477 fn test_value_with_comma() {
6478 assert!(!check_implicit_mapping("value , more"));
6480 }
6481
6482 #[test]
6483 fn test_nested_sequence_as_key() {
6484 assert!(check_implicit_mapping("[a, b]: value"));
6486 }
6487
6488 #[test]
6489 fn test_nested_mapping_not_implicit() {
6490 assert!(!check_implicit_mapping("{a: 1}, b"));
6492 }
6493
6494 #[test]
6495 fn test_deeply_nested_key() {
6496 assert!(check_implicit_mapping("[[a]]: value"));
6498 }
6499
6500 #[test]
6501 fn test_with_whitespace() {
6502 assert!(check_implicit_mapping("'key' : value"));
6504 }
6505
6506 #[test]
6507 fn test_mapping_value_in_sequence() {
6508 assert!(!check_implicit_mapping("a, {key: value}"));
6511 }
6512
6513 #[test]
6514 fn test_multiple_colons() {
6515 assert!(check_implicit_mapping("key1: value1, key2: value2"));
6517 }
6518
6519 #[test]
6520 fn test_url_not_mapping() {
6521 assert!(!check_implicit_mapping("http://example.com"));
6524 }
6525 }
6526
6527 #[test]
6528 fn test_from_str_relaxed_valid() {
6529 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: value\n");
6530 assert!(errors.is_empty());
6531 let doc = yaml_file.document().unwrap();
6532 let mapping = doc.as_mapping().unwrap();
6533 assert_eq!(
6534 mapping.get("key").unwrap().as_scalar().unwrap().to_string(),
6535 "value"
6536 );
6537 }
6538
6539 #[test]
6540 fn test_from_str_relaxed_unclosed_flow_sequence() {
6541 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: [a, b");
6542 assert!(!errors.is_empty());
6543 let doc = yaml_file.document().unwrap();
6545 assert!(doc.as_mapping().is_some());
6546 }
6547
6548 #[test]
6549 fn test_from_str_relaxed_unclosed_flow_mapping() {
6550 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: {a: 1");
6551 assert!(!errors.is_empty());
6552 let doc = yaml_file.document().unwrap();
6553 assert!(doc.as_mapping().is_some());
6554 }
6555
6556 #[test]
6557 fn test_from_str_relaxed_preserves_valid_content() {
6558 let input = "good: value\nbad: [unclosed\nalso_good: 42\n";
6559 let (yaml_file, errors) = YamlFile::from_str_relaxed(input);
6560 assert!(!errors.is_empty());
6561 let doc = yaml_file.document().unwrap();
6562 let mapping = doc.as_mapping().unwrap();
6563 assert_eq!(
6564 mapping
6565 .get("good")
6566 .unwrap()
6567 .as_scalar()
6568 .unwrap()
6569 .to_string(),
6570 "value"
6571 );
6572 }
6573
6574 #[test]
6575 fn test_from_str_relaxed_empty_input() {
6576 let (yaml_file, errors) = YamlFile::from_str_relaxed("");
6577 assert!(errors.is_empty());
6578 assert!(yaml_file.document().is_none());
6579 }
6580
6581 #[test]
6582 fn test_parse_flow_sequence_with_stray_close_brace_does_not_hang() {
6583 let parse = crate::Parse::parse_yaml("[}9[Z");
6586 let _ = parse.tree();
6587 assert!(parse.has_errors());
6588 }
6589
6590 #[test]
6591 fn test_parse_flow_mapping_with_stray_close_bracket_does_not_hang() {
6592 let parse = crate::Parse::parse_yaml("{]");
6594 let _ = parse.tree();
6595 assert!(parse.has_errors());
6596 }
6597
6598 #[test]
6599 fn test_parse_explicit_key_multiline_with_comment_does_not_hang() {
6600 let input = "?\u{18}\0\0\0\0\0\0\n\t#\t\t?\t";
6604 let parse = crate::Parse::parse_yaml(input);
6605 let _ = parse.tree();
6606 }
6607
6608 #[test]
6609 fn test_parse_deeply_nested_flow_mapping_does_not_blow_up() {
6610 let input = "{".repeat(2000);
6613 let parse = crate::Parse::parse_yaml(&input);
6614 let _ = parse.tree();
6615 assert!(parse.has_errors());
6616 }
6617
6618 #[test]
6619 fn test_parse_deeply_nested_flow_sequence_does_not_blow_up() {
6620 let input = "[".repeat(2000);
6621 let parse = crate::Parse::parse_yaml(&input);
6622 let _ = parse.tree();
6623 assert!(parse.has_errors());
6624 }
6625
6626 #[test]
6627 fn test_parse_explicit_key_followed_by_question_in_value_does_not_hang() {
6628 let input = "<<?: ?: \n<<\n ?: : 0\n:@";
6632 let parse = crate::Parse::parse_yaml(input);
6633 let _ = parse.tree();
6634 }
6635
6636 #[test]
6637 fn test_parse_complex_key_mapping_with_stray_close_bracket_does_not_hang() {
6638 let input = "[%:%:]: \n]: \n\n";
6641 let parse = crate::Parse::parse_yaml(input);
6642 let _ = parse.tree();
6643 }
6644
6645 #[test]
6646 fn test_parse_mapping_with_stray_close_brace_does_not_hang() {
6647 let input = "\0:\n\n}:\n\n{";
6652 let parse = crate::Parse::parse_yaml(input);
6653 let _ = parse.tree();
6654 assert!(parse.has_errors());
6655 }
6656}