use proc_macro2::TokenStream as TokenStream2;
use quote::{quote};
use syn::{parse_macro_input, Ident, Type, Block, Token, Path};
use syn::parse::{Parse, ParseStream, Result};
pub struct PacketMatchInput {
packet_var: Ident,
base_path: Path, cases: Vec<PacketCase>,
default_case: Option<Block>,
}
pub struct PacketCase {
packet_id: Ident, packet_type: Type, var_name: Ident,
handler: Block,
}
impl Parse for PacketMatchInput {
fn parse(input: ParseStream) -> Result<Self> {
let packet_var: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let base_path: Path = input.parse()?;
input.parse::<Token![,]>()?;
let content;
syn::braced!(content in input);
let mut cases = Vec::new();
let mut default_case = None;
while !content.is_empty() {
let lookahead = content.lookahead1();
if lookahead.peek(syn::Ident) {
let ident: Ident = content.parse()?;
match ident.to_string().as_str() {
"case" => {
content.parse::<Token![!]>()?;
let case = parse_packet_case(&content, &base_path)?;
cases.push(case);
}
"default" => {
content.parse::<Token![!]>()?;
let handler_block: Block = content.parse()?;
default_case = Some(handler_block);
}
_ => {
return Err(syn::Error::new(
ident.span(),
"expected 'case' or 'default'"
));
}
}
} else {
return Err(lookahead.error());
}
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
}
}
Ok(PacketMatchInput {
packet_var,
base_path,
cases,
default_case,
})
}
}
fn parse_packet_case(input: ParseStream, _base_path: &Path) -> Result<PacketCase> {
let packet_id: Ident = input.parse()?;
input.parse::<Token![=>]>()?;
let packet_type: Type = input.parse()?;
input.parse::<Token![as]>()?;
let var_name: Ident = input.parse()?;
let handler: Block = input.parse()?;
Ok(PacketCase {
packet_id,
packet_type,
var_name,
handler,
})
}
fn generate_packet_match(input: PacketMatchInput) -> TokenStream2 {
let packet_var = &input.packet_var;
let base_path = &input.base_path;
let cases = &input.cases;
let default_case = &input.default_case;
let case_arms: Vec<_> = cases.iter().map(|case| {
let packet_id = &case.packet_id;
let packet_type = &case.packet_type;
let var_name = &case.var_name;
let handler = &case.handler;
let full_packet_id = quote! { #base_path::pool::#packet_id };
let full_packet_type = if let syn::Type::Path(type_path) = packet_type {
if type_path.path.segments.is_empty() {
packet_type.clone()
} else {
let mut new_path = base_path.clone();
for segment in &type_path.path.segments {
new_path.segments.push(segment.clone());
}
syn::Type::Path(syn::TypePath {
path: new_path,
qself: None,
})
}
} else {
packet_type.clone()
};
quote! {
#full_packet_id => {
if let Some(#var_name) = #packet_var.as_any().downcast_ref::<#full_packet_type>() {
#handler
}
}
}
}).collect();
let default_arm = if let Some(default_handler) = default_case {
quote! { _ => #default_handler }
} else {
quote! { _ => {} }
};
quote! {
{
let #packet_var = &#packet_var;
match #packet_var.id() {
#(#case_arms)*
#default_arm
}
}
}
}
pub fn parse_packet_match(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as PacketMatchInput);
generate_packet_match(input).into()
}