Project restructuring, dedicated traits, TryScribeString derive

rename
Pantonshire 5 years ago
parent 6a2ffc19eb
commit 0c4c252be9

@ -1,19 +1,7 @@
[package] [workspace]
name = "enumscribe" members = [
version = "0.1.0" "enumscribe",
authors = ["Tom Panton <pantonshire@gmail.com>"] "enumscribe_derive",
edition = "2018" "enumscribe_tests",
license = "MIT" "enumscribe_examples"
description = "Procedural macros for converting between enums and strings" ]
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
syn = "1.0"
quote = "1.0"
[dev-dependencies]
serde = "1.0"
serde_json = "1.0"

@ -0,0 +1,10 @@
[package]
name = "enumscribe"
version = "0.1.0"
authors = ["Tom Panton <pantonshire@gmail.com>"]
edition = "2018"
license = "MIT"
description = "Procedural macros for converting between enums and strings"
[dependencies]
enumscribe_derive = { path = "../enumscribe_derive" }

@ -0,0 +1,34 @@
#[macro_use]
extern crate enumscribe_derive;
use std::borrow::Cow;
pub use enumscribe_derive::*;
//TODO
pub trait ScribeStaticStr {
fn scribe(&self) -> &'static str;
}
//TODO
pub trait TryScribeStaticStr {
fn try_scribe(&self) -> Option<&'static str>;
}
pub trait ScribeString {
fn scribe(&self) -> String;
}
pub trait TryScribeString {
fn try_scribe(&self) -> Option<String>;
}
//TODO
pub trait ScribeCowStr {
fn scribe(&self) -> Cow<'static, str>;
}
//TODO
pub trait TryScribeCowStr {
fn try_scribe(&self) -> Option<Cow<'static, str>>;
}

@ -0,0 +1,19 @@
[package]
name = "enumscribe_derive"
version = "0.1.0"
authors = ["Tom Panton <pantonshire@gmail.com>"]
edition = "2018"
license = "MIT"
description = "Procedural macros for converting between enums and strings"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
syn = "1.0"
quote = "1.0"
[dev-dependencies]
serde = "1.0"
serde_json = "1.0"

