use proc_macro2::TokenStream;
use quote::quote;
use syn::{DeriveInput, Meta, parse_macro_input};
use crate::event_macros::runnable::{generate_event_enum_and_dispatch, generate_runnable};
use crate::event_macros::utils::{AttributeParser, attr_matches_name, has_runnable_marker};
pub struct ProcessorConfig {
pub event_types: Vec<syn::Path>,
pub poll_interval_ms: Option<u64>,
}
pub fn parse_processor_config(
tokens: impl Into<TokenStream>,
) -> Result<ProcessorConfig, TokenStream> {
let parser = AttributeParser::new_validated(tokens, &["subscribe", "poll_interval"])?;
let poll_interval_ms = parser.get_int("poll_interval")?;
Ok(ProcessorConfig {
event_types: parser.get_path_array("subscribe")?,
poll_interval_ms,
})
}
pub fn processor_impl(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let mut input = parse_macro_input!(item as DeriveInput);
let mut config = match parse_processor_config(proc_macro2::TokenStream::from(attr)) {
Ok(config) => config,
Err(err) => return err.into(),
};
for attr in &input.attrs {
if attr.path().is_ident("processor")
&& let Meta::List(meta_list) = &attr.meta
{
match parse_processor_config(meta_list.tokens.clone()) {
Ok(sibling_config) => {
config.event_types.extend(sibling_config.event_types);
if sibling_config.poll_interval_ms.is_some() {
if config.poll_interval_ms.is_some() {
return syn::Error::new_spanned(
attr,
"Conflicting poll_interval in multiple #[processor] attributes",
)
.to_compile_error()
.into();
}
config.poll_interval_ms = sibling_config.poll_interval_ms;
}
}
Err(err) => return err.into(),
}
}
}
config.event_types.retain({
let mut seen = std::collections::HashSet::new();
move |path| seen.insert(quote!(#path).to_string())
});
if config.event_types.is_empty() {
return syn::Error::new_spanned(
&input.ident,
"#[processor] requires at least one event type in `subscribe`. \
Use `#[processor(subscribe = [EventType])]`.",
)
.to_compile_error()
.into();
}
let struct_name = &input.ident;
let vis = &input.vis;
let generics = &input.generics;
let (impl_generics, _, where_clause) = generics.split_for_impl();
let deduped_ty_generics = crate::event_macros::utils::deduplicate_type_generics(generics);
let has_marker = has_runnable_marker(&input.attrs);
let has_input_device = input
.attrs
.iter()
.any(|attr| attr_matches_name(attr, "input_device"));
let (event_type_tokens, event_enum_def, event_subscriber_impl, process_body) =
generate_event_enum_and_dispatch(
struct_name,
vis,
&config.event_types,
"Processor",
quote! { ::rmk::event::SubscribableEvent },
quote! { subscriber },
);
let polling_processor_impl = if let Some(interval_ms) = config.poll_interval_ms {
quote! {
impl #impl_generics ::rmk::processor::PollingProcessor for #struct_name #deduped_ty_generics #where_clause {
fn interval(&self) -> ::embassy_time::Duration {
::embassy_time::Duration::from_millis(#interval_ms)
}
async fn update(&mut self) {
self.poll().await;
}
}
}
} else {
quote! {}
};
let input_device_config = if has_input_device {
let attr = input
.attrs
.iter()
.find(|attr| attr_matches_name(attr, "input_device"))
.unwrap();
if let Meta::List(meta_list) = &attr.meta {
use crate::event_macros::parser::parse_input_device_config;
match parse_input_device_config(meta_list.tokens.clone()) {
Ok(cfg) => Some(cfg),
Err(err) => return err.into(),
}
} else {
return syn::Error::new_spanned(
attr,
"#[input_device] requires parameters. Use `#[input_device(publish = EventType)]`",
)
.to_compile_error()
.into();
}
} else {
None
};
let runnable_impl = if has_marker {
quote! {}
} else {
generate_runnable(
struct_name,
generics,
where_clause,
input_device_config.as_ref(),
Some(&config),
)
};
input
.attrs
.retain(|attr| !attr.path().is_ident("processor"));
if has_input_device && !has_marker {
input
.attrs
.push(syn::parse_quote!(#[::rmk::macros::runnable_generated]));
}
let expanded = quote! {
#input
#event_enum_def
#event_subscriber_impl
impl #impl_generics ::rmk::processor::Processor for #struct_name #deduped_ty_generics #where_clause {
type Event = #event_type_tokens;
fn subscriber() -> impl ::rmk::event::EventSubscriber<Event = Self::Event> {
<#event_type_tokens as ::rmk::event::SubscribableEvent>::subscriber()
}
async fn process(&mut self, event: Self::Event) {
#process_body
}
}
#polling_processor_impl
#runnable_impl
};
expanded.into()
}