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