use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use crate::core::pascal_to_snake;
struct VariantEntry {
name: syn::Ident,
ty: syn::Type,
}
impl Parse for VariantEntry {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name: syn::Ident = input.parse()?;
input.parse::<syn::Token![=>]>()?;
let ty: syn::Type = input.parse()?;
Ok(Self { name, ty })
}
}
struct UnionizeInput {
variants: Vec<VariantEntry>,
vis: syn::Visibility,
name: syn::Ident,
}
impl Parse for UnionizeInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
syn::braced!(content in input);
let entries = content.parse_terminated(VariantEntry::parse, syn::Token![,])?;
input.parse::<syn::Token![as]>()?;
let vis: syn::Visibility = input.parse()?;
let name: syn::Ident = input.parse()?;
Ok(Self {
variants: entries.into_iter().collect(),
vis,
name,
})
}
}
pub fn render(tokens: proc_macro::TokenStream) -> syn::Result<TokenStream> {
let input: UnionizeInput = syn::parse(tokens)?;
let vis = &input.vis;
let name = &input.name;
let enum_variants: Vec<_> = input
.variants
.iter()
.map(|v| {
let vname = &v.name;
let ty = &v.ty;
quote! { #vname(#ty) }
})
.collect();
let mut methods = Vec::new();
let mut from_impls = Vec::new();
for v in &input.variants {
let vname = &v.name;
let ty = &v.ty;
let snake = pascal_to_snake(&vname.to_string());
let is_name = format_ident!("is_{}", snake);
let as_name = format_ident!("as_{}", snake);
let to_name = format_ident!("to_{}", snake);
methods.push(quote! {
pub fn #is_name(&self) -> bool {
matches!(self, Self::#vname(..))
}
pub fn #as_name(&self) -> ::std::option::Option<&#ty> {
match self {
Self::#vname(__v) => ::std::option::Option::Some(__v),
_ => ::std::option::Option::None,
}
}
pub fn #to_name(&self) -> ::std::option::Option<#ty>
where #ty: ::std::clone::Clone
{
match self {
Self::#vname(__v) => ::std::option::Option::Some(__v.clone()),
_ => ::std::option::Option::None,
}
}
});
from_impls.push(quote! {
impl ::std::convert::From<#ty> for #name {
fn from(value: #ty) -> Self {
Self::#vname(value)
}
}
});
}
Ok(quote! {
#vis enum #name {
#(#enum_variants,)*
}
impl #name {
#(#methods)*
}
#(#from_impls)*
})
}