Skip to main content

unistructgen_core/
codegen.rs

1//! Code generator trait and related types
2//!
3//! This module defines the core trait that all code generators must implement.
4//! It provides a unified interface for generating code in different programming
5//! languages from the Intermediate Representation (IR).
6
7use crate::IRModule;
8use std::error::Error as StdError;
9
10/// Result type alias for code generation operations
11pub type CodegenResult<T> = std::result::Result<T, Box<dyn StdError + Send + Sync>>;
12
13/// Core trait for all code generators
14///
15/// Implement this trait to add support for generating code in new languages.
16/// The generator is responsible for converting an IR module into syntactically
17/// correct code in the target language.
18///
19/// # Type Parameters
20///
21/// * `Error` - The error type produced by this generator. Must implement
22///   `std::error::Error + Send + Sync + 'static` for composability.
23///
24/// # Examples
25///
26/// ```ignore
27/// use unistructgen_core::{CodeGenerator, IRModule};
28///
29/// struct MyGenerator {
30///     options: MyOptions,
31/// }
32///
33/// impl CodeGenerator for MyGenerator {
34///     type Error = MyGeneratorError;
35///
36///     fn generate(&self, module: &IRModule) -> Result<String, Self::Error> {
37///         // Generate code from IR
38///         todo!()
39///     }
40///
41///     fn language(&self) -> &'static str {
42///         "MyLanguage"
43///     }
44///
45///     fn file_extension(&self) -> &str {
46///         "my"
47///     }
48/// }
49/// ```
50pub trait CodeGenerator {
51    /// The error type this generator produces
52    type Error: StdError + Send + Sync + 'static;
53
54    /// Generate code from an IR module
55    ///
56    /// # Arguments
57    ///
58    /// * `module` - The IR module to generate code from
59    ///
60    /// # Returns
61    ///
62    /// Returns `Ok(String)` containing the generated code on success.
63    ///
64    /// # Errors
65    ///
66    /// Returns `Self::Error` if code generation fails. The error should
67    /// contain detailed information about what went wrong.
68    fn generate(&self, module: &IRModule) -> Result<String, Self::Error>;
69
70    /// Get the target language name
71    ///
72    /// Used for diagnostics and user-facing messages.
73    ///
74    /// # Examples
75    ///
76    /// ```ignore
77    /// assert_eq!(rust_gen.language(), "Rust");
78    /// assert_eq!(typescript_gen.language(), "TypeScript");
79    /// ```
80    fn language(&self) -> &'static str;
81
82    /// Get the file extension for generated code
83    ///
84    /// Used when writing generated code to files.
85    ///
86    /// # Examples
87    ///
88    /// ```ignore
89    /// assert_eq!(rust_gen.file_extension(), "rs");
90    /// assert_eq!(typescript_gen.file_extension(), "ts");
91    /// ```
92    fn file_extension(&self) -> &str;
93
94    /// Validate IR before generation (optional)
95    ///
96    /// Provides a way to check if the IR is compatible with this generator
97    /// before attempting full code generation. Default implementation
98    /// always returns `Ok(())`.
99    ///
100    /// # Arguments
101    ///
102    /// * `module` - The IR module to validate
103    ///
104    /// # Returns
105    ///
106    /// Returns `Ok(())` if IR is valid, `Err` otherwise.
107    fn validate(&self, _module: &IRModule) -> Result<(), Self::Error> {
108        Ok(())
109    }
110
111    /// Format generated code (optional)
112    ///
113    /// Apply language-specific formatting to the generated code.
114    /// Default implementation returns the code unchanged.
115    ///
116    /// # Arguments
117    ///
118    /// * `code` - The generated code to format
119    ///
120    /// # Returns
121    ///
122    /// Returns formatted code on success.
123    fn format(&self, code: String) -> Result<String, Self::Error> {
124        Ok(code)
125    }
126
127    /// Get metadata about the generator (optional)
128    ///
129    /// Returns additional information about the generator's capabilities,
130    /// version, etc. Default implementation returns empty metadata.
131    fn metadata(&self) -> GeneratorMetadata {
132        GeneratorMetadata::default()
133    }
134}
135
136/// Metadata about a code generator
137///
138/// Contains additional information about generator capabilities and configuration.
139#[derive(Debug, Clone, Default)]
140pub struct GeneratorMetadata {
141    /// Generator version
142    pub version: Option<String>,
143    /// Description of what this generator produces
144    pub description: Option<String>,
145    /// Minimum language version required (e.g., "1.70" for Rust)
146    pub min_language_version: Option<String>,
147    /// List of supported features
148    pub features: Vec<String>,
149    /// Additional custom metadata
150    pub custom: std::collections::HashMap<String, String>,
151}
152
153impl GeneratorMetadata {
154    /// Create new empty metadata
155    pub fn new() -> Self {
156        Self::default()
157    }
158
159    /// Set version
160    pub fn with_version(mut self, version: impl Into<String>) -> Self {
161        self.version = Some(version.into());
162        self
163    }
164
165    /// Set description
166    pub fn with_description(mut self, description: impl Into<String>) -> Self {
167        self.description = Some(description.into());
168        self
169    }
170
171    /// Set minimum language version
172    pub fn with_min_language_version(mut self, version: impl Into<String>) -> Self {
173        self.min_language_version = Some(version.into());
174        self
175    }
176
177    /// Add a feature
178    pub fn with_feature(mut self, feature: impl Into<String>) -> Self {
179        self.features.push(feature.into());
180        self
181    }
182
183    /// Add custom metadata
184    pub fn with_custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
185        self.custom.insert(key.into(), value.into());
186        self
187    }
188}
189
190/// Extension trait for CodeGenerator to provide convenience methods
191pub trait CodeGeneratorExt: CodeGenerator {
192    /// Generate and format code in one step
193    fn generate_formatted(&self, module: &IRModule) -> Result<String, Self::Error> {
194        let code = self.generate(module)?;
195        self.format(code)
196    }
197
198    /// Generate code with validation first
199    fn generate_validated(&self, module: &IRModule) -> Result<String, Self::Error> {
200        self.validate(module)?;
201        self.generate(module)
202    }
203
204    /// Generate, validate, and format code
205    fn generate_complete(&self, module: &IRModule) -> Result<String, Self::Error> {
206        self.validate(module)?;
207        let code = self.generate(module)?;
208        self.format(code)
209    }
210
211    /// Generate code and convert to a specific error type
212    fn generate_or<E>(&self, module: &IRModule) -> Result<String, E>
213    where
214        E: From<Self::Error>,
215    {
216        self.generate(module).map_err(E::from)
217    }
218}
219
220// Blanket implementation for all CodeGenerator types
221impl<G: CodeGenerator> CodeGeneratorExt for G {}
222
223/// Helper error type for multi-generator operations
224#[derive(Debug)]
225pub struct MultiGeneratorError {
226    pub generator_name: String,
227    pub message: String,
228}
229
230impl std::fmt::Display for MultiGeneratorError {
231    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232        write!(f, "Generator '{}' failed: {}", self.generator_name, self.message)
233    }
234}
235
236impl StdError for MultiGeneratorError {}
237
238/// Helper for chaining multiple generators
239///
240/// Allows generating code in multiple languages from the same IR.
241/// This is useful for projects that need to generate code in multiple
242/// target languages from a single schema.
243///
244/// # Examples
245///
246/// ```ignore
247/// use unistructgen_core::{MultiGenerator, IRModule};
248///
249/// let multi = MultiGenerator::new()
250///     .add("rust", rust_generator)
251///     .add("typescript", ts_generator);
252///
253/// let results = multi.generate_all(&module)?;
254/// for (name, code) in results {
255///     println!("Generated {} code", name);
256/// }
257/// ```
258pub struct MultiGenerator {
259    generators: Vec<GeneratorEntry>,
260}
261
262struct GeneratorEntry {
263    name: String,
264    generator: Box<dyn GeneratorWrapper>,
265}
266
267trait GeneratorWrapper {
268    fn generate_wrapped(&self, module: &IRModule) -> Result<String, MultiGeneratorError>;
269    #[allow(dead_code)]
270    fn language(&self) -> &'static str;
271    #[allow(dead_code)]
272    fn file_extension(&self) -> &str;
273}
274
275struct GenWrap<G>(G);
276
277impl<G> GeneratorWrapper for GenWrap<G>
278where
279    G: CodeGenerator,
280{
281    fn generate_wrapped(&self, module: &IRModule) -> Result<String, MultiGeneratorError> {
282        self.0.generate(module).map_err(|e| MultiGeneratorError {
283            generator_name: self.0.language().to_string(),
284            message: e.to_string(),
285        })
286    }
287
288    fn language(&self) -> &'static str {
289        self.0.language()
290    }
291
292    fn file_extension(&self) -> &str {
293        self.0.file_extension()
294    }
295}
296
297impl MultiGenerator {
298    /// Create a new multi-generator
299    pub fn new() -> Self {
300        Self {
301            generators: Vec::new(),
302        }
303    }
304
305    /// Add a generator
306    pub fn add<G>(mut self, name: impl Into<String>, generator: G) -> Self
307    where
308        G: CodeGenerator + 'static,
309    {
310        self.generators.push(GeneratorEntry {
311            name: name.into(),
312            generator: Box::new(GenWrap(generator)),
313        });
314        self
315    }
316
317    /// Generate code with all registered generators
318    pub fn generate_all(
319        &self,
320        module: &IRModule,
321    ) -> Result<Vec<(String, String)>, MultiGeneratorError> {
322        let mut results = Vec::new();
323        for entry in &self.generators {
324            let code = entry.generator.generate_wrapped(module)?;
325            results.push((entry.name.clone(), code));
326        }
327        Ok(results)
328    }
329
330    /// Get list of registered generator names
331    pub fn generator_names(&self) -> Vec<&str> {
332        self.generators.iter().map(|e| e.name.as_str()).collect()
333    }
334}
335
336impl Default for MultiGenerator {
337    fn default() -> Self {
338        Self::new()
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345    use crate::{IRModule, IRStruct, IRType};
346
347    // Mock generator for testing
348    struct MockGenerator;
349
350    #[derive(Debug)]
351    struct MockError;
352
353    impl std::fmt::Display for MockError {
354        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355            write!(f, "Mock error")
356        }
357    }
358
359    impl StdError for MockError {}
360
361    impl CodeGenerator for MockGenerator {
362        type Error = MockError;
363
364        fn generate(&self, module: &IRModule) -> Result<String, Self::Error> {
365            Ok(format!("struct {};", module.name))
366        }
367
368        fn language(&self) -> &'static str {
369            "Mock"
370        }
371
372        fn file_extension(&self) -> &str {
373            "mock"
374        }
375    }
376
377    #[test]
378    fn test_generator_trait() {
379        let generator = MockGenerator;
380        let mut module = IRModule::new("Test".to_string());
381        let test_struct = IRStruct::new("Test".to_string());
382        module.add_type(IRType::Struct(test_struct));
383
384        let result = generator.generate(&module);
385        assert!(result.is_ok());
386        assert_eq!(result.unwrap(), "struct Test;");
387    }
388
389    #[test]
390    fn test_generator_metadata() {
391        let generator = MockGenerator;
392        assert_eq!(generator.language(), "Mock");
393        assert_eq!(generator.file_extension(), "mock");
394    }
395
396    #[test]
397    fn test_generator_metadata_builder() {
398        let metadata = GeneratorMetadata::new()
399            .with_version("1.0.0")
400            .with_description("Test generator")
401            .with_min_language_version("2021")
402            .with_feature("generics")
403            .with_custom("author", "test");
404
405        assert_eq!(metadata.version, Some("1.0.0".to_string()));
406        assert_eq!(metadata.description, Some("Test generator".to_string()));
407        assert_eq!(metadata.min_language_version, Some("2021".to_string()));
408        assert_eq!(metadata.features, vec!["generics"]);
409        assert_eq!(metadata.custom.get("author"), Some(&"test".to_string()));
410    }
411
412    #[test]
413    fn test_generator_ext() {
414        let generator = MockGenerator;
415        let mut module = IRModule::new("Test".to_string());
416        let test_struct = IRStruct::new("Test".to_string());
417        module.add_type(IRType::Struct(test_struct));
418
419        let result = generator.generate_validated(&module);
420        assert!(result.is_ok());
421
422        let result = generator.generate_formatted(&module);
423        assert!(result.is_ok());
424
425        let result = generator.generate_complete(&module);
426        assert!(result.is_ok());
427    }
428
429    #[test]
430    fn test_multi_generator() {
431        let multi = MultiGenerator::new()
432            .add("mock1", MockGenerator)
433            .add("mock2", MockGenerator);
434
435        let mut module = IRModule::new("Test".to_string());
436        let test_struct = IRStruct::new("Test".to_string());
437        module.add_type(IRType::Struct(test_struct));
438
439        let results = multi.generate_all(&module).unwrap();
440        assert_eq!(results.len(), 2);
441        assert_eq!(results[0].0, "mock1");
442        assert_eq!(results[1].0, "mock2");
443    }
444}