1use super::{Tool, ToolError, Result, OutputFormat, parse_output_format};
2use clap::{Arg, ArgMatches, Command};
3use std::path::Path;
4use std::fs;
5use std::collections::HashMap;
6use colored::*;
7use syn::{parse_file, visit::Visit, ItemMacro, Macro};
8use quote::{quote, ToTokens};
9use serde::{Serialize, Deserialize};
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct MacroCall {
12 pub name: String,
13 pub args: String,
14 pub span_placeholder: String,
15 pub file: String,
16 pub line: usize,
17 pub column: usize,
18}
19#[derive(Debug, Clone)]
20pub struct ExpansionStep {
21 pub before: String,
22 pub after: String,
23 pub macro_name: String,
24 pub description: String,
25 pub line: usize,
26}
27#[derive(Debug, Clone)]
28pub struct ValidationIssue {
29 pub issue: String,
30 pub severity: String,
31 pub line: usize,
32 pub suggestion: String,
33}
34#[derive(Debug, Clone)]
35pub struct MacroDependency {
36 pub name: String,
37 pub dependency_type: String,
38 pub location: String,
39}
40#[derive(Debug, Clone, PartialEq)]
41pub enum MacroType {
42 Declarative,
43 Procedural,
44 BuiltIn,
45 Unknown,
46}
47pub struct MacroExpandTool;
48impl MacroExpandTool {
49 pub fn new() -> Self {
50 Self
51 }
52 fn parse_macro_calls(&self, file_path: &str) -> Result<Vec<MacroCall>> {
53 if !Path::new(file_path).exists() {
54 return Err(
55 ToolError::InvalidArguments(format!("File not found: {}", file_path)),
56 );
57 }
58 let content = fs::read_to_string(file_path)?;
59 let ast = parse_file(&content)
60 .map_err(|e| ToolError::ExecutionFailed(
61 format!("Failed to parse Rust file: {}", e),
62 ))?;
63 let mut visitor = MacroCallVisitor::new(file_path.to_string());
64 visitor.visit_file(&ast);
65 Ok(visitor.macro_calls)
66 }
67 fn expand_macro_step_by_step(
68 &self,
69 macro_call: &MacroCall,
70 ) -> Result<Vec<ExpansionStep>> {
71 let mut steps = Vec::new();
72 if let Ok(macro_def) = self
73 .find_macro_definition(¯o_call.name, ¯o_call.file)
74 {
75 steps
76 .push(ExpansionStep {
77 before: macro_call.args.clone(),
78 after: self.show_pattern_match(¯o_call.args, ¯o_def)?,
79 macro_name: macro_call.name.clone(),
80 description: "Pattern matching and hygiene application".to_string(),
81 line: macro_call.line,
82 });
83 steps
84 .push(ExpansionStep {
85 before: steps.last().unwrap().after.clone(),
86 after: self.apply_hygiene(&steps.last().unwrap().after)?,
87 macro_name: macro_call.name.clone(),
88 description: "Hygiene application".to_string(),
89 line: macro_call.line,
90 });
91 } else {
92 steps
93 .push(ExpansionStep {
94 before: macro_call.args.clone(),
95 after: format!("/* {} expanded */", macro_call.name),
96 macro_name: macro_call.name.clone(),
97 description: "Macro expansion (definition not found)".to_string(),
98 line: macro_call.line,
99 });
100 }
101 Ok(steps)
102 }
103 fn find_macro_definition(
104 &self,
105 macro_name: &str,
106 file_path: &str,
107 ) -> Result<String> {
108 let content = fs::read_to_string(file_path)?;
109 let macro_pattern = format!(
110 "macro_rules!\\s*{}\\s*\\{{", regex::escape(macro_name)
111 );
112 let re = regex::Regex::new(¯o_pattern).unwrap();
113 if let Some(mat) = re.find(&content) {
114 let start = mat.start();
115 let mut brace_count = 0;
116 let mut end = start;
117 for (i, c) in content[start..].chars().enumerate() {
118 match c {
119 '{' => brace_count += 1,
120 '}' => brace_count -= 1,
121 _ => {}
122 }
123 end = start + i;
124 if brace_count == 0 {
125 break;
126 }
127 }
128 Ok(content[start..=end].to_string())
129 } else {
130 Err(
131 ToolError::ExecutionFailed(
132 format!("Macro definition for '{}' not found", macro_name),
133 ),
134 )
135 }
136 }
137 fn show_pattern_match(&self, args: &str, macro_def: &str) -> Result<String> {
138 let args = args.trim_matches(&['(', ')'][..]);
139 if macro_def.contains("$x:expr") {
140 let elements: Vec<&str> = args.split(',').collect();
141 let mut result = "{\n let mut v = Vec::new();".to_string();
142 for elem in elements {
143 result.push_str(&format!("\n v.push({});", elem.trim()));
144 }
145 result.push_str("\n v\n}");
146 Ok(result)
147 } else {
148 Ok(format!("/* Pattern matched: {} */", args))
149 }
150 }
151 fn apply_hygiene(&self, code: &str) -> Result<String> {
152 let mut result = code
153 .replace("Vec::", "::alloc::vec::Vec::")
154 .replace("vec!", "::alloc::vec!");
155 if result.contains("::alloc::vec::Vec::new()") {
156 result = result
157 .replace("::alloc::vec::Vec::new()", "::alloc::vec::Vec::new");
158 }
159 Ok(result)
160 }
161 fn generate_expanded_code(&self, file_path: &str) -> Result<String> {
162 let macro_calls = self.parse_macro_calls(file_path)?;
163 let content = fs::read_to_string(file_path)?;
164 if macro_calls.is_empty() {
165 return Ok(content);
166 }
167 let mut expanded = content.clone();
168 for call in macro_calls {
169 if let Ok(steps) = self.expand_macro_step_by_step(&call) {
170 if let Some(final_step) = steps.last() {
171 let macro_call_pattern = format!("{}!{}", call.name, call.args);
172 expanded = expanded.replace(¯o_call_pattern, &final_step.after);
173 }
174 }
175 }
176 Ok(expanded)
177 }
178 fn highlight_differences(&self, original: &str, expanded: &str) -> Result<String> {
179 let original_lines: Vec<&str> = original.lines().collect();
180 let expanded_lines: Vec<&str> = expanded.lines().collect();
181 let mut result = String::new();
182 for (i, (orig, exp)) in original_lines
183 .iter()
184 .zip(expanded_lines.iter())
185 .enumerate()
186 {
187 if orig != exp {
188 result
189 .push_str(
190 &format!(
191 "{} {} {}\n", format!("{}:", i + 1) .yellow(), "-".red(),
192 orig.red()
193 ),
194 );
195 result
196 .push_str(
197 &format!(
198 "{} {} {}\n", format!("{}:", i + 1) .yellow(), "+".green(),
199 exp.green()
200 ),
201 );
202 } else {
203 result
204 .push_str(
205 &format!("{} {}\n", format!("{}:", i + 1) .blue(), orig),
206 );
207 }
208 }
209 Ok(result)
210 }
211 fn extract_macro_dependencies(
212 &self,
213 macro_call: &MacroCall,
214 ) -> Vec<MacroDependency> {
215 let mut deps = Vec::new();
216 if macro_call.args.contains("vec!") {
217 deps.push(MacroDependency {
218 name: "vec".to_string(),
219 dependency_type: "Built-in macro".to_string(),
220 location: "std library".to_string(),
221 });
222 }
223 if macro_call.name.contains("println") || macro_call.name.contains("print") {
224 deps.push(MacroDependency {
225 name: "print".to_string(),
226 dependency_type: "Built-in macro".to_string(),
227 location: "std library".to_string(),
228 });
229 }
230 deps
231 }
232 fn validate_macro_expansion(
233 &self,
234 expanded_code: &str,
235 ) -> Result<Vec<ValidationIssue>> {
236 let mut issues = Vec::new();
237 if let Err(_) = syn::parse_file(expanded_code) {
238 issues
239 .push(ValidationIssue {
240 issue: "Expanded code contains syntax errors".to_string(),
241 severity: "High".to_string(),
242 line: 0,
243 suggestion: "Review macro definition and arguments".to_string(),
244 });
245 }
246 if expanded_code.contains("unresolved name") {
247 issues
248 .push(ValidationIssue {
249 issue: "Unresolved names in expanded code".to_string(),
250 severity: "Medium".to_string(),
251 line: 0,
252 suggestion: "Check macro hygiene and imports".to_string(),
253 });
254 }
255 Ok(issues)
256 }
257 fn classify_macro(&self, macro_name: &str) -> MacroType {
258 match macro_name {
259 "println" | "print" | "format" | "vec" | "assert" | "panic" => {
260 MacroType::BuiltIn
261 }
262 name if name.contains("macro_rules!") => MacroType::Declarative,
263 name if name.contains("#[") => MacroType::Procedural,
264 _ => MacroType::Unknown,
265 }
266 }
267 fn format_expanded_code(&self, code: &str, format: &str) -> Result<String> {
268 match format {
269 "rust" => Ok(code.to_string()),
270 "html" => self.format_as_html(code),
271 "json" => self.format_as_json(code),
272 _ => Ok(code.to_string()),
273 }
274 }
275 fn format_as_html(&self, code: &str) -> Result<String> {
276 let html = format!(
277 "<!DOCTYPE html>
278<html>
279<head>
280 <title>Macro Expansion</title>
281 <style>
282 .code {{ font-family: 'Monaco', 'Menlo', monospace; background: #f5f5f5; padding: 1em; }}
283 .expanded {{ color: #28a745; }}
284 .original {{ color: #dc3545; }}
285 </style>
286</head>
287<body>
288 <h1>Macro Expansion Result</h1>
289 <pre class=\"code\">{}</pre>
290</body>
291</html>",
292 code.replace("<", "<").replace(">", ">")
293 );
294 Ok(html)
295 }
296 fn format_as_json(&self, code: &str) -> Result<String> {
297 let json = serde_json::json!(
298 { "expanded_code" : code, "timestamp" : chrono::Utc::now().to_rfc3339(),
299 "tool" : "macro-expand" }
300 );
301 Ok(serde_json::to_string_pretty(&json).unwrap())
302 }
303}
304struct MacroCallVisitor {
305 macro_calls: Vec<MacroCall>,
306 current_file: String,
307}
308impl MacroCallVisitor {
309 fn new(file_path: String) -> Self {
310 Self {
311 macro_calls: Vec::new(),
312 current_file: file_path,
313 }
314 }
315}
316impl<'ast> Visit<'ast> for MacroCallVisitor {
317 fn visit_macro(&mut self, node: &'ast Macro) {
318 if let Some(last_segment) = node.path.segments.last() {
319 let macro_name = last_segment.ident.to_string();
320 let args = quote!(# node).to_string();
321 self.macro_calls
322 .push(MacroCall {
323 name: macro_name,
324 args: format!("({})", args),
325 span_placeholder: "span_info_unavailable".to_string(),
326 file: self.current_file.clone(),
327 line: 0,
328 column: 0,
329 });
330 }
331 syn::visit::visit_macro(self, node);
332 }
333}
334impl Tool for MacroExpandTool {
335 fn name(&self) -> &'static str {
336 "macro-expand"
337 }
338 fn description(&self) -> &'static str {
339 "Better macro expansion viewer with step-by-step expansion and syntax highlighting"
340 }
341 fn command(&self) -> Command {
342 Command::new(self.name())
343 .about(self.description())
344 .long_about(
345 "Provide a better macro expansion viewer with syntax highlighting, step-by-step expansion, and interactive exploration.\n\
346 \n\
347 This tool helps you understand complex macros by:\n\
348 ⢠Expanding procedural and declarative macros\n\
349 ⢠Showing intermediate expansion steps\n\
350 ⢠Syntax highlighting for expanded code\n\
351 ⢠Comparing original vs expanded code\n\
352 \n\
353 EXAMPLES:\n\
354 cm tool macro-expand --input src/lib.rs --step-by-step\n\
355 cm tool macro-expand --input src/main.rs --macro my_macro --highlight\n\
356 cm tool macro-expand --input src/lib.rs --diff --validate",
357 )
358 .args(
359 &[
360 Arg::new("input")
361 .long("input")
362 .short('i')
363 .help("Input Rust file to analyze")
364 .required(true),
365 Arg::new("macro")
366 .long("macro")
367 .short('m')
368 .help("Specific macro to expand (expand all if not specified)"),
369 Arg::new("step-by-step")
370 .long("step-by-step")
371 .help("Show step-by-step expansion")
372 .action(clap::ArgAction::SetTrue),
373 Arg::new("highlight")
374 .long("highlight")
375 .help("Highlight expanded code with syntax highlighting")
376 .action(clap::ArgAction::SetTrue),
377 Arg::new("diff")
378 .long("diff")
379 .help("Show diff between original and expanded")
380 .action(clap::ArgAction::SetTrue),
381 Arg::new("validate")
382 .long("validate")
383 .help("Validate that expanded code compiles")
384 .action(clap::ArgAction::SetTrue),
385 Arg::new("interactive")
386 .long("interactive")
387 .help("Interactive macro exploration mode")
388 .action(clap::ArgAction::SetTrue),
389 Arg::new("output")
390 .long("output")
391 .short('o')
392 .help("Output file for expanded code")
393 .default_value("expanded.rs"),
394 Arg::new("format")
395 .long("format")
396 .short('f')
397 .help("Output format: rust, html, json")
398 .default_value("rust"),
399 ],
400 )
401 .args(&super::common_options())
402 }
403 fn execute(&self, matches: &ArgMatches) -> Result<()> {
404 let input = matches.get_one::<String>("input").unwrap();
405 let specific_macro = matches.get_one::<String>("macro");
406 let step_by_step = matches.get_flag("step-by-step");
407 let highlight = matches.get_flag("highlight");
408 let diff = matches.get_flag("diff");
409 let validate = matches.get_flag("validate");
410 let interactive = matches.get_flag("interactive");
411 let output_file = matches.get_one::<String>("output").unwrap();
412 let format = matches.get_one::<String>("format").unwrap();
413 let verbose = matches.get_flag("verbose");
414 let dry_run = matches.get_flag("dry-run");
415 let output_format = parse_output_format(matches);
416 if dry_run {
417 println!("š Would analyze macro expansion in: {}", input);
418 return Ok(());
419 }
420 match output_format {
421 OutputFormat::Human => {
422 println!(
423 "š {} - {}", "Macro Expansion Analysis".bold(), self.description()
424 .cyan()
425 );
426 match self.parse_macro_calls(input) {
427 Ok(macro_calls) => {
428 println!("\nš File: {}", input.bold());
429 println!(
430 "š Macros Found: {}", macro_calls.len().to_string().cyan()
431 );
432 if macro_calls.is_empty() {
433 println!("ā
No macro calls found in the file.");
434 return Ok(());
435 }
436 let filtered_calls: Vec<_> = if let Some(macro_name) = specific_macro {
437 macro_calls
438 .into_iter()
439 .filter(|call| call.name == *macro_name)
440 .collect()
441 } else {
442 macro_calls
443 };
444 if filtered_calls.is_empty() {
445 if let Some(name) = specific_macro {
446 println!(
447 "ā Macro '{}' not found in the file.", name.red()
448 );
449 }
450 return Ok(());
451 }
452 let mut macro_types = HashMap::new();
453 for call in &filtered_calls {
454 let macro_type = self.classify_macro(&call.name);
455 macro_types
456 .entry(format!("{:?}", macro_type))
457 .or_insert_with(Vec::new)
458 .push(call.name.clone());
459 }
460 println!("\nš Expansion Summary:");
461 for (type_name, names) in ¯o_types {
462 println!(" ⢠{}: {}", type_name, names.len());
463 }
464 for (i, call) in filtered_calls.iter().enumerate() {
465 println!("\nš¬ Macro: {}!{}", call.name.bold(), call.args);
466 println!(
467 " š Line {}, Column {}", call.line, call.column
468 );
469 if step_by_step {
470 match self.expand_macro_step_by_step(call) {
471 Ok(steps) => {
472 for (step_num, step) in steps.iter().enumerate() {
473 println!(
474 "\n Step {} - {}:", step_num + 1, step.description.bold()
475 );
476 println!(" ```rust");
477 for line in step.after.lines() {
478 println!(" {}", line);
479 }
480 println!(" ```");
481 }
482 }
483 Err(e) => {
484 if verbose {
485 println!(" ā ļø Could not expand step by step: {}", e);
486 }
487 }
488 }
489 }
490 let deps = self.extract_macro_dependencies(call);
491 if !deps.is_empty() {
492 println!("\n š Dependencies:");
493 for dep in &deps {
494 println!(
495 " ⢠{} ({})", dep.name.cyan(), dep.dependency_type
496 );
497 }
498 }
499 if validate {
500 if let Ok(steps) = self.expand_macro_step_by_step(call) {
501 if let Some(final_step) = steps.last() {
502 match self.validate_macro_expansion(&final_step.after) {
503 Ok(issues) => {
504 if issues.is_empty() {
505 println!(
506 "\n ā
Validation: Expanded code compiles successfully"
507 );
508 } else {
509 for issue in issues {
510 let severity_color = match issue.severity.as_str() {
511 "High" => issue.severity.red().bold(),
512 "Medium" => issue.severity.yellow().bold(),
513 _ => issue.severity.green().bold(),
514 };
515 println!(
516 "\n šØ Validation Issue [{}]: {}", severity_color, issue
517 .issue
518 );
519 println!(" š” {}", issue.suggestion.cyan());
520 }
521 }
522 }
523 Err(e) => {
524 if verbose {
525 println!("\n ā ļø Validation failed: {}", e);
526 }
527 }
528 }
529 }
530 }
531 }
532 if let Ok(steps) = self.expand_macro_step_by_step(call) {
533 if let Some(final_step) = steps.last() {
534 let original_size = call.args.len();
535 let expanded_size = final_step.after.len();
536 let ratio = if original_size > 0 {
537 expanded_size as f64 / original_size as f64
538 } else {
539 1.0
540 };
541 println!("\nš Expansion Metrics:");
542 println!(" ⢠Original size: {} chars", original_size);
543 println!(" ⢠Expanded size: {} chars", expanded_size);
544 println!(" ⢠Expansion ratio: {:.1}x", ratio);
545 }
546 }
547 if i < filtered_calls.len() - 1 {
548 println!("{}", "ā".repeat(50).blue());
549 }
550 }
551 if let Ok(expanded) = self.generate_expanded_code(input) {
552 if diff {
553 println!("\nš Original vs Expanded Comparison:");
554 match self
555 .highlight_differences(
556 &fs::read_to_string(input).unwrap_or_default(),
557 &expanded,
558 )
559 {
560 Ok(diff_output) => {
561 for line in diff_output.lines().take(20) {
562 println!(" {}", line);
563 }
564 if diff_output.lines().count() > 20 {
565 println!(
566 " ... (truncated - {} more lines)", diff_output.lines()
567 .count() - 20
568 );
569 }
570 }
571 Err(e) => {
572 if verbose {
573 println!(" ā ļø Could not generate diff: {}", e);
574 }
575 }
576 }
577 }
578 if let Err(e) = fs::write(output_file, &expanded) {
579 if verbose {
580 println!("ā ļø Could not write to output file: {}", e);
581 }
582 } else if verbose {
583 println!(
584 "\nš¾ Expanded code written to: {}", output_file
585 );
586 }
587 }
588 }
589 Err(e) => {
590 return Err(
591 ToolError::ExecutionFailed(
592 format!("Failed to analyze macros: {}", e),
593 ),
594 );
595 }
596 }
597 }
598 OutputFormat::Json => {
599 let macro_calls = self.parse_macro_calls(input)?;
600 let mut json_output = serde_json::json!(
601 { "file" : input, "macro_calls" : macro_calls.len(), "macros" :
602 macro_calls, }
603 );
604 if let Ok(expanded) = self.generate_expanded_code(input) {
605 json_output["expanded_code"] = expanded.into();
606 }
607 println!("{}", serde_json::to_string_pretty(& json_output).unwrap());
608 }
609 OutputFormat::Table => {
610 let macro_calls = self.parse_macro_calls(input)?;
611 println!(
612 "āā Macro Expansion Analysis āāāāāāāāāāāāāāāāāāāāāāā"
613 );
614 println!("ā File: {:<45} ā", input);
615 println!("ā Macros Found: {:<36} ā", macro_calls.len());
616 for call in macro_calls.iter().take(5) {
617 println!(
618 "ā ⢠{:<45} ā", format!("{}!{}", call.name, call.args)
619 );
620 }
621 if macro_calls.len() > 5 {
622 println!(
623 "ā ... and {} more {:<32} ā", macro_calls.len() - 5, ""
624 );
625 }
626 println!(
627 "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
628 );
629 }
630 }
631 Ok(())
632 }
633}