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