dampen_core/codegen/
mod.rs

1//! Code generation for production builds
2//!
3//! This module generates static Rust code from Dampen UI definitions.
4//! The generated code has zero runtime overhead compared to hand-written Iced code.
5//!
6//! # Overview
7//!
8//! Code generation is used in production mode to eliminate runtime parsing:
9//! 1. Parse `.dampen` files at build time
10//! 2. Generate Rust code with `generate_application()`
11//! 3. Include generated code via `include!(concat!(env!("OUT_DIR"), "/ui_generated.rs"))`
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! // At build time:
17//! let doc = parse(ui_xml)?;
18//! let output = generate_application(&doc, "Model", "Message", &handlers)?;
19//! fs::write("ui_generated.rs", output.code)?;
20//!
21//! // At runtime:
22//! include!("ui_generated.rs");
23//! // No XML parsing, no runtime overhead!
24//! ```
25//!
26//! # Generated Code Structure
27//!
28//! The generated code includes:
29//! - Message enum from handler signatures
30//! - `impl Application for Model` with `view()` and `update()` methods
31//! - Inlined widget tree with evaluated bindings
32
33pub mod application;
34pub mod bindings;
35pub mod config;
36pub mod handlers;
37pub mod theme;
38pub mod update;
39pub mod view;
40
41use crate::DampenDocument;
42use crate::HandlerSignature;
43use proc_macro2::TokenStream;
44use quote::quote;
45use std::path::PathBuf;
46use std::time::SystemTime;
47
48/// Handler signature classification for code generation
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum HandlerSignatureType {
51    /// fn(&mut Model) -> ()
52    /// Simple handler with no additional parameters
53    Simple,
54
55    /// fn(&mut Model, T) -> ()
56    /// Handler that receives a value from the UI element
57    WithValue,
58
59    /// fn(&mut Model) -> Command<Message>
60    /// Handler that returns a command for side effects
61    WithCommand,
62}
63
64/// Metadata structure emitted by #[ui_handler] macro for build-time registration
65#[derive(Debug, Clone)]
66pub struct HandlerInfo {
67    /// Unique handler name referenced in XML
68    pub name: &'static str,
69
70    /// Handler signature classification
71    pub signature_type: HandlerSignatureType,
72
73    /// Parameter type names for validation
74    pub param_types: &'static [&'static str],
75
76    /// Return type name
77    pub return_type: &'static str,
78
79    /// Source file location for error messages
80    pub source_file: &'static str,
81
82    /// Source line number
83    pub source_line: u32,
84}
85
86/// Result of code generation
87///
88/// Contains the generated Rust code and any warnings or notes.
89#[derive(Debug)]
90pub struct CodegenOutput {
91    /// Generated Rust code
92    pub code: String,
93    /// Any warnings or notes
94    pub warnings: Vec<String>,
95}
96
97/// Output structure from code generation
98#[derive(Debug, Clone)]
99pub struct GeneratedApplication {
100    /// Generated Rust code as string
101    pub code: String,
102
103    /// List of handler names discovered
104    pub handlers: Vec<String>,
105
106    /// List of widget types used
107    pub widgets: Vec<String>,
108
109    /// Any warnings generated during code gen
110    pub warnings: Vec<String>,
111}
112
113/// Container for generated Rust code with metadata
114///
115/// This structure holds generated code along with metadata about the source
116/// file and validation status. It provides methods for validating and formatting
117/// the generated code.
118#[derive(Debug, Clone)]
119pub struct GeneratedCode {
120    /// Generated Rust source code
121    pub code: String,
122
123    /// Module path (e.g., "ui_window")
124    pub module_name: String,
125
126    /// Source .dampen file path
127    pub source_file: PathBuf,
128
129    /// Generated at timestamp
130    pub timestamp: SystemTime,
131
132    /// Validation status
133    pub validated: bool,
134}
135
136impl GeneratedCode {
137    /// Create a new GeneratedCode instance
138    ///
139    /// # Arguments
140    /// * `code` - The generated Rust source code
141    /// * `module_name` - Module name (e.g., "ui_window")
142    /// * `source_file` - Path to the source .dampen file
143    ///
144    /// # Returns
145    /// A new GeneratedCode instance with validated set to false
146    pub fn new(code: String, module_name: String, source_file: PathBuf) -> Self {
147        Self {
148            code,
149            module_name,
150            source_file,
151            timestamp: SystemTime::now(),
152            validated: false,
153        }
154    }
155
156    /// Validate syntax by parsing with syn
157    ///
158    /// # Returns
159    /// Ok(()) if the code is valid Rust, Err with message otherwise
160    pub fn validate(&mut self) -> Result<(), String> {
161        match syn::parse_file(&self.code) {
162            Ok(_) => {
163                self.validated = true;
164                Ok(())
165            }
166            Err(e) => Err(format!("Syntax validation failed: {}", e)),
167        }
168    }
169
170    /// Format code with prettyplease
171    ///
172    /// # Returns
173    /// Ok(()) if formatting succeeded, Err with message otherwise
174    pub fn format(&mut self) -> Result<(), String> {
175        // First parse the code
176        match syn::parse_file(&self.code) {
177            Ok(syntax_tree) => {
178                // Format using prettyplease
179                self.code = prettyplease::unparse(&syntax_tree);
180                Ok(())
181            }
182            Err(e) => Err(format!("Failed to parse code for formatting: {}", e)),
183        }
184    }
185
186    /// Write to output directory
187    ///
188    /// # Arguments
189    /// * `path` - Path to write the generated code to
190    ///
191    /// # Returns
192    /// Ok(()) if write succeeded, Err with IO error otherwise
193    pub fn write_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
194        std::fs::write(path, &self.code)
195    }
196}
197
198/// Generate complete application code from a Dampen document
199///
200/// This is the main entry point for code generation. It orchestrates
201/// the generation of all necessary components.
202///
203/// # Arguments
204///
205/// * `document` - Parsed Dampen document
206/// * `model_name` - Name of the model struct (e.g., "Model")
207/// * `message_name` - Name of the message enum (e.g., "Message")
208/// * `handlers` - List of handler signatures
209///
210/// # Returns
211///
212/// `Ok(CodegenOutput)` with generated code and warnings
213///
214/// # Errors
215///
216/// Returns `CodegenError` if:
217/// - Handler validation fails
218/// - Widget generation fails
219pub fn generate_application(
220    document: &DampenDocument,
221    model_name: &str,
222    message_name: &str,
223    handlers: &[HandlerSignature],
224) -> Result<CodegenOutput, CodegenError> {
225    let warnings = Vec::new();
226
227    let message_enum = generate_message_enum(handlers);
228
229    let view_fn = view::generate_view(document, model_name, message_name)?;
230
231    let update_match_arms = update::generate_update_match_arms(handlers, message_name)?;
232
233    let model_ident = syn::Ident::new(model_name, proc_macro2::Span::call_site());
234    let message_ident = syn::Ident::new(message_name, proc_macro2::Span::call_site());
235
236    let combined = quote! {
237        use crate::ui::window::{self, #model_ident};
238        use iced::{Element, Task, executor};
239
240        #message_enum
241
242        pub fn new_model() -> (#model_ident, Task<#message_ident>) {
243            (#model_ident::default(), Task::none())
244        }
245
246        pub fn update_model(model: &mut #model_ident, message: #message_ident) -> Task<#message_ident> {
247            #update_match_arms
248        }
249
250        pub fn view_model(model: &#model_ident) -> Element<'_, #message_ident> {
251            let count = &model.count;
252            #view_fn
253        }
254    };
255
256    Ok(CodegenOutput {
257        code: combined.to_string(),
258        warnings,
259    })
260}
261
262/// Generate Message enum from handler signatures
263fn generate_message_enum(handlers: &[HandlerSignature]) -> TokenStream {
264    if handlers.is_empty() {
265        return quote! {
266            #[derive(Clone, Debug)]
267            pub enum Message {}
268        };
269    }
270
271    let variants: Vec<_> = handlers
272        .iter()
273        .map(|h| {
274            // Convert snake_case to UpperCamelCase
275            let variant_name = to_upper_camel_case(&h.name);
276            let ident = syn::Ident::new(&variant_name, proc_macro2::Span::call_site());
277
278            if let Some(param_type) = &h.param_type {
279                let type_ident = syn::Ident::new(param_type, proc_macro2::Span::call_site());
280                quote! { #ident(#type_ident) }
281            } else {
282                quote! { #ident }
283            }
284        })
285        .collect();
286
287    quote! {
288        #[derive(Clone, Debug)]
289        pub enum Message {
290            #(#variants),*
291        }
292    }
293}
294
295/// Convert snake_case to UpperCamelCase
296fn to_upper_camel_case(s: &str) -> String {
297    let mut result = String::new();
298    let mut capitalize_next = true;
299    for c in s.chars() {
300        if c == '_' {
301            capitalize_next = true;
302        } else if capitalize_next {
303            result.push(c.to_ascii_uppercase());
304            capitalize_next = false;
305        } else {
306            result.push(c);
307        }
308    }
309    result
310}
311
312/// Optimize constant expressions in generated code
313pub fn constant_folding(code: &str) -> String {
314    // This is a placeholder for constant folding optimization
315    // In a full implementation, this would:
316    // - Evaluate constant expressions at compile time
317    // - Remove dead code
318    // - Inline small functions
319    // - Optimize string concatenations
320
321    code.to_string()
322}
323
324/// Validate that all handlers referenced in the document exist
325pub fn validate_handlers(
326    document: &DampenDocument,
327    available_handlers: &[HandlerSignature],
328) -> Result<(), CodegenError> {
329    let handler_names: Vec<_> = available_handlers.iter().map(|h| h.name.clone()).collect();
330
331    // Collect all handler references from the document
332    fn collect_handlers(node: &crate::WidgetNode, handlers: &mut Vec<String>) {
333        for event in &node.events {
334            handlers.push(event.handler.clone());
335        }
336        for child in &node.children {
337            collect_handlers(child, handlers);
338        }
339    }
340
341    let mut referenced_handlers = Vec::new();
342    collect_handlers(&document.root, &mut referenced_handlers);
343
344    // Check each referenced handler exists
345    for handler in referenced_handlers {
346        if !handler_names.contains(&handler) {
347            return Err(CodegenError::MissingHandler(handler));
348        }
349    }
350
351    Ok(())
352}
353
354/// Errors that can occur during code generation
355#[derive(Debug, thiserror::Error)]
356pub enum CodegenError {
357    #[error("Handler '{0}' is referenced but not defined")]
358    MissingHandler(String),
359
360    #[error("Invalid widget kind for code generation: {0}")]
361    InvalidWidget(String),
362
363    #[error("Binding expression error: {0}")]
364    BindingError(String),
365
366    #[error("Theme code generation error: {0}")]
367    ThemeError(String),
368
369    #[error("IO error: {0}")]
370    IoError(#[from] std::io::Error),
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376    use crate::parse;
377
378    #[test]
379    fn test_message_enum_generation() {
380        let handlers = vec![
381            HandlerSignature {
382                name: "increment".to_string(),
383                param_type: None,
384                returns_command: false,
385            },
386            HandlerSignature {
387                name: "update_value".to_string(),
388                param_type: Some("String".to_string()),
389                returns_command: false,
390            },
391        ];
392
393        let tokens = generate_message_enum(&handlers);
394        let code = tokens.to_string();
395
396        assert!(code.contains("Increment"));
397        assert!(code.contains("UpdateValue"));
398    }
399
400    #[test]
401    fn test_handler_validation() {
402        let xml = r#"<column><button on_click="increment" /></column>"#;
403        let doc = parse(xml).unwrap();
404
405        let handlers = vec![HandlerSignature {
406            name: "increment".to_string(),
407            param_type: None,
408            returns_command: false,
409        }];
410
411        assert!(validate_handlers(&doc, &handlers).is_ok());
412
413        // Missing handler should fail
414        let handlers_empty: Vec<HandlerSignature> = vec![];
415        assert!(validate_handlers(&doc, &handlers_empty).is_err());
416    }
417}