1use crate::as_yaml::YamlNode;
14use crate::lex::SyntaxKind;
15use crate::yaml::{Document, Mapping, Scalar, Sequence, SyntaxNode};
16use std::fmt;
17
18pub fn print_tree(node: &SyntaxNode) {
56 print_tree_indent(node, 0);
57}
58
59pub fn print_tree_indent(node: &SyntaxNode, indent: usize) {
61 for child in node.children_with_tokens() {
62 let prefix = " ".repeat(indent);
63 match child {
64 rowan::NodeOrToken::Node(n) => {
65 println!("{}{:?}", prefix, n.kind());
66 print_tree_indent(&n, indent + 1);
67 }
68 rowan::NodeOrToken::Token(t) => {
69 let text = t.text().replace('\n', "\\n").replace('\r', "\\r");
70 println!("{}{:?}: {:?}", prefix, t.kind(), text);
71 }
72 }
73 }
74}
75
76pub fn tree_to_string(node: &SyntaxNode) -> String {
94 let mut result = String::new();
95 tree_to_string_indent(node, 0, &mut result);
96 result
97}
98
99fn tree_to_string_indent(node: &SyntaxNode, indent: usize, result: &mut String) {
100 for child in node.children_with_tokens() {
101 let prefix = " ".repeat(indent);
102 match child {
103 rowan::NodeOrToken::Node(n) => {
104 result.push_str(&format!("{}{:?}\n", prefix, n.kind()));
105 tree_to_string_indent(&n, indent + 1, result);
106 }
107 rowan::NodeOrToken::Token(t) => {
108 let text = t.text().replace('\n', "\\n").replace('\r', "\\r");
109 result.push_str(&format!("{}{:?}: {:?}\n", prefix, t.kind(), text));
110 }
111 }
112 }
113}
114
115pub fn print_stats(node: &SyntaxNode) {
131 let mut node_counts = std::collections::HashMap::new();
132 let mut token_counts = std::collections::HashMap::new();
133
134 count_nodes(node, &mut node_counts, &mut token_counts);
135
136 println!("=== Node Counts ===");
137 let mut node_vec: Vec<_> = node_counts.iter().collect();
138 node_vec.sort_by_key(|(_, count)| std::cmp::Reverse(**count));
139 for (kind, count) in node_vec {
140 println!(" {:?}: {}", kind, count);
141 }
142
143 println!("\n=== Token Counts ===");
144 let mut token_vec: Vec<_> = token_counts.iter().collect();
145 token_vec.sort_by_key(|(_, count)| std::cmp::Reverse(**count));
146 for (kind, count) in token_vec {
147 println!(" {:?}: {}", kind, count);
148 }
149}
150
151fn count_nodes(
152 node: &SyntaxNode,
153 node_counts: &mut std::collections::HashMap<SyntaxKind, usize>,
154 token_counts: &mut std::collections::HashMap<SyntaxKind, usize>,
155) {
156 *node_counts.entry(node.kind()).or_insert(0) += 1;
157
158 for child in node.children_with_tokens() {
159 match child {
160 rowan::NodeOrToken::Node(n) => {
161 count_nodes(&n, node_counts, token_counts);
162 }
163 rowan::NodeOrToken::Token(t) => {
164 *token_counts.entry(t.kind()).or_insert(0) += 1;
165 }
166 }
167 }
168}
169
170pub fn validate_tree(node: &SyntaxNode) -> Result<(), String> {
192 validate_node(node)
193}
194
195fn validate_node(node: &SyntaxNode) -> Result<(), String> {
196 match node.kind() {
197 SyntaxKind::MAPPING_ENTRY => {
198 let keys: Vec<_> = node
199 .children()
200 .filter(|c| c.kind() == SyntaxKind::KEY)
201 .collect();
202 if keys.len() != 1 {
203 return Err(format!(
204 "MAPPING_ENTRY should have exactly 1 KEY, found {}",
205 keys.len()
206 ));
207 }
208
209 let colons: Vec<_> = node
210 .children_with_tokens()
211 .filter(|c| {
212 c.as_token()
213 .map(|t| t.kind() == SyntaxKind::COLON)
214 .unwrap_or(false)
215 })
216 .collect();
217 if colons.len() != 1 {
218 return Err(format!(
219 "MAPPING_ENTRY should have exactly 1 COLON, found {}",
220 colons.len()
221 ));
222 }
223
224 let values: Vec<_> = node
225 .children()
226 .filter(|c| c.kind() == SyntaxKind::VALUE)
227 .collect();
228 if values.len() > 1 {
229 return Err(format!(
230 "MAPPING_ENTRY should have at most 1 VALUE, found {}",
231 values.len()
232 ));
233 }
234
235 if !is_in_flow_collection(node) {
238 if let Some(last_token) = node.last_token() {
239 if last_token.kind() != SyntaxKind::NEWLINE {
240 return Err(format!(
241 "Block-style MAPPING_ENTRY should end with NEWLINE, ends with {:?}",
242 last_token.kind()
243 ));
244 }
245 }
246 }
247 }
248 SyntaxKind::SEQUENCE_ENTRY => {
249 if !is_in_flow_collection(node) {
251 if let Some(last_token) = node.last_token() {
252 if last_token.kind() != SyntaxKind::NEWLINE {
253 return Err(format!(
254 "Block-style SEQUENCE_ENTRY should end with NEWLINE, ends with {:?}",
255 last_token.kind()
256 ));
257 }
258 }
259 }
260 }
261 _ => {}
262 }
263
264 for child in node.children() {
266 validate_node(&child)?;
267 }
268
269 Ok(())
270}
271
272fn is_in_flow_collection(node: &SyntaxNode) -> bool {
273 let mut current = node.parent();
275 while let Some(parent) = current {
276 match parent.kind() {
277 SyntaxKind::MAPPING => {
278 for child in parent.children_with_tokens() {
280 if let Some(token) = child.as_token() {
281 if token.kind() == SyntaxKind::LEFT_BRACE {
282 return true;
283 }
284 }
285 }
286 return false;
287 }
288 SyntaxKind::SEQUENCE => {
289 for child in parent.children_with_tokens() {
291 if let Some(token) = child.as_token() {
292 if token.kind() == SyntaxKind::LEFT_BRACKET {
293 return true;
294 }
295 }
296 }
297 return false;
298 }
299 _ => current = parent.parent(),
300 }
301 }
302 false
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::yaml::YamlFile;
309 use rowan::ast::AstNode;
310 use std::str::FromStr;
311
312 #[test]
313 fn test_print_tree() {
314 let yaml = YamlFile::from_str("name: Alice").unwrap();
315 print_tree(yaml.syntax());
317 }
318
319 #[test]
320 fn test_tree_to_string() {
321 let yaml = YamlFile::from_str("name: Alice").unwrap();
322 let s = tree_to_string(yaml.syntax());
323 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";
324 assert_eq!(s, expected);
325 }
326
327 #[test]
328 fn test_tree_to_string_sequence() {
329 let yaml = YamlFile::from_str("- item1\n- item2\n").unwrap();
330 let s = tree_to_string(yaml.syntax());
331 assert_eq!(
332 s,
333 concat!(
334 "DOCUMENT\n",
335 " SEQUENCE\n",
336 " SEQUENCE_ENTRY\n",
337 " DASH: \"-\"\n",
338 " WHITESPACE: \" \"\n",
339 " SCALAR\n",
340 " STRING: \"item1\"\n",
341 " NEWLINE: \"\\\\n\"\n",
342 " SEQUENCE_ENTRY\n",
343 " DASH: \"-\"\n",
344 " WHITESPACE: \" \"\n",
345 " SCALAR\n",
346 " STRING: \"item2\"\n",
347 " NEWLINE: \"\\\\n\"\n",
348 )
349 );
350 }
351
352 #[test]
353 fn test_print_stats() {
354 let yaml = YamlFile::from_str("name: Alice\nage: 30\n").unwrap();
355 print_stats(yaml.syntax());
357 }
358
359 #[test]
360 fn test_validate_tree() {
361 let yaml = YamlFile::from_str("name: Alice\nage: 30\n").unwrap();
362 validate_tree(yaml.syntax()).expect("Tree should be valid");
363 }
364}
365
366pub struct PrettyDebug<'a, T>(pub &'a T);
381
382impl<'a> fmt::Display for PrettyDebug<'a, Document> {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 writeln!(f, "Document {{")?;
385
386 if let Some(mapping) = self.0.as_mapping() {
387 write_indented(f, &format!("{}", PrettyDebug(&mapping)), 1)?;
388 } else if let Some(sequence) = self.0.as_sequence() {
389 write_indented(f, &format!("{}", PrettyDebug(&sequence)), 1)?;
390 } else if let Some(scalar) = self.0.as_scalar() {
391 writeln!(f, " {}", PrettyDebug(&scalar))?;
392 }
393
394 writeln!(f, "}}")
395 }
396}
397
398impl<'a> fmt::Display for PrettyDebug<'a, Mapping> {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 writeln!(f, "Mapping [")?;
401
402 for (key, value) in self.0.iter() {
403 write!(f, " {:?}: ", key)?;
404 format_node(f, &value, 1)?;
405 writeln!(f)?;
406 }
407
408 write!(f, "]")
409 }
410}
411
412impl<'a> fmt::Display for PrettyDebug<'a, Sequence> {
413 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414 writeln!(f, "Sequence [")?;
415
416 for (i, value) in self.0.values().enumerate() {
417 write!(f, " [{}]: ", i)?;
418 format_node(f, &value, 1)?;
419 writeln!(f)?;
420 }
421
422 write!(f, "]")
423 }
424}
425
426impl<'a> fmt::Display for PrettyDebug<'a, Scalar> {
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 write!(f, "Scalar({:?})", self.0.value())
429 }
430}
431
432fn write_indented(f: &mut fmt::Formatter<'_>, text: &str, indent: usize) -> fmt::Result {
433 let indent_str = " ".repeat(indent);
434 for line in text.lines() {
435 writeln!(f, "{}{}", indent_str, line)?;
436 }
437 Ok(())
438}
439
440fn format_node(f: &mut fmt::Formatter<'_>, node: &YamlNode, indent: usize) -> fmt::Result {
441 let indent_str = " ".repeat(indent);
442
443 match node {
444 YamlNode::Scalar(s) => {
445 let sv = crate::scalar::ScalarValue::from_scalar(s);
446 write!(f, "{:?} (type: {:?})", sv.value(), sv.scalar_type())?;
447 }
448 YamlNode::Mapping(m) => {
449 writeln!(f, "Mapping {{")?;
450 for (k, v) in m.iter() {
451 write!(f, "{} ", indent_str)?;
452 format_node(f, &k, 0)?;
453 write!(f, ": ")?;
454 format_node(f, &v, indent + 1)?;
455 writeln!(f)?;
456 }
457 write!(f, "{}}}", indent_str)?;
458 }
459 YamlNode::Sequence(s) => {
460 writeln!(f, "Sequence [")?;
461 for (i, v) in s.values().enumerate() {
462 write!(f, "{} [{}]: ", indent_str, i)?;
463 format_node(f, &v, indent + 1)?;
464 writeln!(f)?;
465 }
466 write!(f, "{}]", indent_str)?;
467 }
468 YamlNode::Alias(a) => {
469 write!(f, "Alias(*{})", a.name())?;
470 }
471 YamlNode::TaggedNode(t) => {
472 write!(f, "Tagged({:?})", t.tag().unwrap_or_default())?;
473 }
474 }
475
476 Ok(())
477}
478
479pub struct ValueInspector<'a>(pub &'a crate::as_yaml::YamlNode);
500
501impl<'a> fmt::Display for ValueInspector<'a> {
502 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503 writeln!(f, "Value Inspector:")?;
504 writeln!(f, "===============")?;
505
506 match self.0 {
507 crate::as_yaml::YamlNode::Scalar(s) => {
508 writeln!(f, "Type: Scalar")?;
509 writeln!(f, " Value: {:?}", s.as_string())?;
510
511 writeln!(f, "\nCoercion Capabilities:")?;
512 writeln!(f, " to_i64: {:?}", self.0.to_i64())?;
513 writeln!(f, " to_f64: {:?}", self.0.to_f64())?;
514 writeln!(f, " to_bool: {:?}", self.0.to_bool())?;
515 }
516 crate::as_yaml::YamlNode::Mapping(m) => {
517 writeln!(f, "Type: Mapping")?;
518 writeln!(f, " Size: {} entries", m.len())?;
519 }
520 crate::as_yaml::YamlNode::Sequence(s) => {
521 writeln!(f, "Type: Sequence")?;
522 writeln!(f, " Length: {} items", s.len())?;
523 }
524 crate::as_yaml::YamlNode::Alias(a) => {
525 writeln!(f, "Type: Alias")?;
526 writeln!(f, " Anchor name: {}", a.name())?;
527 }
528 crate::as_yaml::YamlNode::TaggedNode(_) => {
529 writeln!(f, "Type: TaggedNode")?;
530 }
531 }
532
533 Ok(())
534 }
535}
536
537pub struct VisualDiff<'a> {
556 pub before: &'a Document,
558 pub after: &'a Document,
560}
561
562impl<'a> fmt::Display for VisualDiff<'a> {
563 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
564 writeln!(f, "Visual Diff:")?;
565 writeln!(f, "============")?;
566
567 writeln!(f, "\nBefore:")?;
568 writeln!(f, "-------")?;
569 for line in self.before.to_string().lines() {
570 writeln!(f, " {}", line)?;
571 }
572
573 writeln!(f, "\nAfter:")?;
574 writeln!(f, "------")?;
575 for line in self.after.to_string().lines() {
576 writeln!(f, " {}", line)?;
577 }
578
579 writeln!(f, "\nChanges:")?;
580 writeln!(f, "--------")?;
581
582 let before_str = self.before.to_string();
584 let after_str = self.after.to_string();
585 let before_lines: Vec<_> = before_str.lines().collect();
586 let after_lines: Vec<_> = after_str.lines().collect();
587
588 for (i, (b, a)) in before_lines.iter().zip(after_lines.iter()).enumerate() {
589 if b != a {
590 writeln!(f, "Line {}: {:?} -> {:?}", i + 1, b, a)?;
591 }
592 }
593
594 if before_lines.len() > after_lines.len() {
595 writeln!(
596 f,
597 "Removed {} lines",
598 before_lines.len() - after_lines.len()
599 )?;
600 } else if after_lines.len() > before_lines.len() {
601 writeln!(f, "Added {} lines", after_lines.len() - before_lines.len())?;
602 }
603
604 Ok(())
605 }
606}
607
608pub fn graphviz_dot(node: &SyntaxNode) -> String {
625 let mut result = String::from("digraph CST {\n");
626 result.push_str(" node [shape=box, fontname=\"Courier\"];\n");
627 result.push_str(" edge [fontsize=10];\n\n");
628
629 let mut counter = 0;
630 graphviz_node(&mut result, node, None, &mut counter);
631
632 result.push_str("}\n");
633 result
634}
635
636fn graphviz_node(
637 result: &mut String,
638 node: &SyntaxNode,
639 parent_id: Option<usize>,
640 counter: &mut usize,
641) {
642 let node_id = *counter;
643 *counter += 1;
644
645 let label = format!("{:?}", node.kind());
647 result.push_str(&format!(" n{} [label=\"{}\"];\n", node_id, label));
648
649 if let Some(pid) = parent_id {
651 result.push_str(&format!(" n{} -> n{};\n", pid, node_id));
652 }
653
654 for child in node.children_with_tokens() {
656 match child {
657 rowan::NodeOrToken::Node(n) => {
658 graphviz_node(result, &n, Some(node_id), counter);
659 }
660 rowan::NodeOrToken::Token(t) => {
661 let token_id = *counter;
662 *counter += 1;
663
664 let text = t
665 .text()
666 .replace('\\', "\\\\")
667 .replace('"', "\\\"")
668 .replace('\n', "\\\\n")
669 .replace('\r', "\\\\r");
670 let label = format!("{:?}\\n{:?}", t.kind(), text);
671 result.push_str(&format!(
672 " n{} [label=\"{}\", shape=ellipse, style=filled, fillcolor=lightgray];\n",
673 token_id, label
674 ));
675 result.push_str(&format!(" n{} -> n{};\n", node_id, token_id));
676 }
677 }
678 }
679}
680
681#[cfg(test)]
682mod new_debug_tests {
683 use super::*;
684 use crate::yaml::YamlFile;
685 use rowan::ast::AstNode;
686 use std::str::FromStr;
687
688 #[test]
689 fn test_pretty_debug_document() {
690 let doc = Document::from_str("name: Alice\nage: 30").unwrap();
691 let output = format!("{}", PrettyDebug(&doc));
692
693 assert_eq!(output.lines().next(), Some("Document {"));
695 assert_eq!(output.lines().last(), Some("}"));
696 }
697
698 #[test]
699 fn test_value_inspector_scalar() {
700 let yaml = YamlFile::from_str("port: 8080").unwrap();
701 let doc = yaml.document().unwrap();
702 let mapping = doc.as_mapping().unwrap();
703 let value = mapping.get("port").unwrap();
704
705 let output = format!("{}", ValueInspector(&value));
706
707 assert_eq!(output.lines().next(), Some("Value Inspector:"));
708 assert_eq!(output.lines().nth(1), Some("==============="));
709 assert_eq!(output.lines().nth(2), Some("Type: Scalar"));
710 }
711
712 #[test]
713 fn test_visual_diff_simple() {
714 let before = Document::from_str("name: Alice").unwrap();
715 let after = Document::from_str("name: Bob").unwrap();
716
717 let diff = VisualDiff {
718 before: &before,
719 after: &after,
720 };
721
722 let output = format!("{}", diff);
723
724 assert_eq!(output.lines().next(), Some("Visual Diff:"));
725 assert_eq!(output.lines().nth(1), Some("============"));
726 }
727
728 #[test]
729 fn test_graphviz_dot_structure() {
730 let yaml = YamlFile::from_str("name: Alice").unwrap();
731 let dot = graphviz_dot(yaml.syntax());
732
733 assert_eq!(dot.lines().next(), Some("digraph CST {"));
734 assert_eq!(dot.lines().last(), Some("}"));
735 }
736
737 #[test]
738 fn test_graphviz_dot_contains_nodes() {
739 let yaml = YamlFile::from_str("key: value").unwrap();
740 let dot = graphviz_dot(yaml.syntax());
741
742 let node_count = dot.lines().filter(|l| l.trim().starts_with("node")).count();
744 assert!(
745 node_count >= 1,
746 "expected at least one node declaration in dot output"
747 );
748 }
749}