1pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum HandlerSignatureType {
51 Simple,
54
55 WithValue,
58
59 WithCommand,
62}
63
64#[derive(Debug, Clone)]
66pub struct HandlerInfo {
67 pub name: &'static str,
69
70 pub signature_type: HandlerSignatureType,
72
73 pub param_types: &'static [&'static str],
75
76 pub return_type: &'static str,
78
79 pub source_file: &'static str,
81
82 pub source_line: u32,
84}
85
86#[derive(Debug)]
90pub struct CodegenOutput {
91 pub code: String,
93 pub warnings: Vec<String>,
95}
96
97#[derive(Debug, Clone)]
99pub struct GeneratedApplication {
100 pub code: String,
102
103 pub handlers: Vec<String>,
105
106 pub widgets: Vec<String>,
108
109 pub warnings: Vec<String>,
111}
112
113#[derive(Debug, Clone)]
119pub struct GeneratedCode {
120 pub code: String,
122
123 pub module_name: String,
125
126 pub source_file: PathBuf,
128
129 pub timestamp: SystemTime,
131
132 pub validated: bool,
134}
135
136impl GeneratedCode {
137 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 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 pub fn format(&mut self) -> Result<(), String> {
175 match syn::parse_file(&self.code) {
177 Ok(syntax_tree) => {
178 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 pub fn write_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
194 std::fs::write(path, &self.code)
195 }
196}
197
198pub 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
262fn 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 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
295fn 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
312pub fn constant_folding(code: &str) -> String {
314 code.to_string()
322}
323
324pub 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 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 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#[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 let handlers_empty: Vec<HandlerSignature> = vec![];
415 assert!(validate_handlers(&doc, &handlers_empty).is_err());
416 }
417}