macros_macros/
lib.rs

1use macros_utils::{
2    call_site, MacroStream, Match, Parse, ParserInput, ParserOutput, Repr, Spacing, Token,
3};
4use proc_macro2::{Span, TokenStream};
5use proc_macro_error::{abort_call_site, proc_macro_error};
6use quote::quote;
7
8/// Create a parser based on a set of patterns.
9///
10/// See `Pattern` for more information on the available patterns.
11///
12/// # Example
13/// ```rs
14/// use macros_core::parser;
15///
16/// parser! {
17///     NameOfParserAndOutputStruct => {}$ { {}$ : param }@
18/// }
19///
20/// let output: NameOfParserAndOutputStruct = NameOfParserAndOutputStruct::parse(
21///     &mut proc_macro2::TokenStream::from_str("hi hello")
22///         .unwrap()
23///         .into(),
24/// );
25#[proc_macro_error]
26#[proc_macro]
27pub fn parser(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
28    match MacroStream::from_tokens(stream.into()) {
29        Err(err) => err.into_diagnostic().abort(),
30        Ok(stream) => parser_impl(stream).into(),
31    }
32}
33
34#[derive(Clone)]
35struct Empty {}
36
37impl ParserOutput for Empty {
38    fn set_match(&mut self, _: &str, _: Match) {}
39    fn name() -> &'static str {
40        "Empty"
41    }
42}
43
44fn parser_impl(mut stream: MacroStream) -> TokenStream {
45    let name = stream.pop();
46    match name {
47        Some(Token::Ident { name, .. }) => {
48            let next = stream.pop();
49            let next2 = stream.pop();
50            match (next, next2) {
51                (
52                    Some(Token::Punctuation {
53                        value: '=',
54                        spacing: Spacing::Joint,
55                        ..
56                    }),
57                    Some(Token::Punctuation {
58                        value: '>',
59                        spacing: Spacing::Alone,
60                        ..
61                    }),
62                ) => {
63                    let input = match ParserInput::<Empty>::parse(&mut stream) {
64                        Err(err) => err.into_diagnostic().abort(),
65                        Ok(input) => input,
66                    };
67                    let patterns = &input
68                        .patterns
69                        .iter()
70                        .map(|p| p.repr(&name))
71                        .collect::<Vec<_>>();
72                    let struct_name = Token::Ident {
73                        name: name.clone(),
74                        span: Span::call_site(),
75                    };
76                    let raw_params = input
77                        .params()
78                        .into_iter()
79                        .map(|(name, optional)| {
80                            let ident = Token::Ident {
81                                name,
82                                span: Span::call_site(),
83                            };
84                            (ident, optional)
85                        })
86                        .collect::<Vec<_>>();
87                    let var_params = raw_params.iter().map(|(ident, optional)| {
88                        if *optional {
89                            quote! {
90                                #ident: None,
91                            }
92                        } else {
93                            quote! {
94                                #ident: macros_utils::Match::None,
95                            }
96                        }
97                    });
98                    let struct_fields = raw_params.iter().map(|(ident, optional)| {
99                        if *optional {
100                            quote! {
101                                pub #ident: Option<macros_utils::Match>,
102                            }
103                        } else {
104                            quote! {
105                                pub #ident: macros_utils::Match,
106                            }
107                        }
108                    });
109                    let patterns_const = Token::Ident {
110                        name: format!("__{}_PATTERNS", name.to_ascii_uppercase()),
111                        span: call_site(),
112                    };
113                    let set_params = raw_params.iter().map(|(ident, _)| {
114                        let name = ident.ident().unwrap();
115                        quote! {
116                            #name => self.#ident = value,
117                        }
118                    });
119                    quote! {
120                        #[derive(Debug, Clone)]
121                        pub struct #struct_name {
122                            #(#struct_fields)*
123                        }
124
125                        macros_utils::lazy_static! {
126                            static ref #patterns_const: Vec<macros_utils::Pattern<#struct_name>> = vec![
127                                #(#patterns,)*
128                            ];
129                        }
130
131                        #[allow(clippy::never_loop)]
132                        impl macros_utils::Parse for #struct_name {
133                            fn parse(stream: &mut macros_utils::MacroStream) -> Result<Self, macros_utils::MacrosError> {
134                                let mut o = Self {
135                                    #(#var_params)*
136                                };
137                                let (res, o) = macros_utils::Pattern::<#struct_name>::match_patterns(std::borrow::Cow::Owned(o), &#patterns_const, stream);
138                                match res {
139                                    Ok(_) => Ok(o.into_owned()),
140                                    Err(e) => Err(e),
141                                }
142                            }
143                        }
144
145                        impl macros_utils::ParserOutput for #struct_name {
146                            fn set_match(&mut self, name: &str, value: macros_utils::Match) {
147                                match name {
148                                    #(#set_params)*
149                                    _ => (),
150                                }
151                            }
152
153                            fn name() -> &'static str {
154                                #name
155                            }
156                        }
157                    }
158                },
159                _ => abort_call_site!("expected => after the name of the parser"),
160            }
161        },
162        _ => abort_call_site!("expected the name of the parser first"),
163    }
164}