rename
Pantonshire 5 years ago
parent fce589369b
commit ed83ffc5f3

@ -116,10 +116,10 @@ pub trait TryScribeCowStr {
fn try_scribe(&self) -> Option<Cow<'static, str>>; fn try_scribe(&self) -> Option<Cow<'static, str>>;
} }
pub trait Unscribe { pub trait Unscribe: Sized {
fn unscribe(to_unscribe: &str) -> Self; fn unscribe(to_unscribe: &str) -> Self;
} }
pub trait TryUnscribe { pub trait TryUnscribe: Sized {
fn try_unscribe(to_unscribe: &str) -> Option<Self>; fn try_unscribe(to_unscribe: &str) -> Option<Self>;
} }

@ -42,10 +42,15 @@ pub(crate) enum VariantConstructor {
} }
impl<'a> Variant<'a> { impl<'a> Variant<'a> {
pub(crate) fn match_variant<F, G>(&self, named_fn: F, other_fn: G) -> MacroResult<Option<(TokenStream, TokenStream)>> pub(crate) fn match_variant<F, G>(
&self,
enum_ident: &Ident,
named_fn: &F,
other_fn: &G
) -> MacroResult<Option<(TokenStream, TokenStream)>>
where where
F: Fn(&str) -> MacroResult<TokenStream>, F: Fn(&Variant, &Ident, &str) -> MacroResult<TokenStream>,
G: Fn(TokenStream) -> MacroResult<TokenStream> G: Fn(&Variant, &Ident, TokenStream) -> MacroResult<TokenStream>
{ {
let variant_ident = &self.data.ident; let variant_ident = &self.data.ident;
@ -54,11 +59,11 @@ impl<'a> Variant<'a> {
VariantType::Named { name, constructor, .. } => { VariantType::Named { name, constructor, .. } => {
let pattern = match constructor { let pattern = match constructor {
VariantConstructor::None => quote! { #variant_ident }, VariantConstructor::None => quote! { #enum_ident::#variant_ident },
VariantConstructor::Paren => quote! { #variant_ident () }, VariantConstructor::Paren => quote! { #enum_ident::#variant_ident() },
VariantConstructor::Brace => quote! { #variant_ident {} } VariantConstructor::Brace => quote! { #enum_ident::#variant_ident{} }
}; };
Ok(Some((pattern, named_fn(name)?))) Ok(Some((pattern, named_fn(self, enum_ident, name)?)))
} }
VariantType::Other { field_name } => { VariantType::Other { field_name } => {
@ -67,10 +72,10 @@ impl<'a> Variant<'a> {
None => quote! { __enumscribe_other_inner } None => quote! { __enumscribe_other_inner }
}; };
let pattern = match field_name { let pattern = match field_name {
Some(_) => quote! { #variant_ident { #field_name_tokens } }, Some(_) => quote! { #enum_ident::#variant_ident{#field_name_tokens} },
None => quote! { #variant_ident (#field_name_tokens) } None => quote! { #enum_ident::#variant_ident(#field_name_tokens) }
}; };
Ok(Some((pattern, other_fn(field_name_tokens)?))) Ok(Some((pattern, other_fn(self, enum_ident, field_name_tokens)?)))
} }
} }
} }

@ -9,6 +9,9 @@ use syn::{Data, DataEnum, DeriveInput};
use error::{MacroError, MacroResult}; use error::{MacroError, MacroResult};
use crate::enums::Variant;
use proc_macro2::Ident;
mod enums; mod enums;
mod attribute; mod attribute;
mod error; mod error;
@ -20,6 +23,8 @@ const OTHER: &'static str = "other";
const IGNORE: &'static str = "ignore"; const IGNORE: &'static str = "ignore";
const CASE_INSENSITIVE: &'static str = "case_insensitive"; const CASE_INSENSITIVE: &'static str = "case_insensitive";
type TokenStream2 = proc_macro2::TokenStream;
macro_rules! proc_try { macro_rules! proc_try {
($x:expr) => { ($x:expr) => {
match $x { match $x {
@ -29,8 +34,19 @@ macro_rules! proc_try {
}; };
} }
#[proc_macro_derive(ScribeStaticStr, attributes(enumscribe))] fn derive_scribe<F, G, E>(
pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { input: TokenStream,
trait_ident: TokenStream2,
trait_return_type: TokenStream2,
named_fn: F,
other_fn: G,
ignore_err_fn: E
) -> TokenStream
where
F: Fn(&Variant, &Ident, &str) -> MacroResult<TokenStream2>,
G: Fn(&Variant, &Ident, TokenStream2) -> MacroResult<TokenStream2>,
E: Fn(&Variant, &Ident) -> MacroError
{
let input: DeriveInput = syn::parse(input) let input: DeriveInput = syn::parse(input)
.expect("failed to parse input"); .expect("failed to parse input");
@ -38,54 +54,44 @@ pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream {
let parsed_enum = proc_try!(enums::parse_enum(enum_data)); let parsed_enum = proc_try!(enums::parse_enum(enum_data));
let enum_ident = &input.ident; let enum_ident = &input.ident;
let enum_idents = iter::repeat(enum_ident);
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len());
let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
for variant in parsed_enum.variants.iter() { for variant in parsed_enum.variants.iter() {
match variant.match_variant( match variant.match_variant(enum_ident, &named_fn, &other_fn) {
|name| Ok(quote! {
#name
}),
|_| Err(MacroError::new(format!(
"cannot derive ScribeStaticStr for {} because the variant {} is marked as {}, so \
there is no &'static str associated with it\n\
hint: try deriving ScribeCowStr instead",
enum_ident.to_string(), variant.data.ident.to_string(), OTHER
), variant.span)),
) {
Ok(Some((pattern, result))) => { Ok(Some((pattern, result))) => {
match_patterns.push(pattern); match_patterns.push(pattern);
match_results.push(result); match_results.push(result);
}, }
Ok(None) => return ignore_err_fn(variant, enum_ident).into(),
Ok(None) => return MacroError::new(format!(
"cannot derive ScribeStaticStr for {} because the variant {} is marked as {}\n\
explanation: since {} is ignored, it cannot be guaranteed that the enum can \
always be successfully converted to a String\n\
hint: try deriving TryScribeStaticStr instead",
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE,
variant.data.ident.to_string(),
), variant.span).into(),
Err(err) => return err.into() Err(err) => return err.into()
} }
} }
(quote! { (quote! {
impl ::enumscribe::ScribeStaticStr for #enum_ident { impl #trait_ident for #enum_ident {
fn scribe(&self) -> &'static str { fn scribe(&self) -> #trait_return_type {
match self { match self {
#(#enum_idents::#match_patterns => #match_results,)* #(#match_patterns => #match_results,)*
} }
} }
} }
}).into() }).into()
} }
#[proc_macro_derive(TryScribeStaticStr, attributes(enumscribe))] fn derive_try_scribe<F, G>(
pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { input: TokenStream,
trait_ident: TokenStream2,
trait_return_type: TokenStream2,
named_fn: F,
other_fn: G,
ignore_result: TokenStream2
) -> TokenStream
where
F: Fn(&Variant, &Ident, &str) -> MacroResult<TokenStream2>,
G: Fn(&Variant, &Ident, TokenStream2) -> MacroResult<TokenStream2>
{
let input: DeriveInput = syn::parse(input) let input: DeriveInput = syn::parse(input)
.expect("failed to parse input"); .expect("failed to parse input");
@ -93,46 +99,33 @@ pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream {
let parsed_enum = proc_try!(enums::parse_enum(enum_data)); let parsed_enum = proc_try!(enums::parse_enum(enum_data));
let enum_ident = &input.ident; let enum_ident = &input.ident;
let enum_idents = iter::repeat(enum_ident);
let mut ignore_variant = false; let mut ignore_variant = false;
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len());
let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
for variant in parsed_enum.variants.iter() { for variant in parsed_enum.variants.iter() {
match variant.match_variant( match variant.match_variant(enum_ident, &named_fn, &other_fn) {
|name| Ok(quote! {
::std::option::Option::Some(#name)
}),
|_| Err(MacroError::new(format!(
"cannot derive TryScribeStaticStr for {} because the variant {} is marked as {}, so \
there is no &'static str associated with it\n\
hint: try deriving ScribeCowStr instead",
enum_ident.to_string(), variant.data.ident.to_string(), OTHER
), variant.span)),
) {
Ok(Some((pattern, result))) => { Ok(Some((pattern, result))) => {
match_patterns.push(pattern); match_patterns.push(pattern);
match_results.push(result); match_results.push(result);
}, }
Ok(None) => ignore_variant = true, Ok(None) => ignore_variant = true,
Err(err) => return err.into() Err(err) => return err.into()
} }
} }
let ignore_arm = if ignore_variant { let ignore_arm = if ignore_variant {
quote! { _ => ::std::option::Option::None, } quote! { _ => #ignore_result, }
} else { } else {
quote! {} quote! {}
}; };
(quote! { (quote! {
impl ::enumscribe::TryScribeStaticStr for #enum_ident { impl #trait_ident for #enum_ident {
fn try_scribe(&self) -> ::std::option::Option<&'static str> { fn try_scribe(&self) -> #trait_return_type {
match self { match self {
#(#enum_idents::#match_patterns => #match_results,)* #(#match_patterns => #match_results,)*
#ignore_arm #ignore_arm
} }
} }
@ -140,226 +133,161 @@ pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream {
}).into() }).into()
} }
#[proc_macro_derive(ScribeString, attributes(enumscribe))] #[proc_macro_derive(ScribeStaticStr, attributes(enumscribe))]
pub fn derive_scribe_string(input: TokenStream) -> TokenStream { pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input) derive_scribe(
.expect("failed to parse input"); input,
let enum_data = proc_try!(get_enum_data(&input)); quote! { ::enumscribe::ScribeStaticStr },
let parsed_enum = proc_try!(enums::parse_enum(enum_data)); quote! { &'static str },
|_, _, name| Ok(quote! { #name }),
|variant, enum_ident, _| Err(MacroError::new(format!(
"cannot derive ScribeStaticStr for {} because the variant {} is marked as {}, so \
there is no &'static str associated with it\n\
hint: try deriving ScribeCowStr instead",
enum_ident.to_string(), variant.data.ident.to_string(), OTHER
), variant.span)),
|variant, enum_ident| MacroError::new(format!(
"cannot derive ScribeStaticStr for {} because the variant {} is marked as {}\n\
explanation: since {} is ignored, it cannot be guaranteed that the enum can \
always be successfully converted to a String\n\
hint: try deriving TryScribeStaticStr instead",
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE,
variant.data.ident.to_string(),
), variant.span)
)
}
let enum_ident = &input.ident; #[proc_macro_derive(TryScribeStaticStr, attributes(enumscribe))]
let enum_idents = iter::repeat(enum_ident); pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream {
derive_try_scribe(
input,
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); quote! { ::enumscribe::TryScribeStaticStr },
let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); quote! { ::std::option::Option<&'static str> },
for variant in parsed_enum.variants.iter() { |_, _, name| Ok(quote! {
match variant.match_variant( ::std::option::Option::Some(#name)
|name| Ok(quote! { }),
<_ as ::std::borrow::ToOwned>::to_owned(#name)
}),
|field| Ok(quote! {
<_ as ::std::convert::Into<::std::string::String>>::into(#field)
}),
) {
Ok(Some((pattern, result))) => {
match_patterns.push(pattern);
match_results.push(result);
},
Ok(None) => return MacroError::new(format!( |variant, enum_ident, _| Err(MacroError::new(format!(
"cannot derive ScribeString for {} because the variant {} is marked as {}\n\ "cannot derive TryScribeStaticStr for {} because the variant {} is marked as {}, so \
explanation: since {} is ignored, it cannot be guaranteed that the enum can \ there is no &'static str associated with it\n\
always be successfully converted to a String\n\ hint: try deriving TryScribeCowStr instead",
hint: try deriving TryScribeString instead", enum_ident.to_string(), variant.data.ident.to_string(), OTHER
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, ), variant.span)),
variant.data.ident.to_string(),
), variant.span).into(),
Err(err) => return err.into() quote! { ::std::option::Option::None }
} )
} }
(quote! { #[proc_macro_derive(ScribeString, attributes(enumscribe))]
impl ::enumscribe::ScribeString for #enum_ident { pub fn derive_scribe_string(input: TokenStream) -> TokenStream {
fn scribe(&self) -> ::std::string::String { derive_scribe(
match self { input,
#(#enum_idents::#match_patterns => #match_results,)*
} quote! { ::enumscribe::ScribeString },
} quote! { ::std::string::String },
}
}).into() |_, _, name| Ok(quote! {
<_ as ::std::borrow::ToOwned>::to_owned(#name)
}),
|_, _, field| Ok(quote! {
<_ as ::std::convert::Into<::std::string::String>>::into(#field)
}),
|variant, enum_ident| MacroError::new(format!(
"cannot derive ScribeString for {} because the variant {} is marked as {}\n\
explanation: since {} is ignored, it cannot be guaranteed that the enum can \
always be successfully converted to a String\n\
hint: try deriving TryScribeString instead",
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE,
variant.data.ident.to_string(),
), variant.span)
)
} }
#[proc_macro_derive(TryScribeString, attributes(enumscribe))] #[proc_macro_derive(TryScribeString, attributes(enumscribe))]
pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_string(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input) derive_try_scribe(
.expect("failed to parse input"); input,
let enum_data = proc_try!(get_enum_data(&input));
let parsed_enum = proc_try!(enums::parse_enum(enum_data));
let enum_ident = &input.ident;
let enum_idents = iter::repeat(enum_ident);
let mut ignore_variant = false;
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len());
let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
for variant in parsed_enum.variants.iter() {
match variant.match_variant(
|name| Ok(quote! {
::std::option::Option::Some(
<_ as ::std::borrow::ToOwned>::to_owned(#name)
)
}),
|field| Ok(quote! {
::std::option::Option::Some(
<_ as ::std::convert::Into<::std::string::String>>::into(#field)
)
}),
) {
Ok(Some((pattern, result))) => {
match_patterns.push(pattern);
match_results.push(result);
},
Ok(None) => ignore_variant = true, quote! { ::enumscribe::TryScribeString },
quote! { ::std::option::Option<::std::string::String> },
Err(err) => return err.into() |_, _, name| Ok(quote! {
} ::std::option::Option::Some(
} <_ as ::std::borrow::ToOwned>::to_owned(#name)
)
}),
let ignore_arm = if ignore_variant { |_, _, field| Ok(quote! {
quote! { _ => ::std::option::Option::None, } ::std::option::Option::Some(
} else { <_ as ::std::convert::Into<::std::string::String>>::into(#field)
quote! {} )
}; }),
(quote! { quote! { ::std::option::Option::None }
impl ::enumscribe::TryScribeString for #enum_ident { )
fn try_scribe(&self) -> ::std::option::Option<::std::string::String> {
match self {
#(#enum_idents::#match_patterns => #match_results,)*
#ignore_arm
}
}
}
}).into()
} }
#[proc_macro_derive(ScribeCowStr, attributes(enumscribe))] #[proc_macro_derive(ScribeCowStr, attributes(enumscribe))]
pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream { pub fn derive_scribe_cow_str(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input) derive_scribe(
.expect("failed to parse input"); input,
let enum_data = proc_try!(get_enum_data(&input));
let parsed_enum = proc_try!(enums::parse_enum(enum_data));
let enum_ident = &input.ident;
let enum_idents = iter::repeat(enum_ident);
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len());
let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
for variant in parsed_enum.variants.iter() { quote! { ::enumscribe::ScribeCowStr },
match variant.match_variant( quote! { ::std::borrow::Cow<'static, str> },
|name| Ok(quote! {
::std::borrow::Cow::Borrowed(#name)
}),
|field| Ok(quote! {
::std::borrow::Cow::Owned(
<_ as ::std::convert::Into<::std::string::String>>::into(#field)
)
}),
) {
Ok(Some((pattern, result))) => {
match_patterns.push(pattern);
match_results.push(result);
},
Ok(None) => return MacroError::new(format!(
"cannot derive ScribeCowStr for {} because the variant {} is marked as {}\n\
explanation: since {} is ignored, it cannot be guaranteed that the enum can \
always be successfully converted to a String\n\
hint: try deriving TryScribeCowStr instead",
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE,
variant.data.ident.to_string(),
), variant.span).into(),
Err(err) => return err.into() |_, _, name| Ok(quote! {
} ::std::borrow::Cow::Borrowed(#name)
} }),
(quote! { |_, _, field| Ok(quote! {
impl ::enumscribe::ScribeCowStr for #enum_ident { ::std::borrow::Cow::Owned(
fn scribe(&self) -> ::std::borrow::Cow<'static, str> { <_ as ::std::convert::Into<::std::string::String>>::into(#field)
match self { )
#(#enum_idents::#match_patterns => #match_results,)* }),
}
} |variant, enum_ident| MacroError::new(format!(
} "cannot derive ScribeCowStr for {} because the variant {} is marked as {}\n\
}).into() explanation: since {} is ignored, it cannot be guaranteed that the enum can \
always be successfully converted to a String\n\
hint: try deriving TryScribeCowStr instead",
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE,
variant.data.ident.to_string(),
), variant.span)
)
} }
#[proc_macro_derive(TryScribeCowStr, attributes(enumscribe))] #[proc_macro_derive(TryScribeCowStr, attributes(enumscribe))]
pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream { pub fn derive_try_scribe_cow_str(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input) derive_try_scribe(
.expect("failed to parse input"); input,
let enum_data = proc_try!(get_enum_data(&input)); quote! { ::enumscribe::TryScribeCowStr },
let parsed_enum = proc_try!(enums::parse_enum(enum_data)); quote! { ::std::option::Option<::std::borrow::Cow<'static, str>> },
let enum_ident = &input.ident; |_, _, name| Ok(quote! {
let enum_idents = iter::repeat(enum_ident); ::std::option::Option::Some(
::std::borrow::Cow::Borrowed(#name)
let mut ignore_variant = false; )
let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); }),
let mut match_results = Vec::with_capacity(parsed_enum.variants.len());
for variant in parsed_enum.variants.iter() { |_, _, field| Ok(quote! {
match variant.match_variant( ::std::option::Option::Some(
|name| Ok(quote! { ::std::borrow::Cow::Owned(
::std::option::Option::Some( <_ as ::std::convert::Into<::std::string::String>>::into(#field)
::std::borrow::Cow::Borrowed(#name)
)
}),
|field| Ok(quote! {
::std::option::Option::Some(
::std::borrow::Cow::Owned(
<_ as ::std::convert::Into<::std::string::String>>::into(#field)
)
) )
}), )
) { }),
Ok(Some((pattern, result))) => {
match_patterns.push(pattern);
match_results.push(result);
},
Ok(None) => ignore_variant = true, quote! { ::std::option::Option::None }
)
Err(err) => return err.into()
}
}
let ignore_arm = if ignore_variant {
quote! { _ => ::std::option::Option::None, }
} else {
quote! {}
};
(quote! {
impl ::enumscribe::TryScribeCowStr for #enum_ident {
fn try_scribe(&self) -> ::std::option::Option<::std::borrow::Cow<'static, str>> {
match self {
#(#enum_idents::#match_patterns => #match_results,)*
#ignore_arm
}
}
}
}).into()
} }
fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> { fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> {

@ -1,8 +1,6 @@
use enumscribe::*; use enumscribe::*;
// #[derive(ScribeString)] #[derive(TryScribeCowStr)]
#[derive(TryScribeStaticStr)]
enum Airport { enum Airport {
#[enumscribe(str = "LHR", case_insensitive)] #[enumscribe(str = "LHR", case_insensitive)]
Heathrow, Heathrow,
@ -12,14 +10,14 @@ enum Airport {
Luton, Luton,
#[enumscribe(str = "BHX", case_insensitive, ignore)] #[enumscribe(str = "BHX", case_insensitive, ignore)]
BirminghamInternational, BirminghamInternational,
// #[enumscribe(other)] #[enumscribe(other)]
// Other(String), Other(String),
} }
fn main() { fn main() {
let luton = Airport::Luton; let luton = Airport::Luton;
println!("Hello, {:?}!", luton.try_scribe()); println!("Hello, {:?}!", luton.try_scribe());
// let other = Airport::Other("Dedicated EasyJet-only airport".to_owned()); let other = Airport::Other("Dedicated EasyJet-only airport".to_owned());
// println!("Hello, {:?}!", other.try_scribe()); println!("Hello, {:?}!", other.try_scribe());
} }

Loading…
Cancel
Save