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