structmeta/
helpers.rs

1use proc_macro2::{Ident, Spacing, Span, TokenStream};
2use syn::{
3    braced, bracketed,
4    ext::IdentExt,
5    parenthesized,
6    parse::{discouraged::Speculative, ParseBuffer, ParseStream},
7    token::{self},
8    MacroDelimiter, Result, Token,
9};
10
11pub mod exports {
12    pub use proc_macro2;
13    pub use quote;
14    pub use syn;
15}
16
17pub enum NameIndex {
18    Flag(std::result::Result<usize, Ident>),
19    NameValue(std::result::Result<usize, Ident>),
20    NameArgs(std::result::Result<usize, Ident>),
21}
22
23#[allow(clippy::too_many_arguments)]
24pub fn try_parse_name(
25    input: ParseStream,
26    flag_names: &[&str],
27    flag_rest: bool,
28    name_value_names: &[&str],
29    name_value_rest: bool,
30    name_args_names: &[&str],
31    name_args_rest: bool,
32    no_unnamed: bool,
33    name_filter: &dyn Fn(&str) -> bool,
34) -> Result<Option<(NameIndex, Span)>> {
35    let may_flag = !flag_names.is_empty() || flag_rest;
36    let may_name_value = !name_value_names.is_empty() || name_value_rest;
37    let may_name_args = !name_args_names.is_empty() || name_args_rest;
38    let fork = input.fork();
39    if let Ok(ident) = Ident::parse_any(&fork) {
40        if name_filter(&ident.to_string()) {
41            let span = ident.span();
42            let mut kind = None;
43            if (no_unnamed || may_flag) && (fork.peek(Token![,]) || fork.is_empty()) {
44                if let Some(i) = name_index_of(flag_names, flag_rest, &ident) {
45                    input.advance_to(&fork);
46                    return Ok(Some((NameIndex::Flag(i), span)));
47                }
48                kind = Some(ArgKind::Flag);
49            } else if (no_unnamed || may_name_value) && peek_eq_op(&fork) {
50                if let Some(i) = name_index_of(name_value_names, name_value_rest, &ident) {
51                    fork.parse::<Token![=]>()?;
52                    input.advance_to(&fork);
53                    return Ok(Some((NameIndex::NameValue(i), span)));
54                }
55                kind = Some(ArgKind::NameValue);
56            } else if (no_unnamed || may_name_args) && fork.peek(token::Paren) {
57                if let Some(i) = name_index_of(name_args_names, name_args_rest, &ident) {
58                    input.advance_to(&fork);
59                    return Ok(Some((NameIndex::NameArgs(i), span)));
60                }
61                kind = Some(ArgKind::NameArgs);
62            };
63
64            if kind.is_some() || no_unnamed {
65                let mut expected = Vec::new();
66                if let Some(name) = name_of(flag_names, flag_rest, &ident) {
67                    expected.push(format!("flag `{name}`"));
68                }
69                if let Some(name) = name_of(name_value_names, name_value_rest, &ident) {
70                    expected.push(format!("`{name} = ...`"));
71                }
72                if let Some(name) = name_of(name_args_names, name_args_rest, &ident) {
73                    expected.push(format!("`{name}(...)`"));
74                }
75                if !expected.is_empty() {
76                    return Err(input.error(msg(
77                        &expected,
78                        kind.map(|kind| Arg {
79                            kind,
80                            ident: &ident,
81                        }),
82                    )));
83                }
84                let help = if let Some(similar_name) =
85                    find_similar_name(&[flag_names, name_value_names, name_args_names], &ident)
86                {
87                    format!(" (help: a parameter with a similar name exists: `{similar_name}`)",)
88                } else {
89                    "".into()
90                };
91                return Err(input.error(format!(
92                    "cannot find parameter `{ident}` in this scope{help}"
93                )));
94            }
95        }
96    }
97    if no_unnamed {
98        let message = if may_flag || may_name_value || may_name_args {
99            "too many unnamed arguments."
100        } else {
101            "too many arguments."
102        };
103        return Err(input.error(message));
104    }
105    Ok(None)
106}
107fn peek_eq_op(input: ParseStream) -> bool {
108    if let Some((p, _)) = input.cursor().punct() {
109        p.as_char() == '=' && p.spacing() == Spacing::Alone
110    } else {
111        false
112    }
113}
114fn name_index_of(
115    names: &[&str],
116    rest: bool,
117    ident: &Ident,
118) -> Option<std::result::Result<usize, Ident>> {
119    if let Some(index) = find(names, ident) {
120        Some(Ok(index))
121    } else if rest {
122        Some(Err(ident.clone()))
123    } else {
124        None
125    }
126}
127fn name_of(names: &[&str], rest: bool, ident: &Ident) -> Option<String> {
128    if rest {
129        Some(ident.to_string())
130    } else {
131        find(names, ident).map(|i| names[i].to_string())
132    }
133}
134fn find(names: &[&str], ident: &Ident) -> Option<usize> {
135    names.iter().position(|name| ident == name)
136}
137fn msg(expected: &[String], found: Option<Arg>) -> String {
138    if expected.is_empty() {
139        return "unexpected token.".into();
140    }
141    let mut m = String::new();
142    m.push_str("expected ");
143    for i in 0..expected.len() {
144        if i != 0 {
145            let sep = if i == expected.len() - 1 {
146                " or "
147            } else {
148                ", "
149            };
150            m.push_str(sep);
151        }
152        m.push_str(&expected[i]);
153    }
154    if let Some(arg) = found {
155        m.push_str(", found ");
156        m.push_str(&match arg.kind {
157            ArgKind::Flag => format!("`{}`", arg.ident),
158            ArgKind::NameValue => format!("`{} = ...`", arg.ident),
159            ArgKind::NameArgs => format!("`{}`(...)", arg.ident),
160        });
161    }
162    m
163}
164fn find_similar_name<'a>(names: &[&[&'a str]], ident: &Ident) -> Option<&'a str> {
165    let c0: Vec<_> = ident.to_string().chars().collect();
166    let mut c1 = Vec::new();
167    let mut r = None;
168    let mut r_d = usize::max_value();
169    for &names in names {
170        for &name in names {
171            c1.clear();
172            c1.extend(name.chars());
173            if let Some(d) = distance(&c0, &c1) {
174                if d < r_d {
175                    r_d = d;
176                    r = Some(name);
177                }
178                if d == r_d && Some(name) != r {
179                    return None;
180                }
181            }
182        }
183    }
184    r
185}
186
187fn distance(s0: &[char], s1: &[char]) -> Option<usize> {
188    if s0.len() > s1.len() {
189        return distance(s1, s0);
190    }
191    if s0.len() + 1 < s1.len() {
192        return None;
193    }
194    let mut start = 0;
195    while start < s0.len() && start < s1.len() && s0[start] == s1[start] {
196        start += 1;
197    }
198    let mut end = 0;
199    while start + end < s0.len()
200        && start + end < s1.len()
201        && s0[s0.len() - end - 1] == s1[s1.len() - end - 1]
202    {
203        end += 1;
204    }
205    if s0.len() == s1.len() {
206        if start + end == s0.len() {
207            return Some(0);
208        }
209        if start + end + 1 == s0.len() {
210            return Some(1);
211        }
212        if start + end + 2 == s0.len() && s0[start] == s1[start + 1] && s0[start + 1] == s1[start] {
213            return Some(2);
214        }
215    } else if start + end == s0.len() {
216        return Some(1);
217    }
218
219    None
220}
221
222pub fn is_snake_case(s: &str) -> bool {
223    s.chars()
224        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
225}
226
227pub fn surround_macro_delimiter<F>(this: &MacroDelimiter, tokens: &mut TokenStream, f: F)
228where
229    F: FnOnce(&mut TokenStream),
230{
231    match this {
232        MacroDelimiter::Paren(p) => p.surround(tokens, f),
233        MacroDelimiter::Bracket(b) => b.surround(tokens, f),
234        MacroDelimiter::Brace(b) => b.surround(tokens, f),
235    }
236}
237
238pub fn parse_macro_delimiter<'a>(
239    input: &ParseBuffer<'a>,
240) -> Result<(MacroDelimiter, ParseBuffer<'a>)> {
241    let content;
242    let token = if input.peek(token::Paren) {
243        MacroDelimiter::Paren(parenthesized!(content in input))
244    } else if input.peek(token::Bracket) {
245        MacroDelimiter::Bracket(bracketed!(content in input))
246    } else if input.peek(token::Brace) {
247        MacroDelimiter::Brace(braced!(content in input))
248    } else {
249        return Err(input.error("expected `(`, `[` or `{`"));
250    };
251    Ok((token, content))
252}
253
254#[doc(hidden)]
255#[macro_export]
256macro_rules! helpers_parse_macro_delimiter {
257    ($content:ident in $input:ident) => {
258        match $crate::helpers::parse_macro_delimiter($input) {
259            Ok((token, content)) => {
260                $content = content;
261                token
262            }
263            Err(e) => return Err(e),
264        }
265    };
266}
267
268#[test]
269fn test_is_near() {
270    fn check(s0: &str, s1: &str, e: Option<usize>) {
271        let c0: Vec<_> = s0.chars().collect();
272        let c1: Vec<_> = s1.chars().collect();
273        assert_eq!(distance(&c0, &c1), e, "{s0} , {s1}")
274    }
275    check("a", "a", Some(0));
276    check("a", "b", Some(1));
277    check("a", "ab", Some(1));
278    check("ab", "a", Some(1));
279    check("a", "aa", Some(1));
280    check("ab", "ba", Some(2));
281}
282
283enum ArgKind {
284    Flag,
285    NameValue,
286    NameArgs,
287}
288struct Arg<'a> {
289    kind: ArgKind,
290    ident: &'a Ident,
291}