js_regexp_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3
4struct FlagSets {
5    d: bool,
6    i: bool,
7    g: bool,
8    s: bool,
9    m: bool,
10    y: bool,
11    u: bool,
12    v: bool,
13}
14fn new_empty_flagsets() -> FlagSets {
15    FlagSets {
16        d: false,
17        i: false,
18        g: false,
19        s: false,
20        m: false,
21        y: false,
22        u: false,
23        v: false,
24    }
25}
26fn check_flags(source: &str) -> Result<(), char> {
27    let mut flags = new_empty_flagsets();
28    let mut inner = |ch: char| -> Option<()> {
29        match ch {
30            'd' => (!flags.d).then(|| flags.d = true)?,
31            'i' => (!flags.i).then(|| flags.i = true)?,
32            'g' => (!flags.g).then(|| flags.g = true)?,
33            's' => (!flags.s).then(|| flags.s = true)?,
34            'm' => (!flags.m).then(|| flags.m = true)?,
35            'y' => (!flags.y).then(|| flags.y = true)?,
36            'u' => (!flags.u && !flags.v).then(|| flags.u = true)?,
37            'v' => (!flags.u && !flags.v).then(|| flags.v = true)?,
38            _ => return None,
39        };
40        Some(())
41    };
42    for ch in source.chars() {
43        if inner(ch).is_none() {
44            return Err(ch);
45        }
46    }
47    Ok(())
48}
49
50/// Checks validity of a flags string literal at compile time and inserts a therefore
51/// safe runtime call to [`Flags::new_unchecked`](struct.Flags.html#method.new_unchecked).
52#[proc_macro]
53pub fn flags(item: TokenStream) -> TokenStream {
54    let item = syn::parse_macro_input!(item as syn::Lit);
55    let item = match item {
56        syn::Lit::Str(v) => v,
57        _ => panic!("Not a string literal"),
58    };
59    let value = item.value();
60    if let Err(e) = check_flags(&value) {
61        panic!("Invalid flags at char: '{}'", e);
62    }
63    quote!(::js_regexp::Flags::new_unchecked(#item)).into()
64}
65
66#[derive(Clone, Copy, Debug)]
67enum Mode {
68    Skip,
69    Do,
70}
71
72#[proc_macro_derive(EnumConvert, attributes(enum_convert))]
73pub fn derive(input: TokenStream) -> TokenStream {
74    let input = syn::parse_macro_input!(input as syn::ItemEnum);
75    let enum_ident = input.ident;
76    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
77    let mut output = proc_macro::TokenStream::new();
78
79    let outer_attr = input
80        .attrs
81        .iter()
82        .find(|v| v.path().is_ident("enum_convert"));
83    let mut into_mode: Mode = Mode::Skip;
84    let mut from_mode: Mode = Mode::Skip;
85    if let Some(attr) = outer_attr {
86        let _ = attr.parse_nested_meta(|meta| {
87            if meta.path.is_ident("into") {
88                into_mode = Mode::Do;
89            } else if meta.path.is_ident("from") {
90                from_mode = Mode::Do;
91            }
92            Ok(())
93        });
94    }
95
96    for variant in input.variants {
97        let variant_ident = variant.ident;
98        let type_path = match variant.fields {
99            syn::Fields::Unnamed(v) => match v.unnamed.first() {
100                Some(v) => match &v.ty {
101                    syn::Type::Path(v) => v.to_owned(),
102                    _ => continue,
103                },
104                None => continue,
105            },
106            _ => continue,
107        };
108
109        let mut into_override = into_mode;
110        let mut from_override = from_mode;
111        let inner_attr = variant
112            .attrs
113            .iter()
114            .find(|v| v.path().is_ident("enum_convert"));
115        if let Some(attr) = inner_attr {
116            into_override = Mode::Skip;
117            from_override = Mode::Skip;
118            attr.parse_nested_meta(|meta| {
119                if meta.path.is_ident("skip") {
120                } else if meta.path.is_ident("from") {
121                    from_override = Mode::Do;
122                } else if meta.path.is_ident("into") {
123                    into_override = Mode::Do;
124                } else {
125                    return Err(meta.error("Invalid mode override"));
126                }
127                Ok(())
128            })
129            .unwrap()
130        }
131
132        if let Mode::Do = from_override {
133            output.extend(Into::<proc_macro::TokenStream>::into(quote! {
134                impl #impl_generics From<#type_path> for #enum_ident #ty_generics #where_clause {
135                    fn from(value: #type_path) -> Self {
136                        Self::#variant_ident(value)
137                    }
138                }
139            }))
140        }
141        if let Mode::Do = into_override {
142            output.extend(Into::<proc_macro::TokenStream>::into(quote! {
143                impl #impl_generics TryInto<#type_path> for #enum_ident #ty_generics #where_clause {
144                    type Error = &'static str;
145                    fn try_into(self) -> Result<#type_path, Self::Error> {
146                        match self {
147                            Self::#variant_ident(value) => Ok(value),
148                            _ => Err("Incorrect Variant"),
149                        }
150                    }
151                }
152            }));
153        }
154    }
155    output.into()
156}