use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, Fields, ItemEnum};
fn to_snake_case(name: &str) -> String {
let mut out = String::with_capacity(name.len() + 4);
let mut prev_upper = true;
for (i, ch) in name.chars().enumerate() {
if ch.is_uppercase() {
if i > 0 && !prev_upper {
out.push('_');
}
for low in ch.to_lowercase() {
out.push(low);
}
prev_upper = true;
} else {
out.push(ch);
prev_upper = false;
}
}
out
}
#[proc_macro_attribute]
pub fn actor_msg(_attr: TokenStream, item: TokenStream) -> TokenStream {
let en = parse_macro_input!(item as ItemEnum);
let expanded = quote! {
#[derive(::core::fmt::Debug)]
#en
};
expanded.into()
}
#[proc_macro_derive(Actor, attributes(msg))]
pub fn derive_actor(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let msg_ty = match extract_msg_attr(&input) {
Ok(t) => t,
Err(e) => return e.to_compile_error().into(),
};
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = quote! {
#[::atomr_core::prelude::async_trait]
impl #impl_generics ::atomr_core::prelude::Actor for #name #ty_generics #where_clause {
type Msg = #msg_ty;
async fn handle(
&mut self,
ctx: &mut ::atomr_core::prelude::Context<Self>,
msg: Self::Msg,
) {
Self::handle_msg(self, ctx, msg).await;
}
}
};
expanded.into()
}
fn extract_msg_attr(input: &DeriveInput) -> Result<syn::Type, syn::Error> {
for attr in &input.attrs {
if attr.path().is_ident("msg") {
return attr.parse_args::<syn::Type>();
}
}
Err(syn::Error::new_spanned(
&input.ident,
"#[derive(Actor)] requires a `#[msg(MsgType)]` attribute naming the actor's message type",
))
}
#[proc_macro_derive(Receive, attributes(msg, receive))]
pub fn derive_receive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let msg_ty = match extract_msg_attr(&input) {
Ok(t) => t,
Err(e) => return e.to_compile_error().into(),
};
let unit_variants = match extract_unit_variants(&input) {
Ok(v) => v,
Err(e) => return e.to_compile_error().into(),
};
let arms = unit_variants.iter().map(|v| {
let snake = format_ident!("on_{}", to_snake_case(&v.to_string()));
quote! {
#msg_ty::#v => Self::#snake(self, ctx).await,
}
});
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = quote! {
#[::atomr_core::prelude::async_trait]
impl #impl_generics ::atomr_core::prelude::Actor for #name #ty_generics #where_clause {
type Msg = #msg_ty;
async fn handle(
&mut self,
ctx: &mut ::atomr_core::prelude::Context<Self>,
msg: Self::Msg,
) {
match msg {
#(#arms)*
#[allow(unreachable_patterns)]
_ => {} }
}
}
};
expanded.into()
}
fn extract_unit_variants(input: &DeriveInput) -> Result<Vec<syn::Ident>, syn::Error> {
for attr in &input.attrs {
if !attr.path().is_ident("receive") {
continue;
}
let mut out = Vec::new();
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("unit_variants") {
meta.parse_nested_meta(|inner| {
if let Some(ident) = inner.path.get_ident() {
out.push(ident.clone());
Ok(())
} else {
Err(inner.error("expected variant identifier"))
}
})
} else {
Err(meta.error("unknown #[receive(...)] key (expected `unit_variants`)"))
}
})?;
return Ok(out);
}
Err(syn::Error::new_spanned(
&input.ident,
"#[derive(Receive)] requires `#[receive(unit_variants(A, B, …))]` for the Phase 1.E minimal subset",
))
}
#[proc_macro]
pub fn props(input: TokenStream) -> TokenStream {
let expr = parse_macro_input!(input as syn::Expr);
let expanded = quote! {
::atomr_core::actor::Props::create(move || #expr)
};
expanded.into()
}
#[allow(dead_code)]
fn _unused_fields_marker(_: Fields) {}