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}