1pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum HandlerSignatureType {
50 Simple,
53
54 WithValue,
57
58 WithCommand,
61}
62
63#[derive(Debug, Clone)]
65pub struct HandlerInfo {
66 pub name: &'static str,
68
69 pub signature_type: HandlerSignatureType,
71
72 pub param_types: &'static [&'static str],
74
75 pub return_type: &'static str,
77
78 pub source_file: &'static str,
80
81 pub source_line: u32,
83}
84
85#[derive(Debug)]
89pub struct CodegenOutput {
90 pub code: String,
92 pub warnings: Vec<String>,
94}
95
96#[derive(Debug, Clone)]
98pub struct GeneratedApplication {
99 pub code: String,
101
102 pub handlers: Vec<String>,
104
105 pub widgets: Vec<String>,
107
108 pub warnings: Vec<String>,
110}
111
112#[derive(Debug, Clone)]
118pub struct GeneratedCode {
119 pub code: String,
121
122 pub module_name: String,
124
125 pub source_file: PathBuf,
127
128 pub timestamp: SystemTime,
130
131 pub validated: bool,
133}
134
135impl GeneratedCode {
136 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 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 pub fn format(&mut self) -> Result<(), String> {
174 match syn::parse_file(&self.code) {
176 Ok(syntax_tree) => {
177 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 pub fn write_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
193 std::fs::write(path, &self.code)
194 }
195}
196
197pub 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
261fn 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 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
294fn 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
311pub fn constant_folding(code: &str) -> String {
313 code.to_string()
321}
322
323pub 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 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 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#[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 let handlers_empty: Vec<HandlerSignature> = vec![];
411 assert!(validate_handlers(&doc, &handlers_empty).is_err());
412 }
413}