1use crate::as_yaml::YamlNode;
14use crate::lex::SyntaxKind;
15use crate::nodes::has_child_token;
16use crate::yaml::{Document, Mapping, Scalar, Sequence, SyntaxNode};
17use std::fmt;
18
19pub fn print_tree(node: &SyntaxNode) {
57 print_tree_indent(node, 0);
58}
59
60pub fn print_tree_indent(node: &SyntaxNode, indent: usize) {
62 let mut out = String::new();
63 tree_to_string_indent(node, indent, &mut out);
64 print!("{out}");
65}
66
67pub fn tree_to_string(node: &SyntaxNode) -> String {
85 let mut result = String::new();
86 tree_to_string_indent(node, 0, &mut result);
87 result
88}
89
90fn tree_to_string_indent(node: &SyntaxNode, indent: usize, result: &mut String) {
91 for child in node.children_with_tokens() {
92 let prefix = " ".repeat(indent);
93 match child {
94 rowan::NodeOrToken::Node(n) => {
95 result.push_str(&format!("{}{:?}\n", prefix, n.kind()));
96 tree_to_string_indent(&n, indent + 1, result);
97 }
98 rowan::NodeOrToken::Token(t) => {
99 let text = t.text().replace('\n', "\\n").replace('\r', "\\r");
100 result.push_str(&format!("{}{:?}: {:?}\n", prefix, t.kind(), text));
101 }
102 }
103 }
104}
105
106pub fn print_stats(node: &SyntaxNode) {
122 let mut node_counts = std::collections::HashMap::new();
123 let mut token_counts = std::collections::HashMap::new();
124
125 count_nodes(node, &mut node_counts, &mut token_counts);
126
127 println!("=== Node Counts ===");
128 let mut node_vec: Vec<_> = node_counts.iter().collect();
129 node_vec.sort_by_key(|(_, count)| std::cmp::Reverse(**count));
130 for (kind, count) in node_vec {
131 println!(" {kind:?}: {count}");
132 }
133
134 println!("\n=== Token Counts ===");
135 let mut token_vec: Vec<_> = token_counts.iter().collect();
136 token_vec.sort_by_key(|(_, count)| std::cmp::Reverse(**count));
137 for (kind, count) in token_vec {
138 println!(" {kind:?}: {count}");
139 }
140}
141
142fn count_nodes(
143 node: &SyntaxNode,
144 node_counts: &mut std::collections::HashMap<SyntaxKind, usize>,
145 token_counts: &mut std::collections::HashMap<SyntaxKind, usize>,
146) {
147 *node_counts.entry(node.kind()).or_insert(0) += 1;
148
149 for child in node.children_with_tokens() {
150 match child {
151 rowan::NodeOrToken::Node(n) => {
152 count_nodes(&n, node_counts, token_counts);
153 }
154 rowan::NodeOrToken::Token(t) => {
155 *token_counts.entry(t.kind()).or_insert(0) += 1;
156 }
157 }
158 }
159}
160
161pub fn validate_tree(node: &SyntaxNode) -> Result<(), String> {
190 validate_node(node)?;
191 validate_no_stacked_indents(node)?;
192 validate_no_double_trailing_newline(node)?;
193 Ok(())
194}
195
196pub fn roundtrip_ok(node: &SyntaxNode) -> Result<(), String> {
209 let text = node.to_string();
210 let parse = crate::Parse::parse_yaml(&text);
211 if !parse.positioned_errors().is_empty() {
212 return Err(format!(
213 "re-parse produced {} error(s); first: {}",
214 parse.positioned_errors().len(),
215 parse.positioned_errors()[0].message,
216 ));
217 }
218 let round = parse.tree().to_string();
219 if round != text {
220 return Err(format!(
221 "roundtrip mismatch:\n original: {text:?}\n reparsed: {round:?}",
222 ));
223 }
224 Ok(())
225}
226
227fn validate_node(node: &SyntaxNode) -> Result<(), String> {
228 match node.kind() {
229 SyntaxKind::MAPPING_ENTRY => {
230 let keys: Vec<_> = node
231 .children()
232 .filter(|c| c.kind() == SyntaxKind::KEY)
233 .collect();
234 if keys.len() != 1 {
235 return Err(format!(
236 "MAPPING_ENTRY should have exactly 1 KEY, found {}",
237 keys.len()
238 ));
239 }
240
241 let colons: Vec<_> = node
247 .children_with_tokens()
248 .filter(|c| c.as_token().is_some_and(|t| t.kind() == SyntaxKind::COLON))
249 .collect();
250 let is_explicit_key = has_child_token(node, |k| k == SyntaxKind::QUESTION);
251 let in_flow = is_in_flow_collection(node);
252 let expected_colons = if is_explicit_key || in_flow {
253 0..=1
254 } else {
255 1..=1
256 };
257 if !expected_colons.contains(&colons.len()) {
258 return Err(format!(
259 "MAPPING_ENTRY should have {} COLON(s), found {}",
260 if is_explicit_key || in_flow {
261 "0 or 1"
262 } else {
263 "exactly 1"
264 },
265 colons.len()
266 ));
267 }
268
269 let values: Vec<_> = node
270 .children()
271 .filter(|c| c.kind() == SyntaxKind::VALUE)
272 .collect();
273 if values.len() > 1 {
274 return Err(format!(
275 "MAPPING_ENTRY should have at most 1 VALUE, found {}",
276 values.len()
277 ));
278 }
279
280 if !is_in_flow_collection(node)
291 && !ends_with_implicit_null(node)
292 && !is_last_entry_in_parent(node)
293 && !entry_is_terminated(node)
294 {
295 return Err(format!(
296 "Block-style MAPPING_ENTRY not terminated by a NEWLINE (last token: {:?})",
297 node.last_token().map(|t| t.kind())
298 ));
299 }
300 }
301 SyntaxKind::SEQUENCE_ENTRY
302 if !is_in_flow_collection(node)
303 && !is_last_entry_in_parent(node)
304 && !entry_is_terminated(node) =>
305 {
306 return Err(format!(
307 "Block-style SEQUENCE_ENTRY not terminated by a NEWLINE (last token: {:?})",
308 node.last_token().map(|t| t.kind())
309 ));
310 }
311 _ => {}
312 }
313
314 for child in node.children() {
316 validate_node(&child)?;
317 }
318
319 Ok(())
320}
321
322fn entry_is_terminated(entry: &SyntaxNode) -> bool {
333 if entry
334 .last_token()
335 .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
336 {
337 return true;
338 }
339 let mut sib = entry.next_sibling_or_token();
340 while let Some(el) = sib {
341 match el.as_token().map(|t| t.kind()) {
342 Some(SyntaxKind::NEWLINE) => return true,
343 Some(SyntaxKind::WHITESPACE | SyntaxKind::COMMENT) => {
344 sib = el.next_sibling_or_token();
345 }
346 _ => return false,
347 }
348 }
349 false
350}
351
352fn is_last_entry_in_parent(entry: &SyntaxNode) -> bool {
359 let kind = entry.kind();
363 let mut sib = entry.next_sibling();
364 while let Some(s) = sib {
365 if s.kind() == kind {
366 return false;
367 }
368 sib = s.next_sibling();
369 }
370 true
371}
372
373fn ends_with_implicit_null(entry: &SyntaxNode) -> bool {
381 let Some(last_token) = entry.last_token() else {
382 return false;
383 };
384 last_token.kind() == SyntaxKind::NULL && last_token.text().is_empty()
385}
386
387fn validate_no_stacked_indents(node: &SyntaxNode) -> Result<(), String> {
393 let mut prev_was_indent = false;
394 for child in node.children_with_tokens() {
395 match &child {
396 rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::INDENT => {
397 if prev_was_indent {
398 return Err(format!(
399 "adjacent INDENT tokens inside {:?} -- would render as stacked indentation",
400 node.kind()
401 ));
402 }
403 prev_was_indent = true;
404 }
405 rowan::NodeOrToken::Token(_) => prev_was_indent = false,
406 rowan::NodeOrToken::Node(n) => {
407 prev_was_indent = false;
408 validate_no_stacked_indents(n)?;
409 }
410 }
411 }
412 Ok(())
413}
414
415fn validate_no_double_trailing_newline(node: &SyntaxNode) -> Result<(), String> {
428 let checks_apply = matches!(
429 node.kind(),
430 SyntaxKind::MAPPING_ENTRY | SyntaxKind::SEQUENCE_ENTRY
431 );
432 if checks_apply {
433 let tokens: Vec<_> = node
434 .children_with_tokens()
435 .filter_map(|c| c.into_token())
436 .collect();
437 let last = tokens.len();
438 if last >= 2
439 && tokens[last - 1].kind() == SyntaxKind::NEWLINE
440 && tokens[last - 2].kind() == SyntaxKind::NEWLINE
441 {
442 return Err(format!(
443 "{:?} ends with two NEWLINE tokens -- would render as a stray blank line: {:?}",
444 node.kind(),
445 node.text().to_string(),
446 ));
447 }
448 }
449 for child in node.children() {
450 validate_no_double_trailing_newline(&child)?;
451 }
452 Ok(())
453}
454
455fn is_in_flow_collection(node: &SyntaxNode) -> bool {
456 let mut current = node.parent();
458 while let Some(parent) = current {
459 match parent.kind() {
460 SyntaxKind::MAPPING => {
462 return has_child_token(&parent, |k| k == SyntaxKind::LEFT_BRACE)
463 }
464 SyntaxKind::SEQUENCE => {
465 return has_child_token(&parent, |k| k == SyntaxKind::LEFT_BRACKET)
466 }
467 _ => current = parent.parent(),
468 }
469 }
470 false
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use crate::yaml::YamlFile;
477 use rowan::ast::AstNode;
478 use std::str::FromStr;
479
480 #[test]
481 fn test_print_tree() {
482 let yaml = YamlFile::from_str("name: Alice").unwrap();
483 print_tree(yaml.syntax());
485 }
486
487 #[test]
488 fn test_tree_to_string() {
489 let yaml = YamlFile::from_str("name: Alice").unwrap();
490 let s = tree_to_string(yaml.syntax());
491 let expected = "DOCUMENT\n MAPPING\n MAPPING_ENTRY\n KEY\n SCALAR\n STRING: \"name\"\n COLON: \":\"\n WHITESPACE: \" \"\n VALUE\n SCALAR\n STRING: \"Alice\"\n";
492 assert_eq!(s, expected);
493 }
494
495 #[test]
496 fn test_tree_to_string_sequence() {
497 let yaml = YamlFile::from_str("- item1\n- item2\n").unwrap();
498 let s = tree_to_string(yaml.syntax());
499 assert_eq!(
500 s,
501 concat!(
502 "DOCUMENT\n",
503 " SEQUENCE\n",
504 " SEQUENCE_ENTRY\n",
505 " DASH: \"-\"\n",
506 " WHITESPACE: \" \"\n",
507 " SCALAR\n",
508 " STRING: \"item1\"\n",
509 " NEWLINE: \"\\\\n\"\n",
510 " SEQUENCE_ENTRY\n",
511 " DASH: \"-\"\n",
512 " WHITESPACE: \" \"\n",
513 " SCALAR\n",
514 " STRING: \"item2\"\n",
515 " NEWLINE: \"\\\\n\"\n",
516 )
517 );
518 }
519
520 #[test]
521 fn test_print_stats() {
522 let yaml = YamlFile::from_str("name: Alice\nage: 30\n").unwrap();
523 print_stats(yaml.syntax());
525 }
526
527 #[test]
528 fn test_validate_tree() {
529 let yaml = YamlFile::from_str("name: Alice\nage: 30\n").unwrap();
530 validate_tree(yaml.syntax()).expect("Tree should be valid");
531 }
532}
533
534pub struct PrettyDebug<'a, T>(pub &'a T);
549
550impl<'a> fmt::Display for PrettyDebug<'a, Document> {
551 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
552 writeln!(f, "Document {{")?;
553
554 if let Some(mapping) = self.0.as_mapping() {
555 write_indented(f, &format!("{}", PrettyDebug(&mapping)), 1)?;
556 } else if let Some(sequence) = self.0.as_sequence() {
557 write_indented(f, &format!("{}", PrettyDebug(&sequence)), 1)?;
558 } else if let Some(scalar) = self.0.as_scalar() {
559 writeln!(f, " {}", PrettyDebug(&scalar))?;
560 }
561
562 writeln!(f, "}}")
563 }
564}
565
566impl<'a> fmt::Display for PrettyDebug<'a, Mapping> {
567 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
568 writeln!(f, "Mapping [")?;
569
570 for (key, value) in self.0.iter() {
571 write!(f, " {key:?}: ")?;
572 format_node(f, &value, 1)?;
573 writeln!(f)?;
574 }
575
576 write!(f, "]")
577 }
578}
579
580impl<'a> fmt::Display for PrettyDebug<'a, Sequence> {
581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 writeln!(f, "Sequence [")?;
583
584 for (i, value) in self.0.values().enumerate() {
585 write!(f, " [{i}]: ")?;
586 format_node(f, &value, 1)?;
587 writeln!(f)?;
588 }
589
590 write!(f, "]")
591 }
592}
593
594impl<'a> fmt::Display for PrettyDebug<'a, Scalar> {
595 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596 write!(f, "Scalar({:?})", self.0.value())
597 }
598}
599
600fn write_indented(f: &mut fmt::Formatter<'_>, text: &str, indent: usize) -> fmt::Result {
601 let indent_str = " ".repeat(indent);
602 for line in text.lines() {
603 writeln!(f, "{indent_str}{line}")?;
604 }
605 Ok(())
606}
607
608fn format_node(f: &mut fmt::Formatter<'_>, node: &YamlNode, indent: usize) -> fmt::Result {
609 let indent_str = " ".repeat(indent);
610
611 match node {
612 YamlNode::Scalar(s) => {
613 let sv = crate::scalar::ScalarValue::from_scalar(s);
614 write!(f, "{:?} (type: {:?})", sv.value(), sv.scalar_type())?;
615 }
616 YamlNode::Mapping(m) => {
617 writeln!(f, "Mapping {{")?;
618 for (k, v) in m.iter() {
619 write!(f, "{indent_str} ")?;
620 format_node(f, &k, 0)?;
621 write!(f, ": ")?;
622 format_node(f, &v, indent + 1)?;
623 writeln!(f)?;
624 }
625 write!(f, "{indent_str}}}")?;
626 }
627 YamlNode::Sequence(s) => {
628 writeln!(f, "Sequence [")?;
629 for (i, v) in s.values().enumerate() {
630 write!(f, "{indent_str} [{i}]: ")?;
631 format_node(f, &v, indent + 1)?;
632 writeln!(f)?;
633 }
634 write!(f, "{indent_str}]")?;
635 }
636 YamlNode::Alias(a) => {
637 write!(f, "Alias(*{})", a.name())?;
638 }
639 YamlNode::TaggedNode(t) => {
640 write!(f, "Tagged({:?})", t.tag().unwrap_or_default())?;
641 }
642 }
643
644 Ok(())
645}
646
647pub struct ValueInspector<'a>(pub &'a crate::as_yaml::YamlNode);
668
669impl<'a> fmt::Display for ValueInspector<'a> {
670 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
671 writeln!(f, "Value Inspector:")?;
672 writeln!(f, "===============")?;
673
674 match self.0 {
675 crate::as_yaml::YamlNode::Scalar(s) => {
676 writeln!(f, "Type: Scalar")?;
677 writeln!(f, " Value: {:?}", s.as_string())?;
678
679 writeln!(f, "\nCoercion Capabilities:")?;
680 writeln!(f, " to_i64: {:?}", self.0.to_i64())?;
681 writeln!(f, " to_f64: {:?}", self.0.to_f64())?;
682 writeln!(f, " to_bool: {:?}", self.0.to_bool())?;
683 }
684 crate::as_yaml::YamlNode::Mapping(m) => {
685 writeln!(f, "Type: Mapping")?;
686 writeln!(f, " Size: {} entries", m.len())?;
687 }
688 crate::as_yaml::YamlNode::Sequence(s) => {
689 writeln!(f, "Type: Sequence")?;
690 writeln!(f, " Length: {} items", s.len())?;
691 }
692 crate::as_yaml::YamlNode::Alias(a) => {
693 writeln!(f, "Type: Alias")?;
694 writeln!(f, " Anchor name: {}", a.name())?;
695 }
696 crate::as_yaml::YamlNode::TaggedNode(_) => {
697 writeln!(f, "Type: TaggedNode")?;
698 }
699 }
700
701 Ok(())
702 }
703}
704
705pub struct VisualDiff<'a> {
724 pub before: &'a Document,
726 pub after: &'a Document,
728}
729
730impl<'a> fmt::Display for VisualDiff<'a> {
731 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732 writeln!(f, "Visual Diff:")?;
733 writeln!(f, "============")?;
734
735 writeln!(f, "\nBefore:")?;
736 writeln!(f, "-------")?;
737 for line in self.before.to_string().lines() {
738 writeln!(f, " {line}")?;
739 }
740
741 writeln!(f, "\nAfter:")?;
742 writeln!(f, "------")?;
743 for line in self.after.to_string().lines() {
744 writeln!(f, " {line}")?;
745 }
746
747 writeln!(f, "\nChanges:")?;
748 writeln!(f, "--------")?;
749
750 let before_str = self.before.to_string();
752 let after_str = self.after.to_string();
753 let before_lines: Vec<_> = before_str.lines().collect();
754 let after_lines: Vec<_> = after_str.lines().collect();
755
756 for (i, (b, a)) in before_lines.iter().zip(after_lines.iter()).enumerate() {
757 if b != a {
758 writeln!(f, "Line {}: {:?} -> {:?}", i + 1, b, a)?;
759 }
760 }
761
762 if before_lines.len() > after_lines.len() {
763 writeln!(
764 f,
765 "Removed {} lines",
766 before_lines.len() - after_lines.len()
767 )?;
768 } else if after_lines.len() > before_lines.len() {
769 writeln!(f, "Added {} lines", after_lines.len() - before_lines.len())?;
770 }
771
772 Ok(())
773 }
774}
775
776pub fn graphviz_dot(node: &SyntaxNode) -> String {
793 let mut result = String::from("digraph CST {\n");
794 result.push_str(" node [shape=box, fontname=\"Courier\"];\n");
795 result.push_str(" edge [fontsize=10];\n\n");
796
797 let mut counter = 0;
798 graphviz_node(&mut result, node, None, &mut counter);
799
800 result.push_str("}\n");
801 result
802}
803
804fn graphviz_node(
805 result: &mut String,
806 node: &SyntaxNode,
807 parent_id: Option<usize>,
808 counter: &mut usize,
809) {
810 let node_id = *counter;
811 *counter += 1;
812
813 let label = format!("{:?}", node.kind());
815 result.push_str(&format!(" n{node_id} [label=\"{label}\"];\n"));
816
817 if let Some(pid) = parent_id {
819 result.push_str(&format!(" n{pid} -> n{node_id};\n"));
820 }
821
822 for child in node.children_with_tokens() {
824 match child {
825 rowan::NodeOrToken::Node(n) => {
826 graphviz_node(result, &n, Some(node_id), counter);
827 }
828 rowan::NodeOrToken::Token(t) => {
829 let token_id = *counter;
830 *counter += 1;
831
832 let text = t
833 .text()
834 .replace('\\', "\\\\")
835 .replace('"', "\\\"")
836 .replace('\n', "\\\\n")
837 .replace('\r', "\\\\r");
838 let label = format!("{:?}\\n{:?}", t.kind(), text);
839 result.push_str(&format!(
840 " n{token_id} [label=\"{label}\", shape=ellipse, style=filled, fillcolor=lightgray];\n"
841 ));
842 result.push_str(&format!(" n{node_id} -> n{token_id};\n"));
843 }
844 }
845 }
846}
847
848#[cfg(test)]
849mod new_debug_tests {
850 use super::*;
851 use crate::yaml::YamlFile;
852 use rowan::ast::AstNode;
853 use std::str::FromStr;
854
855 #[test]
856 fn test_pretty_debug_document() {
857 let doc = Document::from_str("name: Alice\nage: 30").unwrap();
858 let output = format!("{}", PrettyDebug(&doc));
859
860 assert_eq!(output.lines().next(), Some("Document {"));
862 assert_eq!(output.lines().last(), Some("}"));
863 }
864
865 #[test]
866 fn test_value_inspector_scalar() {
867 let yaml = YamlFile::from_str("port: 8080").unwrap();
868 let doc = yaml.document().unwrap();
869 let mapping = doc.as_mapping().unwrap();
870 let value = mapping.get("port").unwrap();
871
872 let output = format!("{}", ValueInspector(&value));
873
874 assert_eq!(output.lines().next(), Some("Value Inspector:"));
875 assert_eq!(output.lines().nth(1), Some("==============="));
876 assert_eq!(output.lines().nth(2), Some("Type: Scalar"));
877 }
878
879 #[test]
880 fn test_visual_diff_simple() {
881 let before = Document::from_str("name: Alice").unwrap();
882 let after = Document::from_str("name: Bob").unwrap();
883
884 let diff = VisualDiff {
885 before: &before,
886 after: &after,
887 };
888
889 let output = format!("{}", diff);
890
891 assert_eq!(output.lines().next(), Some("Visual Diff:"));
892 assert_eq!(output.lines().nth(1), Some("============"));
893 }
894
895 #[test]
896 fn test_graphviz_dot_structure() {
897 let yaml = YamlFile::from_str("name: Alice").unwrap();
898 let dot = graphviz_dot(yaml.syntax());
899
900 assert_eq!(dot.lines().next(), Some("digraph CST {"));
901 assert_eq!(dot.lines().last(), Some("}"));
902 }
903
904 #[test]
905 fn test_graphviz_dot_contains_nodes() {
906 let yaml = YamlFile::from_str("key: value").unwrap();
907 let dot = graphviz_dot(yaml.syntax());
908
909 let node_count = dot.lines().filter(|l| l.trim().starts_with("node")).count();
911 assert!(
912 node_count >= 1,
913 "expected at least one node declaration in dot output"
914 );
915 }
916}