@ -42,15 +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) -> Option<(TokenStream, TokenStream)> pub(crate) fn match_variant<F, G>(&self, named_fn: F, other_fn: G) -> MacroResult<Option<(TokenStream, TokenStream)>>
where where
F: Fn(&str) -> TokenStream, F: Fn(&str) -> MacroResult<TokenStream>,
G: Fn(TokenStream) -> TokenStream G: Fn(TokenStream) -> MacroResult<TokenStream>
{ {
let variant_ident = &self.data.ident; let variant_ident = &self.data.ident;
match &self.v_type { match &self.v_type {
VariantType::Ignore => None, VariantType::Ignore => Ok(None),
VariantType::Named { name, constructor, .. } => { VariantType::Named { name, constructor, .. } => {
let pattern = match constructor { let pattern = match constructor {
@ -58,19 +58,19 @@ impl<'a> Variant<'a> {
VariantConstructor::Paren => quote! { #variant_ident () }, VariantConstructor::Paren => quote! { #variant_ident () },
VariantConstructor::Brace => quote! { #variant_ident {} } VariantConstructor::Brace => quote! { #variant_ident {} }
}; };
Some((pattern, named_fn(name))) Ok(Some((pattern, named_fn(name)?)))
} }
VariantType::Other { field_name } => { VariantType::Other { field_name } => {
let field_name_tokens = match field_name { let field_name_tokens = match field_name {
Some(field_name) => field_name.to_token_stream(), Some(field_name) => field_name.to_token_stream(),
None => quote! { __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! { #variant_ident { #field_name_tokens } },
None => quote! { #variant_ident (#field_name_tokens) } None => quote! { #variant_ident (#field_name_tokens) }
}; };
Some((pattern, other_fn(field_name_tokens))) Ok(Some((pattern, other_fn(field_name_tokens)?)))
} }
} }
} }

@ -0,0 +1,138 @@
use proc_macro::TokenStream;
use std::iter;
use quote::quote;
use syn::{Data, DataEnum, DeriveInput};
use error::{MacroError, MacroResult};
mod enums;
mod attribute;
mod error;
const CRATE_ATTR: &'static str = "enumscribe";
const NAME: &'static str = "str";
const OTHER: &'static str = "other";
const IGNORE: &'static str = "ignore";
const CASE_INSENSITIVE: &'static str = "case_insensitive";
macro_rules! proc_try {
($x:expr) => {
match $x {
Ok(val) => val,
Err(err) => return err.into()
}
};
}
#[proc_macro_derive(ScribeString, attributes(enumscribe))]
pub fn derive_enum_to_string(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input)
.expect("failed to parse 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() {
match variant.match_variant(
|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!(
"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).into(),
Err(err) => return err.into()
}
}
(quote! {
impl ::enumscribe::ScribeString for #enum_ident {
fn scribe(&self) -> ::std::string::String {
match self {
#(#enum_idents::#match_patterns => #match_results,)*
}
}
}
}).into()
}
#[proc_macro_derive(TryScribeString, attributes(enumscribe))]
pub fn derive_try_enum_to_string(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input)
.expect("failed to parse 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,
Err(err) => return err.into()
}
}
let ignore_arm = if ignore_variant {
quote! { _ => ::std::option::Option::None, }
} else {
quote! {}
};
(quote! {
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()
}
fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> {
let enum_data = match &input.data {
Data::Enum(enum_data) => enum_data,
Data::Struct(_) => return Err(MacroError::new("enumscribe cannot be used for structs", input.ident.span())),
Data::Union(_) => return Err(MacroError::new("enumscribe cannot be used for unions", input.ident.span()))
};
if enum_data.variants.is_empty() {
return Err(MacroError::new("enumscribe cannot be used for empty enums", input.ident.span()));
}
Ok(enum_data)
}

@ -0,0 +1,10 @@
[package]
name = "enumscribe_examples"
version = "0.1.0"
authors = ["Tom Panton <pantonshire@gmail.com>"]
edition = "2018"
license = "MIT"
[dev-dependencies]
enumscribe = { path = "../enumscribe" }
enumscribe_derive = { path = "../enumscribe_derive" }

@ -1,7 +1,7 @@
#[macro_use] use enumscribe::*;
extern crate enumscribe;
#[derive(EnumToString)] // #[derive(ScribeString)]
#[derive(TryScribeString)]
enum Airport { enum Airport {
#[enumscribe(str = "LHR", case_insensitive)] #[enumscribe(str = "LHR", case_insensitive)]
Heathrow, Heathrow,
@ -9,7 +9,7 @@ enum Airport {
Gatwick, Gatwick,
#[enumscribe(str = "LTN", case_insensitive)] #[enumscribe(str = "LTN", case_insensitive)]
Luton, Luton,
#[enumscribe(str = "BHX", case_insensitive)] #[enumscribe(str = "BHX", case_insensitive, ignore)]
BirminghamInternational, BirminghamInternational,
#[enumscribe(other)] #[enumscribe(other)]
Other(String), Other(String),
@ -17,6 +17,6 @@ enum Airport {
fn main() { fn main() {
let luton = Airport::Luton; let luton = Airport::Luton;
let luton_str: String = luton.into(); let luton_string = luton.try_scribe();
println!("Hello, {}!", luton_str); println!("Hello, {:?}!", luton_string);
} }

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

@ -0,0 +1,8 @@
[package]
name = "enumscribe_tests"
version = "0.1.0"
authors = ["Tom Panton <pantonshire@gmail.com>"]
edition = "2018"
license = "MIT"
[dependencies]

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

@ -1,87 +0,0 @@
use proc_macro::TokenStream;
use std::iter;
use quote::quote;
use syn::{Data, DataEnum, DeriveInput};
use error::{MacroError, MacroResult};
use crate::enums::VariantType;
mod enums;
mod attribute;
mod error;
const CRATE_ATTR: &'static str = "enumscribe";
const NAME: &'static str = "str";
const OTHER: &'static str = "other";
const IGNORE: &'static str = "ignore";
const CASE_INSENSITIVE: &'static str = "case_insensitive";
macro_rules! proc_try {
($x:expr) => {
match $x {
Ok(val) => val,
Err(err) => return err.into()
}
};
}
#[proc_macro_derive(EnumToString, attributes(enumscribe))]
pub fn derive_enum_to_string(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input)
.expect("failed to parse 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);
if parsed_enum.variants.is_empty() {
return MacroError::new(format!(
"cannot derive EnumToString for {} because it has no variants",
enum_ident.to_string()
), input.ident.span()).into();
}
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() {
if let Some((pattern, result)) = variant.match_variant(
|name| quote! { <_ as ::std::borrow::ToOwned>::to_owned(#name) },
|field| quote! { <_ as ::std::convert::Into<::std::string::String>>::into(#field) }
) {
match_patterns.push(pattern);
match_results.push(result);
} else {
return MacroError::new(format!(
"cannot derive EnumToString 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", //TODO: suggest another derive to use instead
enum_ident.to_string(), variant.data.ident.to_string(), IGNORE,
variant.data.ident.to_string(),
), variant.span).into();
}
}
(quote! {
impl ::std::convert::From<#enum_ident> for ::std::string::String {
fn from(__enum_to_scribe: #enum_ident) -> Self {
match __enum_to_scribe {
#(#enum_idents::#match_patterns => #match_results),*
}
}
}
}).into()
}
fn get_enum_data(input: &DeriveInput) -> MacroResult<&DataEnum> {
match &input.data {
Data::Enum(data) => Ok(data),
Data::Struct(_) => Err(MacroError::new("enumscribe cannot be used for structs", input.ident.span())),
Data::Union(_) => Err(MacroError::new("enumscribe cannot be used for unions", input.ident.span()))
}
}
Loading…
Cancel
Save