use crate::*;
impl Parse for ComputedInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut signals: Vec<Expr> = Vec::new();
while !input.peek(Token![|]) {
let expr: Expr = input.parse()?;
signals.push(expr);
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
input.parse::<Token![|]>()?;
let mut param_names: Vec<Option<Ident>> = Vec::new();
let mut param_types: Vec<Option<Type>> = Vec::new();
while !input.peek(Token![|]) {
let param_name: Option<Ident> = if input.peek(Token![_]) {
input.parse::<Token![_]>()?;
None
} else {
Some(input.parse::<Ident>()?)
};
param_names.push(param_name);
let param_type: Option<Type> = if input.peek(Token![:]) {
input.parse::<Token![:]>()?;
Some(input.parse::<Type>()?)
} else {
None
};
param_types.push(param_type);
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
input.parse::<Token![|]>()?;
input.parse::<Token![->]>()?;
let return_type: Type = input.parse::<Type>()?;
let body_content: ParseBuffer<'_>;
braced!(body_content in input);
let mut body: Vec<Stmt> = Vec::new();
while !body_content.is_empty() {
let stmt: Stmt = body_content.parse()?;
body.push(stmt);
}
if signals.len() != param_names.len() {
return Err(input.error(ERR_SIGNAL_PARAM_MISMATCH));
}
Ok(Self {
signals,
param_names,
param_types,
return_type,
body,
})
}
}
impl ToTokens for ComputedInput {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let signals: Vec<Ident> = (0..self.get_signals().len())
.map(|signal_index: usize| {
Ident::new(
&format!("{COMPUTED_SIGNAL_PREFIX}{signal_index}"),
Span::call_site(),
)
})
.collect();
let result_ident: Ident = Ident::new(COMPUTED_RESULT_PREFIX, Span::call_site());
let signal_exprs: &Vec<Expr> = self.get_signals();
let param_names: &Vec<Option<Ident>> = self.get_param_names();
let param_types: &Vec<Option<Type>> = self.get_param_types();
let return_type: &Type = self.get_return_type();
let body: &Vec<Stmt> = self.get_body();
let all_gets: Vec<proc_macro2::TokenStream> = signals
.iter()
.zip(param_names.iter())
.zip(param_types.iter())
.map(
|((signal, param), param_type): ((&Ident, &Option<Ident>), &Option<Type>)| match (
param, param_type,
) {
(Some(name), Some(ty)) => quote! { let #name: #ty = #signal.get(); },
(Some(name), None) => quote! { let #name = #signal.get(); },
(None, Some(ty)) => quote! { let _: #ty = #signal.get(); },
(None, None) => quote! { let _ = #signal.get(); },
},
)
.collect();
let subscribe_calls: Vec<proc_macro2::TokenStream> = signals
.iter()
.map(|signal: &Ident| {
quote! {
{
#signal.subscribe(move || {
unsafe { (&mut *(__euv_computed_fire_addr as *mut Box<dyn ::std::ops::FnMut()>))() }
});
}
}
})
.collect();
tokens.extend(quote! {{
#(let #signals = #signal_exprs;)*
let #result_ident: ::euv::Signal<#return_type> = ::euv::use_signal(|| {
#(#all_gets)*
{ #(#body)* }
});
let __euv_computed_subscribed: ::euv::Signal<bool> = ::euv::use_signal(|| false);
if !__euv_computed_subscribed.get() {
let __euv_computed_fire_addr: usize = Box::leak(Box::new(Box::new(move || {
#(#all_gets)*
#result_ident.set_silent({ #(#body)* });
}) as Box<dyn ::std::ops::FnMut()>)) as *mut Box<dyn ::std::ops::FnMut()> as usize;
::euv::batch_updates(|| {
#(#subscribe_calls)*
__euv_computed_subscribed.set_silent(true);
});
}
#result_ident
}});
}
}