use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, Result as SynResult, Token, Type};
enum PackInput {
Explicit {
group_name: Ident,
type_name: Ident,
inner_type: Type,
},
Default {
type_name: Ident,
inner_type: Type,
},
}
impl Parse for PackInput {
fn parse(input: ParseStream) -> SynResult<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Ident) && input.peek2(Token![,]) {
let group_name = input.parse()?;
input.parse::<Token![,]>()?;
let type_name = input.parse()?;
input.parse::<Token![=]>()?;
let inner_type = input.parse()?;
Ok(PackInput::Explicit {
group_name,
type_name,
inner_type,
})
} else if lookahead.peek(Ident) && input.peek2(Token![=]) {
let type_name = input.parse()?;
input.parse::<Token![=]>()?;
let inner_type = input.parse()?;
Ok(PackInput::Default {
type_name,
inner_type,
})
} else {
Err(lookahead.error())
}
}
}
pub fn pack(input: TokenStream) -> TokenStream {
let pack_input = syn::parse_macro_input!(input as PackInput);
let (group_name, type_name, inner_type, use_default) = match pack_input {
PackInput::Explicit {
group_name,
type_name,
inner_type,
} => (group_name, type_name, inner_type, false),
PackInput::Default {
type_name,
inner_type,
} => (
Ident::new("DefaultProgram", proc_macro2::Span::call_site()),
type_name,
inner_type,
true,
),
};
#[cfg(not(feature = "serde"))]
let struct_def = quote! {
#[derive(Debug)]
pub struct #type_name {
pub(crate) inner: #inner_type,
}
};
#[cfg(feature = "serde")]
let struct_def = quote! {
#[derive(Debug, serde::Serialize)]
pub struct #type_name {
pub(crate) inner: #inner_type,
}
};
let new_impl = quote! {
impl #type_name {
pub fn new(inner: #inner_type) -> Self {
Self { inner }
}
}
};
let from_into_impl = quote! {
impl From<#inner_type> for #type_name {
fn from(inner: #inner_type) -> Self {
Self::new(inner)
}
}
impl From<#type_name> for #inner_type {
fn from(wrapper: #type_name) -> #inner_type {
wrapper.inner
}
}
};
let as_ref_impl = quote! {
impl ::std::convert::AsRef<#inner_type> for #type_name {
fn as_ref(&self) -> &#inner_type {
&self.inner
}
}
impl ::std::convert::AsMut<#inner_type> for #type_name {
fn as_mut(&mut self) -> &mut #inner_type {
&mut self.inner
}
}
};
let deref_impl = quote! {
impl ::std::ops::Deref for #type_name {
type Target = #inner_type;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ::std::ops::DerefMut for #type_name {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
};
let default_impl = quote! {
impl ::std::default::Default for #type_name
where
#inner_type: ::std::default::Default,
{
fn default() -> Self {
Self::new(::std::default::Default::default())
}
}
};
let any_out_impl = quote! {
impl Into<mingling::AnyOutput<#group_name>> for #type_name {
fn into(self) -> mingling::AnyOutput<#group_name> {
mingling::AnyOutput::new(self)
}
}
impl Into<mingling::ChainProcess<#group_name>> for #type_name {
fn into(self) -> mingling::ChainProcess<#group_name> {
mingling::AnyOutput::new(self).route_chain()
}
}
impl #type_name {
pub fn to_chain(self) -> mingling::ChainProcess<#group_name> {
mingling::AnyOutput::new(self).route_chain()
}
pub fn to_render(self) -> mingling::ChainProcess<#group_name> {
mingling::AnyOutput::new(self).route_renderer()
}
}
};
let group_impl = quote! {
impl ::mingling::Groupped<#group_name> for #type_name {
fn member_id() -> #group_name {
#group_name::#type_name
}
}
};
let expanded = if use_default {
quote! {
#struct_def
#new_impl
#from_into_impl
#as_ref_impl
#deref_impl
#default_impl
impl Into<mingling::AnyOutput<DefaultProgram>> for #type_name {
fn into(self) -> mingling::AnyOutput<DefaultProgram> {
mingling::AnyOutput::new(self)
}
}
impl From<#type_name> for mingling::ChainProcess<DefaultProgram> {
fn from(value: #type_name) -> Self {
mingling::AnyOutput::new(value).route_chain()
}
}
impl #type_name {
pub fn to_chain(self) -> mingling::ChainProcess<DefaultProgram> {
mingling::AnyOutput::new(self).route_chain()
}
pub fn to_render(self) -> mingling::ChainProcess<DefaultProgram> {
mingling::AnyOutput::new(self).route_renderer()
}
}
impl ::mingling::Groupped<DefaultProgram> for #type_name {
fn member_id() -> DefaultProgram {
DefaultProgram::#type_name
}
}
}
} else {
quote! {
#struct_def
#new_impl
#from_into_impl
#as_ref_impl
#deref_impl
#default_impl
#any_out_impl
#group_impl
}
};
expanded.into()
}