Skip to main content

decy_debugger/
visualize_ast.rs

1//! C AST visualization for debugging
2//!
3//! Provides formatted visualization of C ASTs parsed by clang-sys.
4//! Inspired by spydecy's Python AST visualizer.
5
6use anyhow::{Context, Result};
7use colored::Colorize;
8use decy_parser::{CParser, Expression, Function, Statement};
9use std::fs;
10use std::path::Path;
11
12/// Visualize C source as AST
13///
14/// # Errors
15///
16/// Returns an error if the file cannot be read or parsed
17pub fn visualize_c_ast(file_path: &Path, use_colors: bool) -> Result<String> {
18    // Read the source file
19    let source = fs::read_to_string(file_path)
20        .with_context(|| format!("Failed to read file: {}", file_path.display()))?;
21
22    // Parse to AST
23    let parser = CParser::new()?;
24    let ast = parser.parse(&source).context("Failed to parse C source")?;
25    let functions = ast.functions();
26
27    // Format the output
28    let mut output = String::new();
29
30    // Header
31    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    // File info
52    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    // Source code preview
60    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    // AST tree
77    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    // Statistics
90    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
112/// Format a function node with indentation
113fn 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    // Return type
125    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    // Parameters
134    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    // Body statements
149    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
159/// Format a statement node
160fn 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
234/// Format an expression for display
235fn 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        // Just verify it produces output (color codes make exact matching hard)
304        let output = result.unwrap();
305        assert!(!output.is_empty());
306        assert!(output.contains("main"));
307    }
308
309    // ============================================================================
310    // Coverage: for loop, while loop, if/else, variable decl, assignment
311    // ============================================================================
312
313    #[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        // Should show both functions
388        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    // ============================================================================
453    // Coverage: error paths
454    // ============================================================================
455
456    #[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    // ============================================================================
463    // Coverage: format_expression and format_statement direct tests
464    // ============================================================================
465
466    #[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        // Color codes wrap the number
478        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        // Falls through to Debug format
534        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}