1use anyhow::{Context, Result};
7use colored::Colorize;
8use decy_parser::{CParser, Expression, Function, Statement};
9use std::fs;
10use std::path::Path;
11
12pub fn visualize_c_ast(file_path: &Path, use_colors: bool) -> Result<String> {
18 let source = fs::read_to_string(file_path)
20 .with_context(|| format!("Failed to read file: {}", file_path.display()))?;
21
22 let parser = CParser::new()?;
24 let ast = parser.parse(&source).context("Failed to parse C source")?;
25 let functions = ast.functions();
26
27 let mut output = String::new();
29
30 if use_colors {
32 output.push_str(&format!(
33 "{}",
34 "╔══════════════════════════════════════════════════════════╗\n".cyan()
35 ));
36 output.push_str(&format!(
37 "{}",
38 "║ Decy Debugger: C AST Visualization ║\n".cyan()
39 ));
40 output.push_str(&format!(
41 "{}",
42 "╚══════════════════════════════════════════════════════════╝\n".cyan()
43 ));
44 } else {
45 output.push_str("╔══════════════════════════════════════════════════════════╗\n");
46 output.push_str("║ Decy Debugger: C AST Visualization ║\n");
47 output.push_str("╚══════════════════════════════════════════════════════════╝\n");
48 }
49 output.push('\n');
50
51 let file_label = if use_colors {
53 "File:".bold().to_string()
54 } else {
55 "File:".to_string()
56 };
57 output.push_str(&format!("{} {}\n", file_label, file_path.display()));
58
59 let size_label = if use_colors {
60 "Size:".bold().to_string()
61 } else {
62 "Size:".to_string()
63 };
64 output.push_str(&format!(
65 "{} {} lines\n",
66 size_label,
67 source.lines().count()
68 ));
69 output.push('\n');
70
71 let source_header = if use_colors {
73 "═══ Source Code ═══".yellow().bold().to_string()
74 } else {
75 "═══ Source Code ═══".to_string()
76 };
77 output.push_str(&format!("{source_header}\n"));
78
79 for (i, line) in source.lines().enumerate() {
80 if use_colors {
81 output.push_str(&format!("{:3} │ {}\n", (i + 1).to_string().dimmed(), line));
82 } else {
83 output.push_str(&format!("{:3} │ {}\n", i + 1, line));
84 }
85 }
86 output.push('\n');
87
88 let ast_header = if use_colors {
90 "═══ Abstract Syntax Tree ═══".green().bold().to_string()
91 } else {
92 "═══ Abstract Syntax Tree ═══".to_string()
93 };
94 output.push_str(&format!("{}\n", ast_header));
95
96 for function in functions {
97 format_function(function, 0, &mut output, use_colors);
98 }
99 output.push('\n');
100
101 let stats_header = if use_colors {
103 "═══ Statistics ═══".blue().bold().to_string()
104 } else {
105 "═══ Statistics ═══".to_string()
106 };
107 output.push_str(&format!("{}\n", stats_header));
108
109 let func_count_label = if use_colors {
110 "Functions:".bold().to_string()
111 } else {
112 "Functions:".to_string()
113 };
114 output.push_str(&format!(" {} {}\n", func_count_label, functions.len()));
115
116 let total_statements: usize = functions.iter().map(|f| f.body.len()).sum();
117 let stmt_count_label = if use_colors {
118 "Total statements:".bold().to_string()
119 } else {
120 "Total statements:".to_string()
121 };
122 output.push_str(&format!(" {} {}\n", stmt_count_label, total_statements));
123
124 Ok(output)
125}
126
127fn format_function(function: &Function, depth: usize, output: &mut String, use_colors: bool) {
129 let indent = " ".repeat(depth);
130
131 let node_type = if use_colors {
132 format!("Function: {}", function.name)
133 .green()
134 .bold()
135 .to_string()
136 } else {
137 format!("Function: {}", function.name)
138 };
139
140 output.push_str(&format!("{}├─ {}", indent, node_type));
141
142 let return_type_str = format!("{:?}", function.return_type);
144 if use_colors {
145 output.push_str(&format!(" → {}", return_type_str.dimmed()));
146 } else {
147 output.push_str(&format!(" → {}", return_type_str));
148 }
149 output.push('\n');
150
151 if !function.parameters.is_empty() {
153 let params_label = if use_colors {
154 "Parameters:".blue().to_string()
155 } else {
156 "Parameters:".to_string()
157 };
158 output.push_str(&format!("{} {} ", indent, params_label));
159
160 for (i, param) in function.parameters.iter().enumerate() {
161 if i > 0 {
162 output.push_str(", ");
163 }
164 output.push_str(&format!("{}: {:?}", param.name, param.param_type));
165 }
166 output.push('\n');
167 }
168
169 if !function.body.is_empty() {
171 let body_label = if use_colors {
172 "Body:".cyan().to_string()
173 } else {
174 "Body:".to_string()
175 };
176 output.push_str(&format!("{} {}\n", indent, body_label));
177
178 for stmt in &function.body {
179 format_statement(stmt, depth + 2, output, use_colors);
180 }
181 }
182}
183
184fn format_statement(stmt: &Statement, depth: usize, output: &mut String, use_colors: bool) {
186 let indent = " ".repeat(depth);
187
188 let stmt_str = match stmt {
189 Statement::Return(Some(expr)) => {
190 let label = if use_colors {
191 "Return".red().to_string()
192 } else {
193 "Return".to_string()
194 };
195 format!("{}: {}", label, format_expression(expr, use_colors))
196 }
197 Statement::Return(None) => {
198 if use_colors {
199 "Return (void)".red().to_string()
200 } else {
201 "Return (void)".to_string()
202 }
203 }
204 Statement::Assignment { target, value } => {
205 let label = if use_colors {
206 "Assignment".yellow().to_string()
207 } else {
208 "Assignment".to_string()
209 };
210 format!(
211 "{}: {} = {}",
212 label,
213 target,
214 format_expression(value, use_colors)
215 )
216 }
217 Statement::If {
218 condition,
219 then_block,
220 else_block,
221 } => {
222 let mut s = if use_colors {
223 format!(
224 "{}: {}",
225 "If".magenta(),
226 format_expression(condition, use_colors)
227 )
228 } else {
229 format!("If: {}", format_expression(condition, use_colors))
230 };
231
232 s.push_str(&format!(" (then: {} stmts", then_block.len()));
233 if let Some(else_b) = else_block {
234 s.push_str(&format!(", else: {} stmts)", else_b.len()));
235 } else {
236 s.push(')');
237 }
238 s
239 }
240 Statement::While { condition, body } => {
241 let label = if use_colors {
242 "While".magenta().to_string()
243 } else {
244 "While".to_string()
245 };
246 format!(
247 "{}: {} ({} stmts)",
248 label,
249 format_expression(condition, use_colors),
250 body.len()
251 )
252 }
253 Statement::For {
254 init,
255 condition,
256 increment,
257 body,
258 } => {
259 let label = if use_colors {
260 "For".magenta().to_string()
261 } else {
262 "For".to_string()
263 };
264 format!(
265 "{}: init={:?}, cond={:?}, inc={:?} ({} stmts)",
266 label,
267 init,
268 condition,
269 increment,
270 body.len()
271 )
272 }
273 Statement::VariableDeclaration {
274 name,
275 var_type,
276 initializer,
277 } => {
278 let label = if use_colors {
279 "VarDecl".cyan().to_string()
280 } else {
281 "VarDecl".to_string()
282 };
283 let mut s = format!("{}: {:?} {}", label, var_type, name);
284 if let Some(init) = initializer {
285 s.push_str(&format!(" = {}", format_expression(init, use_colors)));
286 }
287 s
288 }
289 _ => format!("{:?}", stmt),
290 };
291
292 output.push_str(&format!("{}├─ {}\n", indent, stmt_str));
293}
294
295fn format_expression(expr: &Expression, use_colors: bool) -> String {
297 match expr {
298 Expression::IntLiteral(n) => {
299 if use_colors {
300 n.to_string().bright_yellow().to_string()
301 } else {
302 n.to_string()
303 }
304 }
305 Expression::Variable(name) => {
306 if use_colors {
307 name.blue().to_string()
308 } else {
309 name.clone()
310 }
311 }
312 Expression::BinaryOp { left, op, right } => {
313 format!(
314 "({} {:?} {})",
315 format_expression(left, use_colors),
316 op,
317 format_expression(right, use_colors)
318 )
319 }
320 Expression::FunctionCall {
321 function,
322 arguments,
323 } => {
324 let args_str = arguments
325 .iter()
326 .map(|arg| format_expression(arg, use_colors))
327 .collect::<Vec<_>>()
328 .join(", ");
329
330 if use_colors {
331 format!("{}({})", function.magenta(), args_str)
332 } else {
333 format!("{}({})", function, args_str)
334 }
335 }
336 _ => format!("{:?}", expr),
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use std::io::Write;
344 use tempfile::NamedTempFile;
345
346 #[test]
347 fn test_visualize_simple_function() {
348 let mut temp_file = NamedTempFile::new().unwrap();
349 writeln!(temp_file, "int add(int a, int b) {{ return a + b; }}").unwrap();
350
351 let result = visualize_c_ast(temp_file.path(), false);
352 assert!(result.is_ok());
353
354 let output = result.unwrap();
355 assert!(output.contains("Function: add"));
356 assert!(output.contains("Return"));
357 }
358
359 #[test]
360 fn test_visualize_with_colors() {
361 let mut temp_file = NamedTempFile::new().unwrap();
362 writeln!(temp_file, "int main() {{ return 0; }}").unwrap();
363
364 let result = visualize_c_ast(temp_file.path(), true);
365 assert!(result.is_ok());
366
367 let output = result.unwrap();
369 assert!(!output.is_empty());
370 assert!(output.contains("main"));
371 }
372
373 #[test]
378 fn test_visualize_for_loop() {
379 let mut temp_file = NamedTempFile::new().unwrap();
380 writeln!(
381 temp_file,
382 "int main() {{ int i; for (i = 0; i < 10; i = i + 1) {{ }} return 0; }}"
383 )
384 .unwrap();
385
386 let result = visualize_c_ast(temp_file.path(), false);
387 assert!(result.is_ok());
388 let output = result.unwrap();
389 assert!(output.contains("Function: main"));
390 }
391
392 #[test]
393 fn test_visualize_while_loop() {
394 let mut temp_file = NamedTempFile::new().unwrap();
395 writeln!(
396 temp_file,
397 "int main() {{ int x = 10; while (x) {{ x = x - 1; }} return 0; }}"
398 )
399 .unwrap();
400
401 let result = visualize_c_ast(temp_file.path(), false);
402 assert!(result.is_ok());
403 let output = result.unwrap();
404 assert!(output.contains("Function: main"));
405 }
406
407 #[test]
408 fn test_visualize_if_else() {
409 let mut temp_file = NamedTempFile::new().unwrap();
410 writeln!(
411 temp_file,
412 "int main() {{ int x = 5; if (x) {{ return 1; }} else {{ return 0; }} }}"
413 )
414 .unwrap();
415
416 let result = visualize_c_ast(temp_file.path(), false);
417 assert!(result.is_ok());
418 let output = result.unwrap();
419 assert!(output.contains("Function: main"));
420 }
421
422 #[test]
423 fn test_visualize_variable_declaration() {
424 let mut temp_file = NamedTempFile::new().unwrap();
425 writeln!(
426 temp_file,
427 "int main() {{ int x = 42; int y = x + 1; return y; }}"
428 )
429 .unwrap();
430
431 let result = visualize_c_ast(temp_file.path(), false);
432 assert!(result.is_ok());
433 let output = result.unwrap();
434 assert!(output.contains("Function: main"));
435 }
436
437 #[test]
438 fn test_visualize_assignment() {
439 let mut temp_file = NamedTempFile::new().unwrap();
440 writeln!(
441 temp_file,
442 "int main() {{ int x = 0; x = 42; return x; }}"
443 )
444 .unwrap();
445
446 let result = visualize_c_ast(temp_file.path(), false);
447 assert!(result.is_ok());
448 }
449
450 #[test]
451 fn test_visualize_function_call() {
452 let mut temp_file = NamedTempFile::new().unwrap();
453 writeln!(
454 temp_file,
455 "int add(int a, int b) {{ return a + b; }}\nint main() {{ return add(2, 3); }}"
456 )
457 .unwrap();
458
459 let result = visualize_c_ast(temp_file.path(), false);
460 assert!(result.is_ok());
461 let output = result.unwrap();
462 assert!(output.contains("Function: add") || output.contains("Function: main"));
464 }
465
466 #[test]
467 fn test_visualize_multiple_parameters() {
468 let mut temp_file = NamedTempFile::new().unwrap();
469 writeln!(
470 temp_file,
471 "int add3(int a, int b, int c) {{ return a + b + c; }}"
472 )
473 .unwrap();
474
475 let result = visualize_c_ast(temp_file.path(), false);
476 assert!(result.is_ok());
477 let output = result.unwrap();
478 assert!(output.contains("Parameters:"));
479 }
480
481 #[test]
482 fn test_visualize_void_return() {
483 let mut temp_file = NamedTempFile::new().unwrap();
484 writeln!(temp_file, "void noop() {{ return; }}").unwrap();
485
486 let result = visualize_c_ast(temp_file.path(), false);
487 assert!(result.is_ok());
488 }
489
490 #[test]
491 fn test_visualize_statistics() {
492 let mut temp_file = NamedTempFile::new().unwrap();
493 writeln!(
494 temp_file,
495 "int f1() {{ return 1; }}\nint f2() {{ return 2; }}"
496 )
497 .unwrap();
498
499 let result = visualize_c_ast(temp_file.path(), false);
500 assert!(result.is_ok());
501 let output = result.unwrap();
502 assert!(output.contains("Statistics"));
503 assert!(output.contains("Functions:"));
504 assert!(output.contains("Total statements:"));
505 }
506
507 #[test]
508 fn test_visualize_colors_for_loop() {
509 let mut temp_file = NamedTempFile::new().unwrap();
510 writeln!(
511 temp_file,
512 "int main() {{ int i; for (i = 0; i < 5; i = i + 1) {{ }} return 0; }}"
513 )
514 .unwrap();
515
516 let result = visualize_c_ast(temp_file.path(), true);
517 assert!(result.is_ok());
518 let output = result.unwrap();
519 assert!(!output.is_empty());
520 }
521
522 #[test]
523 fn test_visualize_colors_if_else() {
524 let mut temp_file = NamedTempFile::new().unwrap();
525 writeln!(
526 temp_file,
527 "int main() {{ int x = 1; if (x) {{ return 1; }} else {{ return 0; }} }}"
528 )
529 .unwrap();
530
531 let result = visualize_c_ast(temp_file.path(), true);
532 assert!(result.is_ok());
533 }
534
535 #[test]
540 fn test_visualize_nonexistent_file() {
541 let result = visualize_c_ast(Path::new("/nonexistent/file.c"), false);
542 assert!(result.is_err());
543 }
544
545 #[test]
550 fn test_format_expression_int_literal_no_colors() {
551 let expr = Expression::IntLiteral(42);
552 let result = format_expression(&expr, false);
553 assert_eq!(result, "42");
554 }
555
556 #[test]
557 fn test_format_expression_int_literal_with_colors() {
558 let expr = Expression::IntLiteral(42);
559 let result = format_expression(&expr, true);
560 assert!(result.contains("42"));
562 }
563
564 #[test]
565 fn test_format_expression_variable_no_colors() {
566 let expr = Expression::Variable("x".to_string());
567 let result = format_expression(&expr, false);
568 assert_eq!(result, "x");
569 }
570
571 #[test]
572 fn test_format_expression_variable_with_colors() {
573 let expr = Expression::Variable("x".to_string());
574 let result = format_expression(&expr, true);
575 assert!(result.contains("x"));
576 }
577
578 #[test]
579 fn test_format_expression_binary_op() {
580 let expr = Expression::BinaryOp {
581 left: Box::new(Expression::IntLiteral(1)),
582 op: decy_parser::parser::BinaryOperator::Add,
583 right: Box::new(Expression::IntLiteral(2)),
584 };
585 let result = format_expression(&expr, false);
586 assert!(result.contains("1"));
587 assert!(result.contains("2"));
588 assert!(result.contains("Add"));
589 }
590
591 #[test]
592 fn test_format_expression_function_call_no_colors() {
593 let expr = Expression::FunctionCall {
594 function: "printf".to_string(),
595 arguments: vec![Expression::Variable("msg".to_string())],
596 };
597 let result = format_expression(&expr, false);
598 assert!(result.contains("printf"));
599 assert!(result.contains("msg"));
600 }
601
602 #[test]
603 fn test_format_expression_function_call_with_colors() {
604 let expr = Expression::FunctionCall {
605 function: "printf".to_string(),
606 arguments: vec![Expression::IntLiteral(0)],
607 };
608 let result = format_expression(&expr, true);
609 assert!(result.contains("printf"));
610 }
611
612 #[test]
613 fn test_format_expression_catchall() {
614 let expr = Expression::StringLiteral("hello".to_string());
615 let result = format_expression(&expr, false);
616 assert!(!result.is_empty());
618 }
619
620 #[test]
621 fn test_format_statement_return_some_no_colors() {
622 let stmt = Statement::Return(Some(Expression::IntLiteral(0)));
623 let mut output = String::new();
624 format_statement(&stmt, 0, &mut output, false);
625 assert!(output.contains("Return"));
626 assert!(output.contains("0"));
627 }
628
629 #[test]
630 fn test_format_statement_return_none_no_colors() {
631 let stmt = Statement::Return(None);
632 let mut output = String::new();
633 format_statement(&stmt, 0, &mut output, false);
634 assert!(output.contains("Return (void)"));
635 }
636
637 #[test]
638 fn test_format_statement_return_none_with_colors() {
639 let stmt = Statement::Return(None);
640 let mut output = String::new();
641 format_statement(&stmt, 0, &mut output, true);
642 assert!(output.contains("Return"));
643 }
644
645 #[test]
646 fn test_format_statement_assignment_no_colors() {
647 let stmt = Statement::Assignment {
648 target: "x".to_string(),
649 value: Expression::IntLiteral(42),
650 };
651 let mut output = String::new();
652 format_statement(&stmt, 0, &mut output, false);
653 assert!(output.contains("Assignment"));
654 assert!(output.contains("x"));
655 }
656
657 #[test]
658 fn test_format_statement_if_with_else_no_colors() {
659 let stmt = Statement::If {
660 condition: Expression::Variable("x".to_string()),
661 then_block: vec![Statement::Return(Some(Expression::IntLiteral(1)))],
662 else_block: Some(vec![Statement::Return(Some(Expression::IntLiteral(0)))]),
663 };
664 let mut output = String::new();
665 format_statement(&stmt, 0, &mut output, false);
666 assert!(output.contains("If"));
667 assert!(output.contains("then: 1 stmts"));
668 assert!(output.contains("else: 1 stmts"));
669 }
670
671 #[test]
672 fn test_format_statement_if_with_else_with_colors() {
673 let stmt = Statement::If {
674 condition: Expression::Variable("x".to_string()),
675 then_block: vec![Statement::Return(Some(Expression::IntLiteral(1)))],
676 else_block: Some(vec![Statement::Return(Some(Expression::IntLiteral(0)))]),
677 };
678 let mut output = String::new();
679 format_statement(&stmt, 0, &mut output, true);
680 assert!(output.contains("else: 1 stmts"));
681 }
682
683 #[test]
684 fn test_format_statement_while_no_colors() {
685 let stmt = Statement::While {
686 condition: Expression::Variable("running".to_string()),
687 body: vec![Statement::Return(Some(Expression::IntLiteral(0)))],
688 };
689 let mut output = String::new();
690 format_statement(&stmt, 0, &mut output, false);
691 assert!(output.contains("While"));
692 assert!(output.contains("1 stmts"));
693 }
694
695 #[test]
696 fn test_format_statement_for_no_colors() {
697 let stmt = Statement::For {
698 init: vec![Statement::VariableDeclaration {
699 name: "i".to_string(),
700 var_type: decy_parser::Type::Int,
701 initializer: Some(Expression::IntLiteral(0)),
702 }],
703 condition: Some(Expression::Variable("i".to_string())),
704 increment: vec![],
705 body: vec![],
706 };
707 let mut output = String::new();
708 format_statement(&stmt, 0, &mut output, false);
709 assert!(output.contains("For"));
710 }
711
712 #[test]
713 fn test_format_statement_var_decl_no_colors() {
714 let stmt = Statement::VariableDeclaration {
715 name: "x".to_string(),
716 var_type: decy_parser::Type::Int,
717 initializer: Some(Expression::IntLiteral(42)),
718 };
719 let mut output = String::new();
720 format_statement(&stmt, 0, &mut output, false);
721 assert!(output.contains("VarDecl"));
722 assert!(output.contains("x"));
723 }
724
725 #[test]
726 fn test_format_statement_var_decl_no_init() {
727 let stmt = Statement::VariableDeclaration {
728 name: "y".to_string(),
729 var_type: decy_parser::Type::Int,
730 initializer: None,
731 };
732 let mut output = String::new();
733 format_statement(&stmt, 0, &mut output, false);
734 assert!(output.contains("VarDecl"));
735 assert!(output.contains("y"));
736 }
737
738 #[test]
739 fn test_format_statement_catchall() {
740 let stmt = Statement::Break;
741 let mut output = String::new();
742 format_statement(&stmt, 0, &mut output, false);
743 assert!(!output.is_empty());
744 }
745
746 #[test]
747 fn test_format_function_no_body() {
748 let function = Function {
749 name: "empty".to_string(),
750 return_type: decy_parser::Type::Void,
751 parameters: vec![],
752 body: vec![],
753 };
754 let mut output = String::new();
755 format_function(&function, 0, &mut output, false);
756 assert!(output.contains("Function: empty"));
757 }
758
759 #[test]
760 fn test_format_function_with_colors() {
761 let function = Function {
762 name: "test_fn".to_string(),
763 return_type: decy_parser::Type::Int,
764 parameters: vec![decy_parser::Parameter::new(
765 "x".to_string(),
766 decy_parser::Type::Int,
767 )],
768 body: vec![Statement::Return(Some(Expression::Variable(
769 "x".to_string(),
770 )))],
771 };
772 let mut output = String::new();
773 format_function(&function, 0, &mut output, true);
774 assert!(output.contains("test_fn"));
775 }
776}