extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Attribute, DeriveInput, ItemStruct, LitStr};
fn find_response_type(attrs: &[Attribute]) -> Option<syn::Type> {
for attr in attrs {
if !attr.path().is_ident("pd_message") {
continue;
}
let mut response: Option<syn::Type> = None;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("response") {
let value: syn::Type = meta.value()?.parse()?;
response = Some(value);
Ok(())
} else {
Err(meta.error("unknown pd_message attribute key"))
}
});
return response;
}
None
}
#[proc_macro_derive(Message, attributes(pd_message))]
pub fn derive_message(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let response_type: syn::Type =
find_response_type(&input.attrs).unwrap_or_else(|| syn::parse_str("()").unwrap());
let expanded = quote! {
impl ::palladium_actor::Message for #name {
type Response = #response_type;
const TYPE_TAG: u64 = ::palladium_actor::fnv1a_64(concat!(module_path!(), "::", stringify!(#name)));
}
};
TokenStream::from(expanded)
}
fn to_snake_case(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 4);
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() && i > 0 {
out.push('_');
}
out.extend(c.to_lowercase());
}
out
}
fn parse_version(s: &str) -> (u32, u32, u32) {
let mut parts = s.splitn(3, '.').map(|p| p.parse::<u32>().unwrap_or(0));
(
parts.next().unwrap_or(0),
parts.next().unwrap_or(0),
parts.next().unwrap_or(0),
)
}
fn parse_pd_actor_args(attr: TokenStream) -> (Option<String>, Option<String>) {
if attr.is_empty() {
return (None, None);
}
let mut name: Option<String> = None;
let mut version: Option<String> = None;
let parser = syn::meta::parser(|meta| {
if meta.path.is_ident("name") {
let s: LitStr = meta.value()?.parse()?;
name = Some(s.value());
Ok(())
} else if meta.path.is_ident("version") {
let s: LitStr = meta.value()?.parse()?;
version = Some(s.value());
Ok(())
} else {
Err(meta.error("unknown palladium_actor argument; expected `name` or `version`"))
}
});
let _ = syn::parse::Parser::parse(parser, attr);
(name, version)
}
#[proc_macro_attribute]
pub fn palladium_actor(attr: TokenStream, item: TokenStream) -> TokenStream {
let (name_opt, version_opt) = parse_pd_actor_args(attr);
let input = parse_macro_input!(item as ItemStruct);
let struct_name = &input.ident;
let plugin_name = name_opt.unwrap_or_else(|| to_snake_case(&struct_name.to_string()));
let (ver_major, ver_minor, ver_patch) =
parse_version(&version_opt.unwrap_or_else(|| "0.1.0".to_string()));
let plugin_name_bytes: Vec<u8> = plugin_name.bytes().collect();
let plugin_name_len = plugin_name.len() as u32;
let type_name_bytes: Vec<u8> = struct_name.to_string().bytes().collect();
let type_name_len = type_name_bytes.len() as u32;
let expanded = quote! {
#input
#[no_mangle]
pub unsafe extern "C" fn pd_plugin_init()
-> *const ::palladium_plugin_api::PdPluginInfo
{
static __PD_PLUGIN_NAME: &[u8] = &[#(#plugin_name_bytes),*];
static __PD_ACTOR_TYPE_NAME: &[u8] = &[#(#type_name_bytes),*];
let types = ::std::boxed::Box::leak(::std::boxed::Box::new([
::palladium_plugin_api::PdActorTypeInfo {
type_name: __PD_ACTOR_TYPE_NAME.as_ptr(),
type_name_len: #type_name_len,
config_schema: ::core::ptr::null(),
config_schema_len: 0,
},
]));
::std::boxed::Box::leak(::std::boxed::Box::new(
::palladium_plugin_api::PdPluginInfo {
name: __PD_PLUGIN_NAME.as_ptr(),
name_len: #plugin_name_len,
version_major: #ver_major,
version_minor: #ver_minor,
version_patch: #ver_patch,
abi_version: ::palladium_plugin_api::PD_ABI_VERSION,
actor_type_count: 1,
actor_types: types.as_ptr(),
},
))
}
#[no_mangle]
pub unsafe extern "C" fn pd_actor_create(
_type_name: *const u8,
_type_name_len: u32,
_config: *const u8,
_config_len: u32,
) -> *mut ::core::ffi::c_void {
::std::boxed::Box::into_raw(
::std::boxed::Box::new(
<#struct_name as ::core::default::Default>::default()
)
) as *mut ::core::ffi::c_void
}
#[no_mangle]
pub unsafe extern "C" fn pd_actor_destroy(state: *mut ::core::ffi::c_void) {
drop(unsafe { ::std::boxed::Box::from_raw(state as *mut #struct_name) });
}
#[no_mangle]
pub unsafe extern "C" fn pd_actor_on_start(
state: *mut ::core::ffi::c_void,
ctx_ptr: *mut ::palladium_plugin_api::PdActorContext,
) -> i32 {
let actor = unsafe { &mut *(state as *mut #struct_name) };
let mut ctx = ::palladium_plugin::make_ffi_context(ctx_ptr);
match ::palladium_actor::Actor::on_start(actor, &mut ctx) {
Ok(()) => 0,
Err(_) => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn pd_actor_on_message(
state: *mut ::core::ffi::c_void,
ctx_ptr: *mut ::palladium_plugin_api::PdActorContext,
envelope_bytes: *const u8,
payload: *const u8,
payload_len: u32,
) -> i32 {
let actor = unsafe { &mut *(state as *mut #struct_name) };
let mut ctx = ::palladium_plugin::make_ffi_context(ctx_ptr);
let env_buf: [u8; 80] =
unsafe { *(envelope_bytes as *const [u8; 80]) };
let envelope = ::palladium_actor::Envelope::from_bytes(&env_buf);
let mp = if payload.is_null() || payload_len == 0 {
::palladium_actor::MessagePayload::serialized(
::std::vec::Vec::<u8>::new()
)
} else {
let s = unsafe {
::std::slice::from_raw_parts(payload, payload_len as usize)
};
::palladium_actor::MessagePayload::serialized(s.to_vec())
};
match ::palladium_actor::Actor::on_message(actor, &mut ctx, &envelope, mp) {
Ok(()) => 0,
Err(_) => -2,
}
}
#[no_mangle]
pub unsafe extern "C" fn pd_actor_on_stop(
state: *mut ::core::ffi::c_void,
ctx_ptr: *mut ::palladium_plugin_api::PdActorContext,
reason: i32,
) {
let actor = unsafe { &mut *(state as *mut #struct_name) };
let mut ctx = ::palladium_plugin::make_ffi_context(ctx_ptr);
let stop_reason = match reason {
0 => ::palladium_actor::StopReason::Normal,
1 => ::palladium_actor::StopReason::Requested,
2 => ::palladium_actor::StopReason::Supervisor,
3 => ::palladium_actor::StopReason::Shutdown,
4 => ::palladium_actor::StopReason::Killed,
_ => ::palladium_actor::StopReason::Error(::palladium_actor::ActorError::Handler),
};
::palladium_actor::Actor::on_stop(actor, &mut ctx, stop_reason);
}
};
TokenStream::from(expanded)
}