codeprism_dev_tools/
lib.rs

1//! CodePrism Development Tools
2//!
3//! This crate provides essential debugging and development utilities for CodePrism parser development.
4//! It includes AST visualization tools, parser validation utilities, and an interactive development REPL.
5//!
6//! # Features
7//!
8//! - **AST Visualization**: Pretty-print syntax trees and export GraphViz diagrams
9//! - **Parser Validation**: Comprehensive validation of nodes, edges, and spans
10//! - **Development REPL**: Interactive parser development environment
11//! - **Performance Profiling**: Real-time parsing performance metrics
12//! - **Diff Comparison**: Compare AST changes between parser versions
13//!
14//! # Example
15//!
16//! ```no_run
17//! use codeprism_dev_tools::{AstVisualizer, ParserValidator};
18//!
19//! // Create dev tools
20//! let visualizer = AstVisualizer::new();
21//! let validator = ParserValidator::new();
22//!
23//! // Usage would be with actual ParseResult and source code:
24//! // let tree_output = visualizer.visualize_tree(&parse_result.tree, &source_code)?;
25//! // let report = validator.validate_complete(&parse_result, &source_code)?;
26//! ```
27
28use anyhow::Result;
29use std::path::Path;
30
31pub mod ast_visualizer;
32pub mod dev_repl;
33pub mod diff_comparison;
34pub mod graphviz_export;
35pub mod parser_validator;
36pub mod performance_profiler;
37
38// Re-export main types for convenience
39pub use ast_visualizer::{AstVisualizer, VisualizationFormat};
40pub use dev_repl::{DevRepl, ReplCommand, ReplResult};
41pub use diff_comparison::{AstDiff, DiffReport, DiffType};
42pub use graphviz_export::{EdgeStyle, GraphVizExporter, GraphVizOptions, NodeStyle};
43pub use parser_validator::{ParserValidator, ValidationError, ValidationReport};
44pub use performance_profiler::{MetricType, PerformanceProfiler, ProfilingReport};
45
46/// Main development tools facade providing access to all utilities
47pub struct DevTools {
48    visualizer: AstVisualizer,
49    validator: ParserValidator,
50    profiler: PerformanceProfiler,
51    exporter: GraphVizExporter,
52}
53
54impl DevTools {
55    /// Create a new DevTools instance with default configuration
56    pub fn new() -> Self {
57        Self {
58            visualizer: AstVisualizer::new(),
59            validator: ParserValidator::new(),
60            profiler: PerformanceProfiler::new(),
61            exporter: GraphVizExporter::new(),
62        }
63    }
64
65    /// Create a DevTools instance with custom configuration
66    pub fn with_config(config: DevToolsConfig) -> Self {
67        Self {
68            visualizer: AstVisualizer::with_config(config.visualization),
69            validator: ParserValidator::with_config(config.validation),
70            profiler: PerformanceProfiler::with_config(config.profiling),
71            exporter: GraphVizExporter::with_config(config.graphviz),
72        }
73    }
74
75    /// Get access to the AST visualizer
76    pub fn visualizer(&self) -> &AstVisualizer {
77        &self.visualizer
78    }
79
80    /// Get access to the parser validator
81    pub fn validator(&self) -> &ParserValidator {
82        &self.validator
83    }
84
85    /// Get access to the performance profiler
86    pub fn profiler(&self) -> &PerformanceProfiler {
87        &self.profiler
88    }
89
90    /// Get access to the GraphViz exporter
91    pub fn exporter(&self) -> &GraphVizExporter {
92        &self.exporter
93    }
94
95    /// Start an interactive development REPL
96    pub async fn start_repl(&self, language: Option<&str>) -> Result<()> {
97        let mut repl = DevRepl::new(language)?;
98        repl.set_visualizer(self.visualizer.clone());
99        repl.set_validator(self.validator.clone());
100        repl.set_profiler(self.profiler.clone());
101        repl.set_exporter(self.exporter.clone());
102        repl.run().await
103    }
104
105    /// Perform a comprehensive analysis of a parse result
106    pub fn analyze_parse_result(
107        &self,
108        parse_result: &codeprism_core::ParseResult,
109        source: &str,
110    ) -> Result<AnalysisReport> {
111        let mut report = AnalysisReport::new();
112
113        // Validate the parse result
114        let validation = self.validator.validate_complete(parse_result, source)?;
115        report.validation = Some(validation);
116
117        // Generate AST visualization
118        let visualization = self.visualizer.visualize_tree(&parse_result.tree, source)?;
119        report.visualization = Some(visualization);
120
121        // Export GraphViz if requested
122        if !parse_result.nodes.is_empty() {
123            let graphviz = self
124                .exporter
125                .export_nodes_and_edges(&parse_result.nodes, &parse_result.edges)?;
126            report.graphviz = Some(graphviz);
127        }
128
129        Ok(report)
130    }
131
132    /// Compare two parse results and generate a diff report
133    pub fn compare_parse_results(
134        &self,
135        old_result: &codeprism_core::ParseResult,
136        new_result: &codeprism_core::ParseResult,
137        source: &str,
138    ) -> Result<DiffReport> {
139        let diff = AstDiff::new();
140        diff.compare(old_result, new_result, source)
141    }
142}
143
144impl Default for DevTools {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150/// Configuration for development tools
151#[derive(Debug, Clone, Default)]
152pub struct DevToolsConfig {
153    pub visualization: ast_visualizer::VisualizationConfig,
154    pub validation: parser_validator::ValidationConfig,
155    pub profiling: performance_profiler::ProfilingConfig,
156    pub graphviz: graphviz_export::GraphVizConfig,
157}
158
159/// Comprehensive analysis report combining all dev tools outputs
160#[derive(Debug)]
161pub struct AnalysisReport {
162    pub validation: Option<ValidationReport>,
163    pub visualization: Option<String>,
164    pub graphviz: Option<String>,
165    pub profiling: Option<ProfilingReport>,
166    pub diff: Option<DiffReport>,
167}
168
169impl AnalysisReport {
170    pub fn new() -> Self {
171        Self {
172            validation: None,
173            visualization: None,
174            graphviz: None,
175            profiling: None,
176            diff: None,
177        }
178    }
179
180    /// Check if the analysis indicates any issues
181    pub fn has_issues(&self) -> bool {
182        if let Some(validation) = &self.validation {
183            if !validation.is_valid() {
184                return true;
185            }
186        }
187        false
188    }
189
190    /// Get a summary of all issues found
191    pub fn issues_summary(&self) -> Vec<String> {
192        let mut issues = Vec::new();
193
194        if let Some(validation) = &self.validation {
195            if !validation.is_valid() {
196                issues.extend(validation.errors().iter().map(|e| e.to_string()));
197            }
198        }
199
200        issues
201    }
202
203    /// Generate a formatted report suitable for display
204    pub fn format_report(&self) -> String {
205        let mut output = String::new();
206
207        output.push_str("=== CodePrism Parser Analysis Report ===\n\n");
208
209        if let Some(validation) = &self.validation {
210            output.push_str("## Validation Results\n");
211            if validation.is_valid() {
212                output.push_str("✅ All validation checks passed\n");
213            } else {
214                output.push_str("❌ Validation errors found:\n");
215                for error in validation.errors() {
216                    output.push_str(&format!("  - {error}\n"));
217                }
218            }
219            output.push('\n');
220        }
221
222        if let Some(visualization) = &self.visualization {
223            output.push_str("## AST Visualization\n");
224            output.push_str(visualization);
225            output.push_str("\n\n");
226        }
227
228        if let Some(profiling) = &self.profiling {
229            output.push_str("## Performance Metrics\n");
230            output.push_str(&profiling.format_summary());
231            output.push('\n');
232        }
233
234        if let Some(diff) = &self.diff {
235            output.push_str("## AST Differences\n");
236            output.push_str(&diff.format_report());
237            output.push('\n');
238        }
239
240        output
241    }
242}
243
244impl Default for AnalysisReport {
245    fn default() -> Self {
246        Self::new()
247    }
248}
249
250/// Error types for development tools
251#[derive(thiserror::Error, Debug)]
252pub enum DevToolsError {
253    #[error("Visualization error: {0}")]
254    Visualization(String),
255
256    #[error("Validation error: {0}")]
257    Validation(String),
258
259    #[error("REPL error: {0}")]
260    Repl(String),
261
262    #[error("GraphViz export error: {0}")]
263    GraphViz(String),
264
265    #[error("Performance profiling error: {0}")]
266    Profiling(String),
267
268    #[error("IO error: {0}")]
269    Io(#[from] std::io::Error),
270
271    #[error("Parse error: {0}")]
272    Parse(#[from] codeprism_core::Error),
273}
274
275/// Utility functions for development tools
276pub mod utils {
277    use super::*;
278
279    /// Load a source file and return its contents
280    pub fn load_source_file<P: AsRef<Path>>(path: P) -> Result<String> {
281        std::fs::read_to_string(path.as_ref())
282            .map_err(|e| anyhow::anyhow!("Failed to load source file: {}", e))
283    }
284
285    /// Detect the language from a file extension
286    pub fn detect_language_from_extension<P: AsRef<Path>>(path: P) -> Option<&'static str> {
287        match path.as_ref().extension()?.to_str()? {
288            "rs" => Some("rust"),
289            "py" => Some("python"),
290            "js" | "mjs" => Some("javascript"),
291            "ts" => Some("typescript"),
292            "java" => Some("java"),
293            _ => None,
294        }
295    }
296
297    /// Format file size in human-readable format
298    pub fn format_file_size(bytes: u64) -> String {
299        const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
300        let mut size = bytes as f64;
301        let mut unit_index = 0;
302
303        while size >= 1024.0 && unit_index < UNITS.len() - 1 {
304            size /= 1024.0;
305            unit_index += 1;
306        }
307
308        if unit_index == 0 {
309            format!("{} {}", bytes, UNITS[unit_index])
310        } else {
311            format!("{:.1} {}", size, UNITS[unit_index])
312        }
313    }
314
315    /// Format duration in human-readable format
316    pub fn format_duration(duration: std::time::Duration) -> String {
317        let total_ms = duration.as_millis();
318
319        if total_ms < 1000 {
320            format!("{total_ms}ms")
321        } else if total_ms < 60_000 {
322            format!("{:.2}s", duration.as_secs_f64())
323        } else {
324            let minutes = total_ms / 60_000;
325            let seconds = (total_ms % 60_000) as f64 / 1000.0;
326            format!("{minutes}m {seconds:.1}s")
327        }
328    }
329}