easy_macros_attributes/
internal.rs

1//! Internal implementation details. Not intended for direct use.
2//!
3//! This module contains the low-level attribute parsing machinery used by the public macros.
4//! Use [`get_attributes!`] and [`fields_get_attributes!`] instead.
5
6use always_context::always_context;
7use anyhow::Context;
8use helpers::context;
9use lazy_static::lazy_static;
10use proc_macro2::TokenTree;
11use quote::ToTokens;
12use syn::{Ident, LitStr};
13
14lazy_static! {
15    pub(crate) static ref UNKNOWN: &'static str = "__unknown__";
16    pub(crate) static ref UNKNOWN_REGEX: regex::Regex =
17        regex::Regex::new(&regex::escape(*UNKNOWN)).expect("Failed to create regex for unknown");
18}
19
20/// Low-level attribute parser used internally by the attribute extraction macros.
21///
22/// **You should not use this type directly.** Use [`get_attributes!`] or [`fields_get_attributes!`] instead.
23///
24/// This type is exported for technical reasons (used by the proc macro crate) but is not
25/// part of the stable API. Implementation details may change without notice.
26#[derive(Debug)]
27pub struct AttrWithUnknown {
28    //Unknown coordinates
29    ///Inside of final group/global
30    unknown_coordinate: usize,
31    unknown_group_coordinates: Vec<usize>,
32    ///Inside of ident or literal
33    partial_unknown_cords: PartialUnknownPos,
34    ///In the same group as the unknown
35    ///
36    /// In reverse order (right to left)
37    tokens_after_unknown: Vec<proc_macro2::TokenTree>,
38    before_unknown: String,
39    after_unknown: String,
40}
41
42#[derive(Debug)]
43struct PartialUnknownPos {
44    skip_start: usize,
45    skip_end: usize,
46}
47
48#[always_context]
49impl AttrWithUnknown {
50    pub fn new(attr: &syn::Attribute) -> anyhow::Result<Option<AttrWithUnknown>> {
51        let stream = attr.to_token_stream();
52        let string = stream.to_string();
53        if let Some(pos) = string.find(*UNKNOWN) {
54            //Get before and after unknown
55            let before_unknown = string.get(..pos)?.to_string();
56            let after_unknown = string.get(pos + UNKNOWN.len()..)?.to_string();
57
58            //Get all tokens, coordinates, and tokens after unknown
59            //later remove last coordinate and use it as `unknown_coordinate`
60            let mut unknown_group_coordinates = vec![];
61
62            struct DataRecursiveResult {
63                partial_unknown_cords: PartialUnknownPos,
64                tokens_after: Vec<proc_macro2::TokenTree>,
65            }
66
67            ///# Return
68            /// Tokens after unknown (in the same group) (not reversed)
69            fn unknown_data_recursive(
70                token_stream: proc_macro2::TokenStream,
71                unknown_group_coordinates: &mut Vec<usize>,
72            ) -> Option<DataRecursiveResult> {
73                let mut tokens_after: Option<Vec<TokenTree>> = None;
74                let mut partial_unknown_cords = None;
75
76                for (index, token) in token_stream.into_iter().enumerate() {
77                    if let Some(tokens_after) = &mut tokens_after {
78                        //Unknown was found
79                        tokens_after.push(token);
80                    } else {
81                        //Unknown not found yet
82                        match token {
83                            TokenTree::Group(group) => {
84                                unknown_group_coordinates.push(index);
85                                let result = unknown_data_recursive(
86                                    group.stream(),
87                                    unknown_group_coordinates,
88                                );
89                                if result.is_some() {
90                                    return result;
91                                } else {
92                                    unknown_group_coordinates.pop();
93                                }
94                            }
95                            TokenTree::Ident(ident) => {
96                                let ident_str = ident.to_string();
97                                let unknown_pos = UNKNOWN_REGEX.find(&ident_str);
98
99                                if let Some(u_pos) = unknown_pos {
100                                    //Unknown found!
101                                    tokens_after = Some(vec![]);
102                                    unknown_group_coordinates.push(index);
103                                    partial_unknown_cords = Some(PartialUnknownPos {
104                                        skip_start: u_pos.start(),
105                                        skip_end: ident_str.len() - u_pos.end(),
106                                    });
107                                }
108                            }
109                            TokenTree::Punct(_) => {}
110                            TokenTree::Literal(literal) => {
111                                let literal_str = literal.to_string();
112                                let unknown_pos = UNKNOWN_REGEX.find(&literal_str);
113
114                                if let Some(u_pos) = unknown_pos {
115                                    //Unknown found!
116                                    tokens_after = Some(vec![]);
117                                    unknown_group_coordinates.push(index);
118                                    partial_unknown_cords = Some(PartialUnknownPos {
119                                        skip_start: u_pos.start(),
120                                        skip_end: literal_str.len() - u_pos.end(),
121                                    });
122                                }
123                            }
124                        }
125                    }
126                }
127
128                if let (Some(partial_unknown_cords), Some(tokens_after)) =
129                    (partial_unknown_cords, tokens_after)
130                {
131                    Some(DataRecursiveResult {
132                        partial_unknown_cords,
133                        tokens_after,
134                    })
135                } else {
136                    None
137                }
138            }
139
140            let data_recursive_result =
141                unknown_data_recursive(stream, &mut unknown_group_coordinates);
142
143            let (token_after, unknown_coordinate, partial_unknown_cords) = if let Some(
144                DataRecursiveResult {
145                    partial_unknown_cords,
146                    mut tokens_after,
147                },
148            ) =
149                data_recursive_result
150            {
151                //Reverse the tokens after unknown
152                tokens_after.reverse();
153                //Remove the last token (which is the unknown cord inside of last group)
154                let unknown_coordinate = unknown_group_coordinates.pop().with_context(context!(
155                    "No unknown coordinates, but tokens after are not None! | tokens_after: {:?}",
156                    tokens_after
157                ))?;
158                (tokens_after, unknown_coordinate, partial_unknown_cords)
159            } else {
160                anyhow::bail!(
161                    "Unknown not found in the attribute! Recursive call failed, but it shouldn't"
162                );
163            };
164
165            return Ok(Some(AttrWithUnknown {
166                before_unknown,
167                after_unknown,
168                unknown_coordinate,
169                tokens_after_unknown: token_after,
170                unknown_group_coordinates,
171                partial_unknown_cords,
172            }));
173        }
174        Ok(None)
175    }
176
177    pub fn get_unknown(
178        &self,
179        attr: &syn::Attribute,
180    ) -> anyhow::Result<Option<proc_macro2::TokenStream>> {
181        //Check if start and end aligns with before and after unknown
182        let attr_tokens = attr.to_token_stream();
183        let attr_str = attr_tokens.to_string();
184
185        //Speed up the process, check if the string starts and ends with tokens before and after the unknown
186        if !(attr_str.starts_with(&self.before_unknown) && attr_str.ends_with(&self.after_unknown))
187        {
188            return Ok(None);
189        }
190
191        let mut current_tokens = attr_tokens;
192
193        for group_index in self.unknown_group_coordinates.iter() {
194            match current_tokens.into_iter().nth(*group_index) {
195                Some(TokenTree::Group(group)) => {
196                    current_tokens = group.stream();
197                }
198                i => {
199                    anyhow::bail!("Bad group index! Expected Group, got {i:?} | self: {self:?}");
200                }
201            }
202        }
203
204        //Get tokens at the unknown (and after)
205        let mut unknown_tokens = current_tokens
206            .into_iter()
207            .skip(self.unknown_coordinate)
208            .collect::<Vec<TokenTree>>();
209        let unknown_tokens_len = unknown_tokens.len();
210
211        // Remove tokens after unknown
212        if !self.tokens_after_unknown.is_empty() {
213            unknown_tokens.drain(unknown_tokens_len - self.tokens_after_unknown.len()..);
214        }
215
216        // Handle partial_unknown_cords
217        {
218            let partial_unknown_cords = &self.partial_unknown_cords;
219
220            //Remove before unknown in ident/literal
221            if partial_unknown_cords.skip_start != 0 {
222                let mut remove_first = false;
223                match unknown_tokens.first_mut() {
224                    Some(TokenTree::Ident(ident)) => {
225                        let ident_str = ident.to_string();
226                        let unknown_replacement = &ident_str[partial_unknown_cords.skip_start..];
227
228                        if unknown_replacement.is_empty() {
229                            remove_first = true;
230                        } else {
231                            *ident = Ident::new(unknown_replacement, ident.span());
232                        }
233                    }
234                    Some(TokenTree::Literal(literal)) => {
235                        let lit_str = syn::parse2::<LitStr>(proc_macro2::TokenStream::from(
236                            TokenTree::Literal(literal.clone()),
237                        ))?;
238                        let literal_str = lit_str.value();
239                        let unknown_replacement = &literal_str[partial_unknown_cords.skip_start..];
240
241                        if unknown_replacement.is_empty() {
242                            remove_first = true;
243                        } else {
244                            *literal = proc_macro2::Literal::string(unknown_replacement);
245                        }
246                    }
247                    Some(i) => anyhow::bail!(
248                        "Expected ident or literal (for removing text before unknown), got {i}"
249                    ),
250                    None => {
251                        anyhow::bail!(
252                            "Unknown tokens is empty while looking for partial unknown! | self: {self:?}"
253                        );
254                    }
255                }
256
257                if remove_first {
258                    unknown_tokens.remove(0);
259                }
260            }
261            //Remove after unknown in ident/literal
262            if partial_unknown_cords.skip_end != 0 {
263                let mut remove_last = false;
264                match unknown_tokens.last_mut() {
265                    Some(TokenTree::Ident(ident)) => {
266                        let ident_str = ident.to_string();
267                        let unknown_replacement =
268                            &ident_str[0..ident_str.len() - partial_unknown_cords.skip_end];
269
270                        if unknown_replacement.is_empty() {
271                            remove_last = true;
272                        } else {
273                            *ident = Ident::new(unknown_replacement, ident.span());
274                        }
275                    }
276                    Some(TokenTree::Literal(literal)) => {
277                        let lit_str = syn::parse2::<LitStr>(proc_macro2::TokenStream::from(
278                            TokenTree::Literal(literal.clone()),
279                        ))?;
280                        let literal_str = lit_str.value();
281                        let unknown_replacement =
282                            &literal_str[0..literal_str.len() - partial_unknown_cords.skip_end];
283
284                        if unknown_replacement.is_empty() {
285                            remove_last = true;
286                        } else {
287                            *literal = proc_macro2::Literal::string(unknown_replacement);
288                        }
289                    }
290                    Some(i) => anyhow::bail!(
291                        "Expected ident or literal (for removing text after unknown), got {i}"
292                    ),
293                    None => {
294                        anyhow::bail!(
295                            "Unknown tokens is empty while looking for partial unknown! | self: {self:?}"
296                        );
297                    }
298                }
299
300                if remove_last {
301                    unknown_tokens.pop();
302                }
303            }
304        }
305
306        Ok(Some(proc_macro2::TokenStream::from_iter(unknown_tokens)))
307    }
308}