Skip to main content

depyler_core/
backend.rs

1//! Backend trait definitions for multiple transpilation targets
2//!
3//! This module provides the extensible backend system introduced in v3.0.0,
4//! allowing Depyler to target multiple output languages beyond Rust.
5//!
6//! # Example Implementation
7//!
8//! ```rust,ignore
9//! use depyler_core::{TranspilationBackend, HirModule, TranspileError, ValidationError};
10//!
11//! struct MyCustomBackend;
12//!
13//! impl TranspilationBackend for MyCustomBackend {
14//!     fn transpile(&self, hir: &HirModule) -> Result<String, TranspileError> {
15//!         // Transform HIR to your target language
16//!         Ok("// Generated code".to_string())
17//!     }
18//!     
19//!     fn validate_output(&self, code: &str) -> Result<(), ValidationError> {
20//!         // Validate generated code
21//!         Ok(())
22//!     }
23//!     
24//!     fn target_name(&self) -> &str {
25//!         "custom"
26//!     }
27//!     
28//!     fn file_extension(&self) -> &str {
29//!         "custom"
30//!     }
31//! }
32//! ```
33
34use crate::error::TranspileError;
35use crate::hir::HirModule;
36use anyhow::Result;
37use std::fmt;
38
39/// Validation error types
40#[derive(Debug, thiserror::Error)]
41pub enum ValidationError {
42    #[error("Invalid syntax: {0}")]
43    InvalidSyntax(String),
44
45    #[error("Type error: {0}")]
46    TypeError(String),
47
48    #[error("Unsupported feature: {0}")]
49    UnsupportedFeature(String),
50}
51
52/// Trait for different transpilation backends
53pub trait TranspilationBackend: Send + Sync {
54    /// Transpile HIR to target language
55    #[allow(clippy::result_large_err)]
56    fn transpile(&self, hir: &HirModule) -> Result<String, TranspileError>;
57
58    /// Validate generated code
59    fn validate_output(&self, code: &str) -> Result<(), ValidationError>;
60
61    /// Optimize HIR before transpilation
62    fn optimize(&self, hir: &HirModule) -> HirModule {
63        hir.clone()
64    }
65
66    /// Get target name
67    fn target_name(&self) -> &str;
68
69    /// Get file extension for target language
70    fn file_extension(&self) -> &str;
71}
72
73/// Transpilation target enumeration
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
75pub enum TranspilationTarget {
76    /// Generate idiomatic Rust code (default)
77    #[default]
78    Rust,
79
80    /// Generate Ruchy script format
81    #[cfg(feature = "ruchy")]
82    Ruchy,
83}
84
85impl fmt::Display for TranspilationTarget {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            Self::Rust => write!(f, "rust"),
89            #[cfg(feature = "ruchy")]
90            Self::Ruchy => write!(f, "ruchy"),
91        }
92    }
93}
94
95impl TranspilationTarget {
96    /// Get file extension for target
97    pub fn file_extension(&self) -> &str {
98        match self {
99            Self::Rust => "rs",
100            #[cfg(feature = "ruchy")]
101            Self::Ruchy => "ruchy",
102        }
103    }
104}
105
106impl std::str::FromStr for TranspilationTarget {
107    type Err = String;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        match s.to_lowercase().as_str() {
111            "rust" | "rs" => Ok(Self::Rust),
112            #[cfg(feature = "ruchy")]
113            "ruchy" | "ruc" => Ok(Self::Ruchy),
114            _ => Err(format!("Unknown transpilation target: {}", s)),
115        }
116    }
117}
118
119// Re-export for convenience
120pub use crate::error::TranspileError as BackendError;
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use std::str::FromStr;
126
127    // ============================================================================
128    // ValidationError Tests
129    // ============================================================================
130
131    #[test]
132    fn test_validation_error_invalid_syntax() {
133        let err = ValidationError::InvalidSyntax("missing semicolon".to_string());
134        assert_eq!(err.to_string(), "Invalid syntax: missing semicolon");
135    }
136
137    #[test]
138    fn test_validation_error_type_error() {
139        let err = ValidationError::TypeError("expected i32, found String".to_string());
140        assert_eq!(err.to_string(), "Type error: expected i32, found String");
141    }
142
143    #[test]
144    fn test_validation_error_unsupported_feature() {
145        let err = ValidationError::UnsupportedFeature("async generators".to_string());
146        assert_eq!(err.to_string(), "Unsupported feature: async generators");
147    }
148
149    // ============================================================================
150    // TranspilationTarget Tests
151    // ============================================================================
152
153    #[test]
154    fn test_transpilation_target_default() {
155        let target = TranspilationTarget::default();
156        assert_eq!(target, TranspilationTarget::Rust);
157    }
158
159    #[test]
160    fn test_transpilation_target_display_rust() {
161        let target = TranspilationTarget::Rust;
162        assert_eq!(target.to_string(), "rust");
163    }
164
165    #[test]
166    #[cfg(feature = "ruchy")]
167    fn test_transpilation_target_display_ruchy() {
168        let target = TranspilationTarget::Ruchy;
169        assert_eq!(target.to_string(), "ruchy");
170    }
171
172    #[test]
173    fn test_transpilation_target_file_extension_rust() {
174        let target = TranspilationTarget::Rust;
175        assert_eq!(target.file_extension(), "rs");
176    }
177
178    #[test]
179    #[cfg(feature = "ruchy")]
180    fn test_transpilation_target_file_extension_ruchy() {
181        let target = TranspilationTarget::Ruchy;
182        assert_eq!(target.file_extension(), "ruchy");
183    }
184
185    #[test]
186    fn test_transpilation_target_from_str_rust() {
187        let target = TranspilationTarget::from_str("rust").unwrap();
188        assert_eq!(target, TranspilationTarget::Rust);
189    }
190
191    #[test]
192    fn test_transpilation_target_from_str_rs() {
193        let target = TranspilationTarget::from_str("rs").unwrap();
194        assert_eq!(target, TranspilationTarget::Rust);
195    }
196
197    #[test]
198    fn test_transpilation_target_from_str_rust_uppercase() {
199        let target = TranspilationTarget::from_str("RUST").unwrap();
200        assert_eq!(target, TranspilationTarget::Rust);
201    }
202
203    #[test]
204    #[cfg(feature = "ruchy")]
205    fn test_transpilation_target_from_str_ruchy() {
206        let target = TranspilationTarget::from_str("ruchy").unwrap();
207        assert_eq!(target, TranspilationTarget::Ruchy);
208    }
209
210    #[test]
211    #[cfg(feature = "ruchy")]
212    fn test_transpilation_target_from_str_ruc() {
213        let target = TranspilationTarget::from_str("ruc").unwrap();
214        assert_eq!(target, TranspilationTarget::Ruchy);
215    }
216
217    #[test]
218    fn test_transpilation_target_from_str_invalid() {
219        let result = TranspilationTarget::from_str("python");
220        assert!(result.is_err());
221        assert_eq!(result.unwrap_err(), "Unknown transpilation target: python");
222    }
223
224    #[test]
225    fn test_transpilation_target_from_str_empty() {
226        let result = TranspilationTarget::from_str("");
227        assert!(result.is_err());
228    }
229
230    // ============================================================================
231    // TranspileError Extension Tests
232    // ============================================================================
233
234    #[test]
235    fn test_backend_error() {
236        let err = TranspileError::backend_error("backend initialization failed");
237        // Check error kind
238        match err.kind {
239            crate::error::ErrorKind::CodeGenerationError(ref msg) => {
240                assert_eq!(msg, "backend initialization failed");
241            }
242            _ => panic!("Expected CodeGenerationError"),
243        }
244    }
245
246    #[test]
247    fn test_transform_error() {
248        let err = TranspileError::transform_error("AST transformation failed");
249        // Check error kind and message format
250        match err.kind {
251            crate::error::ErrorKind::CodeGenerationError(ref msg) => {
252                assert!(msg.contains("Transformation failed"));
253                assert!(msg.contains("AST transformation failed"));
254            }
255            _ => panic!("Expected CodeGenerationError"),
256        }
257    }
258
259    #[test]
260    fn test_optimization_error() {
261        let err = TranspileError::optimization_error("constant folding failed");
262        // Check error kind and message format
263        match err.kind {
264            crate::error::ErrorKind::InternalError(ref msg) => {
265                assert!(msg.contains("Optimization failed"));
266                assert!(msg.contains("constant folding failed"));
267            }
268            _ => panic!("Expected InternalError"),
269        }
270    }
271
272    #[test]
273    fn test_backend_error_with_string() {
274        let err = TranspileError::backend_error(String::from("dynamic error message"));
275        // Check error kind
276        match err.kind {
277            crate::error::ErrorKind::CodeGenerationError(ref msg) => {
278                assert_eq!(msg, "dynamic error message");
279            }
280            _ => panic!("Expected CodeGenerationError"),
281        }
282    }
283
284    // ============================================================================
285    // TranspilationTarget Trait Coverage
286    // ============================================================================
287
288    #[test]
289    fn test_transpilation_target_clone() {
290        let target = TranspilationTarget::Rust;
291        let cloned = target;
292        assert_eq!(target, cloned);
293    }
294
295    #[test]
296    fn test_transpilation_target_debug() {
297        let target = TranspilationTarget::Rust;
298        let debug_str = format!("{:?}", target);
299        assert!(debug_str.contains("Rust"));
300    }
301
302    #[test]
303    fn test_transpilation_target_eq() {
304        let target1 = TranspilationTarget::Rust;
305        let target2 = TranspilationTarget::Rust;
306        assert_eq!(target1, target2);
307    }
308
309    #[test]
310    #[cfg(feature = "ruchy")]
311    fn test_transpilation_target_ne() {
312        let target1 = TranspilationTarget::Rust;
313        let target2 = TranspilationTarget::Ruchy;
314        assert_ne!(target1, target2);
315    }
316}