2019-02-24 08:12:04 +03:00
|
|
|
#![recursion_limit = "1024"]
|
|
|
|
|
2018-03-02 08:50:50 +03:00
|
|
|
#[macro_use]
|
|
|
|
extern crate pmutil;
|
|
|
|
extern crate proc_macro;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate quote;
|
2019-11-17 07:21:53 +03:00
|
|
|
|
|
|
|
use syn;
|
2018-03-02 08:50:50 +03:00
|
|
|
|
2019-02-27 04:42:22 +03:00
|
|
|
use pmutil::prelude::Quote;
|
2018-03-02 08:50:50 +03:00
|
|
|
use swc_macros_common::prelude::*;
|
2019-02-27 04:42:22 +03:00
|
|
|
use syn::*;
|
2018-03-02 08:50:50 +03:00
|
|
|
|
|
|
|
/// Creates `.as_str()` and then implements `Debug` and `Display` using it.
|
2018-03-02 09:07:09 +03:00
|
|
|
///
|
|
|
|
///# Input
|
2018-03-02 08:50:50 +03:00
|
|
|
/// Enum with \`str_value\`-style **doc** comment for each variant.
|
2018-03-02 09:07:09 +03:00
|
|
|
///
|
2018-03-02 08:50:50 +03:00
|
|
|
/// e.g.
|
2018-03-02 09:07:09 +03:00
|
|
|
///
|
|
|
|
///```no_run
|
2018-03-02 08:50:50 +03:00
|
|
|
/// pub enum BinOp {
|
|
|
|
/// /// `+`
|
|
|
|
/// Add,
|
|
|
|
/// /// `-`
|
|
|
|
/// Minus,
|
|
|
|
/// }
|
|
|
|
/// ```
|
2018-03-02 09:07:09 +03:00
|
|
|
///
|
2018-03-02 08:50:50 +03:00
|
|
|
/// Currently, \`str_value\` must be live in it's own line.
|
2018-03-02 09:07:09 +03:00
|
|
|
///
|
2018-03-02 08:50:50 +03:00
|
|
|
///# Output
|
|
|
|
///
|
|
|
|
/// - `pub fn as_str(&self) -> &'static str`
|
2019-02-24 08:12:04 +03:00
|
|
|
/// - `impl serde::Serilaize`
|
|
|
|
/// - `impl serde::Deserilaize`
|
|
|
|
/// - `impl FromStr`
|
2018-03-02 08:50:50 +03:00
|
|
|
/// - `impl Debug`
|
|
|
|
/// - `impl Display`
|
2018-03-02 09:07:09 +03:00
|
|
|
///
|
2018-03-02 08:50:50 +03:00
|
|
|
///# Example
|
2018-03-02 09:07:09 +03:00
|
|
|
///
|
|
|
|
///
|
|
|
|
///```
|
2018-03-02 08:50:50 +03:00
|
|
|
/// #[macro_use]
|
|
|
|
/// extern crate string_enum;
|
2019-02-22 06:04:41 +03:00
|
|
|
/// extern crate serde;
|
2018-03-02 08:50:50 +03:00
|
|
|
///
|
|
|
|
/// #[derive(StringEnum)]
|
|
|
|
/// pub enum Tokens {
|
2018-06-02 12:01:00 +03:00
|
|
|
/// /// `a`
|
|
|
|
/// A,
|
2019-02-24 08:12:04 +03:00
|
|
|
/// /// `bar`
|
|
|
|
/// B,
|
2018-03-02 08:50:50 +03:00
|
|
|
/// }
|
2018-03-02 09:07:09 +03:00
|
|
|
/// # fn main() {
|
2018-03-02 08:50:50 +03:00
|
|
|
///
|
|
|
|
/// assert_eq!(Tokens::A.as_str(), "a");
|
2019-02-24 08:12:04 +03:00
|
|
|
/// assert_eq!(Tokens::B.as_str(), "bar");
|
2018-03-02 08:50:50 +03:00
|
|
|
///
|
|
|
|
/// assert_eq!(Tokens::A.to_string(), "a");
|
2018-03-02 09:07:09 +03:00
|
|
|
/// assert_eq!(format!("{:?}", Tokens::A), format!("{:?}", "a"));
|
|
|
|
///
|
|
|
|
/// # }
|
2018-03-02 08:50:50 +03:00
|
|
|
/// ```
|
2018-03-02 09:07:09 +03:00
|
|
|
///
|
|
|
|
///
|
|
|
|
/// All formatting flags are handled correctly.
|
2018-03-02 08:50:50 +03:00
|
|
|
#[proc_macro_derive(StringEnum)]
|
|
|
|
pub fn derive_string_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
|
|
let input = syn::parse::<syn::DeriveInput>(input)
|
|
|
|
.map(From::from)
|
|
|
|
.expect("failed to parse derive input");
|
2018-06-02 12:01:00 +03:00
|
|
|
let mut tts = TokenStream::new();
|
2018-03-02 08:50:50 +03:00
|
|
|
|
|
|
|
make_as_str(&input).to_tokens(&mut tts);
|
2019-02-24 08:12:04 +03:00
|
|
|
make_from_str(&input).to_tokens(&mut tts);
|
2018-03-02 08:50:50 +03:00
|
|
|
|
2019-02-20 05:35:41 +03:00
|
|
|
make_serialize(&input).to_tokens(&mut tts);
|
2019-02-24 08:12:04 +03:00
|
|
|
make_deserialize(&input).to_tokens(&mut tts);
|
2019-02-20 05:35:41 +03:00
|
|
|
|
2019-01-15 07:29:57 +03:00
|
|
|
derive_fmt(&input, quote_spanned!(call_site() => std::fmt::Debug)).to_tokens(&mut tts);
|
|
|
|
derive_fmt(&input, quote_spanned!(call_site() => std::fmt::Display)).to_tokens(&mut tts);
|
2018-03-02 08:50:50 +03:00
|
|
|
|
2019-02-24 08:12:04 +03:00
|
|
|
print("derive(StringEnum)", tts)
|
2018-03-02 08:50:50 +03:00
|
|
|
}
|
|
|
|
|
2018-06-02 12:01:00 +03:00
|
|
|
fn derive_fmt(i: &DeriveInput, trait_path: TokenStream) -> ItemImpl {
|
2018-11-18 13:57:35 +03:00
|
|
|
Quote::new(def_site::<Span>())
|
2018-03-02 08:50:50 +03:00
|
|
|
.quote_with(smart_quote!(
|
|
|
|
Vars {
|
|
|
|
Trait: trait_path,
|
|
|
|
Type: &i.ident,
|
|
|
|
as_str: make_as_str_ident(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
impl Trait for Type {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
let s = self.as_str();
|
|
|
|
Trait::fmt(s, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-25 07:17:05 +03:00
|
|
|
))
|
|
|
|
.parse::<ItemImpl>()
|
2018-03-02 08:50:50 +03:00
|
|
|
.with_generics(i.generics.clone())
|
|
|
|
}
|
|
|
|
|
2019-02-24 08:12:04 +03:00
|
|
|
fn get_str_value(attrs: &[Attribute]) -> String {
|
|
|
|
// TODO: Accept multiline string
|
|
|
|
let docs: Vec<_> = attrs.iter().map(doc_str).filter_map(|o| o).collect();
|
|
|
|
for raw_line in docs {
|
|
|
|
let line = raw_line.trim();
|
2019-11-17 07:21:53 +03:00
|
|
|
if line.starts_with('`') && line.ends_with('`') {
|
2019-02-24 08:12:04 +03:00
|
|
|
let mut s: String = line.split_at(1).1.into();
|
|
|
|
let new_len = s.len() - 1;
|
|
|
|
s.truncate(new_len);
|
|
|
|
return s;
|
2018-03-02 08:50:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-24 08:12:04 +03:00
|
|
|
panic!("StringEnum: Cannot determine string value of this variant")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_from_str(i: &DeriveInput) -> ItemImpl {
|
|
|
|
let arms = Binder::new_from(&i)
|
|
|
|
.variants()
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| {
|
|
|
|
// Qualified path of variant.
|
|
|
|
let qual_name = v.qual_path();
|
|
|
|
|
|
|
|
let str_value = get_str_value(&v.attrs());
|
|
|
|
|
|
|
|
let pat: Pat = Quote::new(def_site::<Span>())
|
|
|
|
.quote_with(smart_quote!(Vars { str_value }, { str_value }))
|
|
|
|
.parse();
|
|
|
|
|
|
|
|
let body = match *v.data() {
|
|
|
|
Fields::Unit => Box::new(
|
|
|
|
Quote::new(def_site::<Span>())
|
|
|
|
.quote_with(smart_quote!(Vars { qual_name }, { return Ok(qual_name) }))
|
|
|
|
.parse(),
|
|
|
|
),
|
|
|
|
_ => unreachable!("StringEnum requires all variants not to have fields"),
|
|
|
|
};
|
|
|
|
|
|
|
|
Arm {
|
|
|
|
body,
|
|
|
|
attrs: v
|
|
|
|
.attrs()
|
|
|
|
.iter()
|
|
|
|
.filter(|attr| is_attr_name(attr, "cfg"))
|
|
|
|
.cloned()
|
|
|
|
.collect(),
|
2019-11-24 08:17:27 +03:00
|
|
|
pat,
|
2019-02-24 08:12:04 +03:00
|
|
|
guard: None,
|
|
|
|
fat_arrow_token: def_site(),
|
|
|
|
comma: Some(def_site()),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.chain(::std::iter::once({
|
|
|
|
Quote::new_call_site()
|
|
|
|
.quote_with(smart_quote!(Vars{}, {
|
|
|
|
_ => Err(())
|
|
|
|
}))
|
|
|
|
.parse()
|
|
|
|
}))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let body = Expr::Match(ExprMatch {
|
|
|
|
attrs: Default::default(),
|
|
|
|
match_token: def_site(),
|
|
|
|
brace_token: def_site(),
|
|
|
|
expr: Box::new(
|
|
|
|
Quote::new_call_site()
|
|
|
|
.quote_with(smart_quote!(Vars {}, { s }))
|
|
|
|
.parse(),
|
|
|
|
),
|
|
|
|
arms,
|
|
|
|
});
|
|
|
|
|
|
|
|
Quote::new_call_site()
|
|
|
|
.quote_with(smart_quote!(
|
|
|
|
Vars {
|
|
|
|
Type: &i.ident,
|
|
|
|
body,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
impl ::std::str::FromStr for Type {
|
|
|
|
type Err = ();
|
|
|
|
fn from_str(s: &str) -> Result<Self, ()> {
|
|
|
|
body
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
))
|
|
|
|
.parse::<ItemImpl>()
|
|
|
|
.with_generics(i.generics.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_as_str(i: &DeriveInput) -> ItemImpl {
|
2018-03-02 08:50:50 +03:00
|
|
|
let arms = Binder::new_from(&i)
|
|
|
|
.variants()
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| {
|
|
|
|
// Qualified path of variant.
|
|
|
|
let qual_name = v.qual_path();
|
|
|
|
|
|
|
|
let str_value = get_str_value(&v.attrs());
|
|
|
|
|
2019-01-19 03:25:47 +03:00
|
|
|
let body = Box::new(
|
|
|
|
Quote::new(def_site::<Span>())
|
|
|
|
.quote_with(smart_quote!(Vars { str_value }, { return str_value }))
|
|
|
|
.parse(),
|
|
|
|
);
|
2018-03-02 08:50:50 +03:00
|
|
|
|
|
|
|
let pat = match *v.data() {
|
2019-01-19 03:25:47 +03:00
|
|
|
Fields::Unit => Box::new(Pat::Path(PatPath {
|
2018-03-02 08:50:50 +03:00
|
|
|
qself: None,
|
|
|
|
path: qual_name,
|
2019-11-24 08:17:27 +03:00
|
|
|
attrs: Default::default(),
|
2019-01-19 03:25:47 +03:00
|
|
|
})),
|
|
|
|
_ => Box::new(
|
|
|
|
Quote::new(def_site::<Span>())
|
|
|
|
.quote_with(smart_quote!(Vars { qual_name }, { qual_name{..} }))
|
|
|
|
.parse(),
|
|
|
|
),
|
2018-03-02 08:50:50 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
Arm {
|
|
|
|
body,
|
2018-06-02 12:01:00 +03:00
|
|
|
attrs: v
|
|
|
|
.attrs()
|
2018-03-02 08:50:50 +03:00
|
|
|
.iter()
|
|
|
|
.filter(|attr| is_attr_name(attr, "cfg"))
|
|
|
|
.cloned()
|
|
|
|
.collect(),
|
2019-11-24 08:17:27 +03:00
|
|
|
pat: Pat::Reference(PatReference {
|
2018-06-02 12:01:00 +03:00
|
|
|
and_token: def_site(),
|
|
|
|
mutability: None,
|
|
|
|
pat,
|
2019-11-24 08:17:27 +03:00
|
|
|
attrs: Default::default(),
|
|
|
|
}),
|
2018-03-02 08:50:50 +03:00
|
|
|
guard: None,
|
2018-06-02 12:01:00 +03:00
|
|
|
fat_arrow_token: def_site(),
|
2018-03-02 08:50:50 +03:00
|
|
|
comma: Some(def_site()),
|
|
|
|
}
|
2018-10-25 07:17:05 +03:00
|
|
|
})
|
|
|
|
.collect();
|
2018-03-02 08:50:50 +03:00
|
|
|
|
|
|
|
let body = Expr::Match(ExprMatch {
|
|
|
|
attrs: Default::default(),
|
|
|
|
match_token: def_site(),
|
|
|
|
brace_token: def_site(),
|
2019-01-19 03:25:47 +03:00
|
|
|
expr: Box::new(
|
|
|
|
Quote::new(def_site::<Span>())
|
|
|
|
.quote_with(smart_quote!(Vars {}, { self }))
|
|
|
|
.parse(),
|
|
|
|
),
|
2018-03-02 08:50:50 +03:00
|
|
|
arms,
|
|
|
|
});
|
|
|
|
|
2018-11-18 13:57:35 +03:00
|
|
|
Quote::new(def_site::<Span>())
|
2018-03-02 08:50:50 +03:00
|
|
|
.quote_with(smart_quote!(
|
|
|
|
Vars {
|
|
|
|
Type: &i.ident,
|
|
|
|
body,
|
|
|
|
as_str: make_as_str_ident(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
impl Type {
|
|
|
|
pub fn as_str(&self) -> &'static str {
|
|
|
|
body
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-25 07:17:05 +03:00
|
|
|
))
|
|
|
|
.parse::<ItemImpl>()
|
2018-03-02 08:50:50 +03:00
|
|
|
.with_generics(i.generics.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_as_str_ident() -> Ident {
|
|
|
|
Ident::new("as_str", call_site())
|
|
|
|
}
|
2019-02-20 05:35:41 +03:00
|
|
|
|
|
|
|
fn make_serialize(i: &DeriveInput) -> ItemImpl {
|
|
|
|
Quote::new_call_site()
|
|
|
|
.quote_with(smart_quote!(Vars { Type: &i.ident }, {
|
|
|
|
impl ::serde::Serialize for Type {
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where
|
|
|
|
S: ::serde::Serializer,
|
|
|
|
{
|
|
|
|
serializer.serialize_str(self.as_str())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
.parse::<ItemImpl>()
|
|
|
|
.with_generics(i.generics.clone())
|
|
|
|
}
|
2019-02-24 08:12:04 +03:00
|
|
|
|
|
|
|
fn make_deserialize(i: &DeriveInput) -> ItemImpl {
|
|
|
|
Quote::new_call_site()
|
|
|
|
.quote_with(smart_quote!(Vars { Type: &i.ident }, {
|
|
|
|
impl<'de> ::serde::Deserialize<'de> for Type {
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
where
|
|
|
|
D: ::serde::Deserializer<'de>,
|
|
|
|
{
|
|
|
|
struct StrVisitor;
|
|
|
|
|
|
|
|
impl<'de> ::serde::de::Visitor<'de> for StrVisitor {
|
|
|
|
type Value = Type;
|
|
|
|
|
|
|
|
fn expecting(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
|
|
|
// TODO: List strings
|
|
|
|
write!(f, "one of (TODO)")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
|
|
|
where
|
|
|
|
E: ::serde::de::Error,
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
value.parse().map_err(|()| E::unknown_variant(value, &[]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deserializer.deserialize_str(StrVisitor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
.parse::<ItemImpl>()
|
|
|
|
.with_generics(i.generics.clone())
|
|
|
|
}
|