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 if !is_in_flow_collection(node) => {
249 if let Some(last_token) = node.last_token() {
251 if last_token.kind() != SyntaxKind::NEWLINE {
252 return Err(format!(
253 "Block-style SEQUENCE_ENTRY should end with NEWLINE, ends with {:?}",
254 last_token.kind()
255 ));
256 }
257 }
258 }
259 _ => {}
260 }
261
262 for child in node.children() {
264 validate_node(&child)?;
265 }
266
267 Ok(())
268}
269
270fn is_in_flow_collection(node: &SyntaxNode) -> bool {
271 let mut current = node.parent();
273 while let Some(parent) = current {
274 match parent.kind() {
275 SyntaxKind::MAPPING => {
276 for child in parent.children_with_tokens() {
278 if let Some(token) = child.as_token() {
279 if token.kind() == SyntaxKind::LEFT_BRACE {
280 return true;
281 }
282 }
283 }
284 return false;
285 }
286 SyntaxKind::SEQUENCE => {
287 for child in parent.children_with_tokens() {
289 if let Some(token) = child.as_token() {
290 if token.kind() == SyntaxKind::LEFT_BRACKET {
291 return true;
292 }
293 }
294 }
295 return false;
296 }
297 _ => current = parent.parent(),
298 }
299 }
300 false
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 use crate::yaml::YamlFile;
307 use rowan::ast::AstNode;
308 use std::str::FromStr;
309
310 #[test]
311 fn test_print_tree() {
312 let yaml = YamlFile::from_str("name: Alice").unwrap();
313 print_tree(yaml.syntax());
315 }
316
317 #[test]
318 fn test_tree_to_string() {
319 let yaml = YamlFile::from_str("name: Alice").unwrap();
320 let s = tree_to_string(yaml.syntax());
321 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";
322 assert_eq!(s, expected);
323 }
324
325 #[test]
326 fn test_tree_to_string_sequence() {
327 let yaml = YamlFile::from_str("- item1\n- item2\n").unwrap();
328 let s = tree_to_string(yaml.syntax());
329 assert_eq!(
330 s,
331 concat!(
332 "DOCUMENT\n",
333 " SEQUENCE\n",
334 " SEQUENCE_ENTRY\n",
335 " DASH: \"-\"\n",
336 " WHITESPACE: \" \"\n",
337 " SCALAR\n",
338 " STRING: \"item1\"\n",
339 " NEWLINE: \"\\\\n\"\n",
340 " SEQUENCE_ENTRY\n",
341 " DASH: \"-\"\n",
342 " WHITESPACE: \" \"\n",
343 " SCALAR\n",
344 " STRING: \"item2\"\n",
345 " NEWLINE: \"\\\\n\"\n",
346 )
347 );
348 }
349
350 #[test]
351 fn test_print_stats() {
352 let yaml = YamlFile::from_str("name: Alice\nage: 30\n").unwrap();
353 print_stats(yaml.syntax());
355 }
356
357 #[test]
358 fn test_validate_tree() {
359 let yaml = YamlFile::from_str("name: Alice\nage: 30\n").unwrap();
360 validate_tree(yaml.syntax()).expect("Tree should be valid");
361 }
362}
363
364pub struct PrettyDebug<'a, T>(pub &'a T);
379
380impl<'a> fmt::Display for PrettyDebug<'a, Document> {
381 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382 writeln!(f, "Document {{")?;
383
384 if let Some(mapping) = self.0.as_mapping() {
385 write_indented(f, &format!("{}", PrettyDebug(&mapping)), 1)?;
386 } else if let Some(sequence) = self.0.as_sequence() {
387 write_indented(f, &format!("{}", PrettyDebug(&sequence)), 1)?;
388 } else if let Some(scalar) = self.0.as_scalar() {
389 writeln!(f, " {}", PrettyDebug(&scalar))?;
390 }
391
392 writeln!(f, "}}")
393 }
394}
395
396impl<'a> fmt::Display for PrettyDebug<'a, Mapping> {
397 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398 writeln!(f, "Mapping [")?;
399
400 for (key, value) in self.0.iter() {
401 write!(f, " {:?}: ", key)?;
402 format_node(f, &value, 1)?;
403 writeln!(f)?;
404 }
405
406 write!(f, "]")
407 }
408}
409
410impl<'a> fmt::Display for PrettyDebug<'a, Sequence> {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 writeln!(f, "Sequence [")?;
413
414 for (i, value) in self.0.values().enumerate() {
415 write!(f, " [{}]: ", i)?;
416 format_node(f, &value, 1)?;
417 writeln!(f)?;
418 }
419
420 write!(f, "]")
421 }
422}
423
424impl<'a> fmt::Display for PrettyDebug<'a, Scalar> {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 write!(f, "Scalar({:?})", self.0.value())
427 }
428}
429
430fn write_indented(f: &mut fmt::Formatter<'_>, text: &str, indent: usize) -> fmt::Result {
431 let indent_str = " ".repeat(indent);
432 for line in text.lines() {
433 writeln!(f, "{}{}", indent_str, line)?;
434 }
435 Ok(())
436}
437
438fn format_node(f: &mut fmt::Formatter<'_>, node: &YamlNode, indent: usize) -> fmt::Result {
439 let indent_str = " ".repeat(indent);
440
441 match node {
442 YamlNode::Scalar(s) => {
443 let sv = crate::scalar::ScalarValue::from_scalar(s);
444 write!(f, "{:?} (type: {:?})", sv.value(), sv.scalar_type())?;
445 }
446 YamlNode::Mapping(m) => {
447 writeln!(f, "Mapping {{")?;
448 for (k, v) in m.iter() {
449 write!(f, "{} ", indent_str)?;
450 format_node(f, &k, 0)?;
451 write!(f, ": ")?;
452 format_node(f, &v, indent + 1)?;
453 writeln!(f)?;
454 }
455 write!(f, "{}}}", indent_str)?;
456 }
457 YamlNode::Sequence(s) => {
458 writeln!(f, "Sequence [")?;
459 for (i, v) in s.values().enumerate() {
460 write!(f, "{} [{}]: ", indent_str, i)?;
461 format_node(f, &v, indent + 1)?;
462 writeln!(f)?;
463 }
464 write!(f, "{}]", indent_str)?;
465 }
466 YamlNode::Alias(a) => {
467 write!(f, "Alias(*{})", a.name())?;
468 }
469 YamlNode::TaggedNode(t) => {
470 write!(f, "Tagged({:?})", t.tag().unwrap_or_default())?;
471 }
472 }
473
474 Ok(())
475}
476
477pub struct ValueInspector<'a>(pub &'a crate::as_yaml::YamlNode);
498
499impl<'a> fmt::Display for ValueInspector<'a> {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 writeln!(f, "Value Inspector:")?;
502 writeln!(f, "===============")?;
503
504 match self.0 {
505 crate::as_yaml::YamlNode::Scalar(s) => {
506 writeln!(f, "Type: Scalar")?;
507 writeln!(f, " Value: {:?}", s.as_string())?;
508
509 writeln!(f, "\nCoercion Capabilities:")?;
510 writeln!(f, " to_i64: {:?}", self.0.to_i64())?;
511 writeln!(f, " to_f64: {:?}", self.0.to_f64())?;
512 writeln!(f, " to_bool: {:?}", self.0.to_bool())?;
513 }
514 crate::as_yaml::YamlNode::Mapping(m) => {
515 writeln!(f, "Type: Mapping")?;
516 writeln!(f, " Size: {} entries", m.len())?;
517 }
518 crate::as_yaml::YamlNode::Sequence(s) => {
519 writeln!(f, "Type: Sequence")?;
520 writeln!(f, " Length: {} items", s.len())?;
521 }
522 crate::as_yaml::YamlNode::Alias(a) => {
523 writeln!(f, "Type: Alias")?;
524 writeln!(f, " Anchor name: {}", a.name())?;
525 }
526 crate::as_yaml::YamlNode::TaggedNode(_) => {
527 writeln!(f, "Type: TaggedNode")?;
528 }
529 }
530
531 Ok(())
532 }
533}
534
535pub struct VisualDiff<'a> {
554 pub before: &'a Document,
556 pub after: &'a Document,
558}
559
560impl<'a> fmt::Display for VisualDiff<'a> {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 writeln!(f, "Visual Diff:")?;
563 writeln!(f, "============")?;
564
565 writeln!(f, "\nBefore:")?;
566 writeln!(f, "-------")?;
567 for line in self.before.to_string().lines() {
568 writeln!(f, " {}", line)?;
569 }
570
571 writeln!(f, "\nAfter:")?;
572 writeln!(f, "------")?;
573 for line in self.after.to_string().lines() {
574 writeln!(f, " {}", line)?;
575 }
576
577 writeln!(f, "\nChanges:")?;
578 writeln!(f, "--------")?;
579
580 let before_str = self.before.to_string();
582 let after_str = self.after.to_string();
583 let before_lines: Vec<_> = before_str.lines().collect();
584 let after_lines: Vec<_> = after_str.lines().collect();
585
586 for (i, (b, a)) in before_lines.iter().zip(after_lines.iter()).enumerate() {
587 if b != a {
588 writeln!(f, "Line {}: {:?} -> {:?}", i + 1, b, a)?;
589 }
590 }
591
592 if before_lines.len() > after_lines.len() {
593 writeln!(
594 f,
595 "Removed {} lines",
596 before_lines.len() - after_lines.len()
597 )?;
598 } else if after_lines.len() > before_lines.len() {
599 writeln!(f, "Added {} lines", after_lines.len() - before_lines.len())?;
600 }
601
602 Ok(())
603 }
604}
605
606pub fn graphviz_dot(node: &SyntaxNode) -> String {
623 let mut result = String::from("digraph CST {\n");
624 result.push_str(" node [shape=box, fontname=\"Courier\"];\n");
625 result.push_str(" edge [fontsize=10];\n\n");
626
627 let mut counter = 0;
628 graphviz_node(&mut result, node, None, &mut counter);
629
630 result.push_str("}\n");
631 result
632}
633
634fn graphviz_node(
635 result: &mut String,
636 node: &SyntaxNode,
637 parent_id: Option<usize>,
638 counter: &mut usize,
639) {
640 let node_id = *counter;
641 *counter += 1;
642
643 let label = format!("{:?}", node.kind());
645 result.push_str(&format!(" n{} [label=\"{}\"];\n", node_id, label));
646
647 if let Some(pid) = parent_id {
649 result.push_str(&format!(" n{} -> n{};\n", pid, node_id));
650 }
651
652 for child in node.children_with_tokens() {
654 match child {
655 rowan::NodeOrToken::Node(n) => {
656 graphviz_node(result, &n, Some(node_id), counter);
657 }
658 rowan::NodeOrToken::Token(t) => {
659 let token_id = *counter;
660 *counter += 1;
661
662 let text = t
663 .text()
664 .replace('\\', "\\\\")
665 .replace('"', "\\\"")
666 .replace('\n', "\\\\n")
667 .replace('\r', "\\\\r");
668 let label = format!("{:?}\\n{:?}", t.kind(), text);
669 result.push_str(&format!(
670 " n{} [label=\"{}\", shape=ellipse, style=filled, fillcolor=lightgray];\n",
671 token_id, label
672 ));
673 result.push_str(&format!(" n{} -> n{};\n", node_id, token_id));
674 }
675 }
676 }
677}
678
679#[cfg(test)]
680mod new_debug_tests {
681 use super::*;
682 use crate::yaml::YamlFile;
683 use rowan::ast::AstNode;
684 use std::str::FromStr;
685
686 #[test]
687 fn test_pretty_debug_document() {
688 let doc = Document::from_str("name: Alice\nage: 30").unwrap();
689 let output = format!("{}", PrettyDebug(&doc));
690
691 assert_eq!(output.lines().next(), Some("Document {"));
693 assert_eq!(output.lines().last(), Some("}"));
694 }
695
696 #[test]
697 fn test_value_inspector_scalar() {
698 let yaml = YamlFile::from_str("port: 8080").unwrap();
699 let doc = yaml.document().unwrap();
700 let mapping = doc.as_mapping().unwrap();
701 let value = mapping.get("port").unwrap();
702
703 let output = format!("{}", ValueInspector(&value));
704
705 assert_eq!(output.lines().next(), Some("Value Inspector:"));
706 assert_eq!(output.lines().nth(1), Some("==============="));
707 assert_eq!(output.lines().nth(2), Some("Type: Scalar"));
708 }
709
710 #[test]
711 fn test_visual_diff_simple() {
712 let before = Document::from_str("name: Alice").unwrap();
713 let after = Document::from_str("name: Bob").unwrap();
714
715 let diff = VisualDiff {
716 before: &before,
717 after: &after,
718 };
719
720 let output = format!("{}", diff);
721
722 assert_eq!(output.lines().next(), Some("Visual Diff:"));
723 assert_eq!(output.lines().nth(1), Some("============"));
724 }
725
726 #[test]
727 fn test_graphviz_dot_structure() {
728 let yaml = YamlFile::from_str("name: Alice").unwrap();
729 let dot = graphviz_dot(yaml.syntax());
730
731 assert_eq!(dot.lines().next(), Some("digraph CST {"));
732 assert_eq!(dot.lines().last(), Some("}"));
733 }
734
735 #[test]
736 fn test_graphviz_dot_contains_nodes() {
737 let yaml = YamlFile::from_str("key: value").unwrap();
738 let dot = graphviz_dot(yaml.syntax());
739
740 let node_count = dot.lines().filter(|l| l.trim().starts_with("node")).count();
742 assert!(
743 node_count >= 1,
744 "expected at least one node declaration in dot output"
745 );
746 }
747}