swc/macros/string_enum/src/lib.rs

341 lines
9.6 KiB
Rust
Raw Normal View History

#![recursion_limit = "1024"]
2018-03-02 08:50:50 +03:00
#[macro_use]
extern crate pmutil;
extern crate proc_macro;
use proc_macro2;
2018-03-02 08:50:50 +03:00
#[macro_use]
extern crate quote;
use syn;
2018-03-02 08:50:50 +03:00
use pmutil::prelude::Quote;
2018-03-02 08:50:50 +03:00
use swc_macros_common::prelude::*;
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`
/// - `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;
/// extern crate serde;
2018-03-02 08:50:50 +03:00
///
/// #[derive(StringEnum)]
/// pub enum Tokens {
/// /// `a`
/// A,
/// /// `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");
/// 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");
let mut tts = TokenStream::new();
2018-03-02 08:50:50 +03:00
make_as_str(&input).to_tokens(&mut tts);
make_from_str(&input).to_tokens(&mut tts);
2018-03-02 08:50:50 +03:00
make_serialize(&input).to_tokens(&mut tts);
make_deserialize(&input).to_tokens(&mut tts);
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
print("derive(StringEnum)", tts)
2018-03-02 08:50:50 +03:00
}
fn derive_fmt(i: &DeriveInput, trait_path: TokenStream) -> ItemImpl {
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)
}
}
}
))
.parse::<ItemImpl>()
2018-03-02 08:50:50 +03:00
.with_generics(i.generics.clone())
}
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();
if line.starts_with('`') && line.ends_with('`') {
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
}
}
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(),
pats: vec![Element::End(pat)].into_iter().collect(),
guard: None,
fat_arrow_token: def_site(),
comma: Some(def_site()),
leading_vert: None,
}
})
.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-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,
attrs: v
.attrs()
2018-03-02 08:50:50 +03:00
.iter()
.filter(|attr| is_attr_name(attr, "cfg"))
.cloned()
.collect(),
pats: vec![Element::End(Pat::Ref(PatRef {
and_token: def_site(),
mutability: None,
pat,
}))]
.into_iter()
.collect(),
2018-03-02 08:50:50 +03:00
guard: None,
fat_arrow_token: def_site(),
2018-03-02 08:50:50 +03:00
comma: Some(def_site()),
leading_vert: None,
2018-03-02 08:50:50 +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,
});
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
}
}
}
))
.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())
}
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())
}
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())
}