use crate::CodegenError;
use crate::handler::HandlerSignature;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn generate_handler_dispatch(
handlers: &[HandlerSignature],
model_name: &str,
message_name: &str,
) -> Result<TokenStream, CodegenError> {
if handlers.is_empty() {
return Ok(quote! {
match _handler {
_ => {}
}
});
}
let model_ident = format_ident!("{}", model_name);
let message_ident = format_ident!("{}", message_name);
let match_arms: Vec<TokenStream> = handlers
.iter()
.map(move |handler| {
let _handler_ident = format_ident!("{}", handler.name);
match &handler.param_type {
Some(param_type) if handler.returns_command => generate_handler_with_command(
&handler.name,
param_type,
&model_ident,
&message_ident,
),
Some(param_type) => generate_handler_with_value(
&handler.name,
param_type,
&model_ident,
&message_ident,
),
None if handler.returns_command => {
generate_handler_simple(&handler.name, &model_ident, &message_ident, true)
}
None => generate_handler_simple(&handler.name, &model_ident, &message_ident, false),
}
})
.collect();
Ok(quote! {
match handler_name {
#(#match_arms),*
}
})
}
fn to_upper_camel_case(s: &str) -> String {
let mut result = String::new();
let mut capitalize_next = true;
for c in s.chars() {
if c == '_' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
fn generate_handler_simple(
handler_name: &str,
_model_ident: &syn::Ident,
message_ident: &syn::Ident,
_returns_command: bool,
) -> TokenStream {
let variant_name = to_upper_camel_case(handler_name);
let handler_ident = format_ident!("{}", variant_name);
quote! {
#handler_name => {
#message_ident::#handler_ident
}
}
}
fn generate_handler_with_value(
handler_name: &str,
value_type: &str,
_model_ident: &syn::Ident,
message_ident: &syn::Ident,
) -> TokenStream {
let variant_name = to_upper_camel_case(handler_name);
let handler_ident = format_ident!("{}", variant_name);
let type_ident = format_ident!("{}", value_type);
quote! {
#handler_name => {
|value: #type_ident| #message_ident::#handler_ident(value)
}
}
}
fn generate_handler_with_command(
handler_name: &str,
value_type: &str,
_model_ident: &syn::Ident,
message_ident: &syn::Ident,
) -> TokenStream {
let variant_name = to_upper_camel_case(handler_name);
let handler_ident = format_ident!("{}", variant_name);
let type_ident = format_ident!("{}", value_type);
quote! {
#handler_name => {
|value: #type_ident| #message_ident::#handler_ident(value)
}
}
}
pub fn generate_update_function(
handlers: &[HandlerSignature],
model_name: &str,
message_name: &str,
) -> Result<TokenStream, CodegenError> {
let message_ident = format_ident!("{}", message_name);
if handlers.is_empty() {
return Ok(quote! {
fn update(&mut self, _message: Self::Message) {}
});
}
let arms: Vec<TokenStream> = handlers
.iter()
.map(|handler| {
let _model_ident = format_ident!("{}", model_name);
let variant_name = to_upper_camel_case(&handler.name);
let _handler_ident = format_ident!("{}", variant_name);
let handler_name_str = handler.name.clone();
match (&handler.param_type, handler.returns_command) {
(None, false) => {
quote! {
#message_ident::#_handler_ident => {
self.handler_registry.call_simple(&mut self.model, #handler_name_str);
}
}
}
(Some(param_type), false) => {
let type_ident = format_ident!("{}", param_type);
quote! {
#message_ident::#_handler_ident(value) => {
self.handler_registry.call_with_value::<#type_ident>(&mut self.model, #handler_name_str, value);
}
}
}
(None, true) => {
quote! {
#message_ident::#_handler_ident => {
if let Some(cmd) = self.handler_registry.call_with_command(&mut self.model, #handler_name_str) {
return cmd;
}
}
}
}
(Some(param_type), true) => {
let type_ident = format_ident!("{}", param_type);
quote! {
#message_ident::#_handler_ident(value) => {
if let Some(cmd) = self.handler_registry.call_with_command::<#type_ident>(&mut self.model, #handler_name_str, value) {
return cmd;
}
}
}
}
}
})
.collect();
let catch_all = quote! {
_ => {}
};
Ok(quote! {
fn update(&mut self, message: Self::Message) -> Option<iced::Command<Self::Message>> {
match message {
#(#arms)*
#catch_all
}
None
}
})
}
pub fn validate_expression_inlinable(expr: &crate::Expr) -> Result<(), CodegenError> {
match expr {
crate::Expr::FieldAccess(_) => Ok(()),
crate::Expr::SharedFieldAccess(_) => Ok(()), crate::Expr::MethodCall(method_expr) => {
validate_expression_inlinable(&method_expr.receiver)?;
for arg in &method_expr.args {
validate_expression_inlinable(arg)?;
}
Ok(())
}
crate::Expr::BinaryOp(binary_expr) => {
validate_expression_inlinable(&binary_expr.left)?;
validate_expression_inlinable(&binary_expr.right)?;
Ok(())
}
crate::Expr::UnaryOp(unary_expr) => {
validate_expression_inlinable(&unary_expr.operand)?;
Ok(())
}
crate::Expr::Conditional(cond_expr) => {
validate_expression_inlinable(&cond_expr.condition)?;
validate_expression_inlinable(&cond_expr.then_branch)?;
validate_expression_inlinable(&cond_expr.else_branch)?;
Ok(())
}
crate::Expr::Literal(_) => Ok(()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handler_dispatch_simple() {
let handlers = vec![HandlerSignature {
name: "increment".to_string(),
param_type: None,
returns_command: false,
}];
let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
let code = result.to_string();
assert!(code.contains("increment"));
}
#[test]
fn test_handler_dispatch_with_value() {
let handlers = vec![HandlerSignature {
name: "set_value".to_string(),
param_type: Some("String".to_string()),
returns_command: false,
}];
let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
let code = result.to_string();
assert!(code.contains("set_value"));
assert!(code.contains("String"));
}
#[test]
fn test_handler_dispatch_with_command() {
let handlers = vec![HandlerSignature {
name: "save".to_string(),
param_type: None,
returns_command: true,
}];
let result = generate_handler_dispatch(&handlers, "Model", "Message").unwrap();
let code = result.to_string();
assert!(code.contains("save"));
}
#[test]
fn test_update_function_generation() {
let handlers = vec![
HandlerSignature {
name: "increment".to_string(),
param_type: None,
returns_command: false,
},
HandlerSignature {
name: "set_value".to_string(),
param_type: Some("String".to_string()),
returns_command: false,
},
];
let result = generate_update_function(&handlers, "Model", "Message").unwrap();
let code = result.to_string();
assert!(code.contains("update"));
assert!(code.contains("increment"));
assert!(code.contains("set_value"));
}
#[test]
fn test_expression_validation() {
use crate::expr::ast::{BinaryOp, BinaryOpExpr, Expr, FieldAccessExpr, LiteralExpr};
let expr = Expr::BinaryOp(BinaryOpExpr {
left: Box::new(Expr::FieldAccess(FieldAccessExpr {
path: vec!["count".to_string()],
})),
op: BinaryOp::Add,
right: Box::new(Expr::Literal(LiteralExpr::Integer(1))),
});
assert!(validate_expression_inlinable(&expr).is_ok());
}
}