Skip to main content

charon_lib/name_matcher/
parser.rs

1use std::{fmt, str::FromStr};
2
3use itertools::Itertools;
4use nom::{
5    Finish, Parser,
6    bytes::complete::{tag, take_while},
7    character::complete::{multispace0, multispace1},
8    combinator::{all_consuming, cut, map_res, opt, success},
9    error::{Error, ParseError},
10    multi::separated_list0,
11    sequence::{delimited, preceded, terminated},
12};
13
14use super::{PatElem, PatTy, Pattern};
15use crate::ast::RefKind;
16
17type ParseResult<'a, T> = nom::IResult<&'a str, T, Error<&'a str>>;
18
19/// Extra methods on parsers.
20trait ParserExt<I, O, E>: Parser<I, O, E> + Sized
21where
22    I: Clone,
23    E: ParseError<I>,
24{
25    fn followed_by<F, O2>(self, suffix: F) -> impl Parser<I, O, E>
26    where
27        F: Parser<I, O2, E>,
28    {
29        terminated(self, suffix)
30    }
31
32    fn precedes<F, O2>(self, next: F) -> impl Parser<I, O2, E>
33    where
34        F: Parser<I, O2, E>,
35    {
36        preceded(self, next)
37    }
38
39    fn opt(self) -> impl Parser<I, Option<O>, E> {
40        opt(self)
41    }
42
43    fn cut(self) -> impl Parser<I, O, E> {
44        cut(self)
45    }
46}
47impl<I, O, E, P> ParserExt<I, O, E> for P
48where
49    I: Clone,
50    E: ParseError<I>,
51    P: Parser<I, O, E>,
52{
53}
54
55/// The entry point for this module: parses a string into a `Pattern`.
56impl FromStr for Pattern {
57    type Err = Error<String>;
58    fn from_str(s: &str) -> Result<Self, Self::Err> {
59        parse_pattern_complete(s)
60    }
61}
62
63fn parse_pattern_complete(i: &str) -> Result<Pattern, Error<String>> {
64    all_consuming(parse_pattern)
65        .parse(i)
66        .finish()
67        .map(|(_, pattern)| pattern)
68        .map_err(|e| Error::new(e.input.to_string(), e.code))
69}
70
71fn parse_pattern(i: &str) -> ParseResult<'_, Pattern> {
72    separated_list0(tag("::").followed_by(multispace0), parse_pat_elem)
73        .map(|elems| Pattern { elems })
74        .parse(i)
75}
76
77impl fmt::Display for Pattern {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        self.elems.iter().format("::").fmt(f)
80    }
81}
82
83impl fmt::Debug for Pattern {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        write!(f, "{self}")
86    }
87}
88
89fn parse_pat_elem(i: &str) -> ParseResult<'_, PatElem> {
90    let parse_glob = tag("*").map(|_| PatElem::Glob);
91    parse_glob
92        .or(parse_impl_elem)
93        .or(parse_simple_elem)
94        .parse(i)
95}
96
97fn parse_simple_elem(i: &str) -> ParseResult<'_, PatElem> {
98    let ident = take_while(|c: char| c.is_alphanumeric() || c == '_');
99    let (i, ident) = ident.followed_by(multispace0).parse(i)?;
100    if ident == "_" {
101        success(PatElem::Glob).parse(i)
102    } else {
103        let args = delimited(
104            tag("<").followed_by(multispace0),
105            separated_list0(
106                tag(",").followed_by(multispace0),
107                parse_pat_ty.followed_by(multispace0),
108            ),
109            tag(">"),
110        );
111        args.opt()
112            .map(|args| PatElem::Ident {
113                name: ident.to_string(),
114                generics: args.unwrap_or_default(),
115                is_trait: false,
116            })
117            .parse(i)
118    }
119}
120
121fn parse_impl_elem(i: &str) -> ParseResult<'_, PatElem> {
122    let for_ty = preceded(
123        tag("for").followed_by(multispace1),
124        parse_pat_ty.followed_by(multispace0),
125    );
126    let impl_contents = parse_pattern.followed_by(multispace0).and(for_ty.opt());
127    let impl_pattern = tag("{").followed_by(multispace0).precedes(
128        delimited(
129            tag("impl").followed_by(multispace1.cut()).opt(),
130            impl_contents,
131            tag("}"),
132        )
133        .cut(),
134    );
135    map_res(impl_pattern, |(mut pat, for_ty)| {
136        if let Some(for_ty) = for_ty {
137            let last_elem = pat
138                .elems
139                .last_mut()
140                .ok_or_else(|| anyhow::anyhow!("trait path must be nonempty"))?;
141            let PatElem::Ident {
142                generics, is_trait, ..
143            } = last_elem
144            else {
145                return Err(anyhow::anyhow!("trait path must end in an ident"));
146            };
147            // Set the type as the first generic arg.
148            generics.insert(0, for_ty);
149            *is_trait = true;
150        }
151        Ok(PatElem::Impl(pat.into()))
152    })
153    .parse(i)
154}
155
156impl fmt::Display for PatElem {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        match self {
159            PatElem::Ident {
160                name,
161                generics,
162                is_trait,
163            } => {
164                write!(f, "{name}")?;
165                let generics = generics.as_slice();
166                let (ty, generics) = if let [ty, generics @ ..] = generics
167                    && *is_trait
168                {
169                    (Some(ty), generics)
170                } else {
171                    (None, generics)
172                };
173                if !generics.is_empty() {
174                    write!(f, "<{}>", generics.iter().format(", "))?;
175                }
176                if let Some(ty) = ty {
177                    write!(f, " for {ty}")?;
178                }
179                Ok(())
180            }
181            PatElem::Impl(pat) => write!(f, "{{impl {pat}}}"),
182            PatElem::Glob => write!(f, "_"),
183        }
184    }
185}
186
187fn parse_pat_ty(i: &str) -> ParseResult<'_, PatTy> {
188    let mutability = tag("mut").followed_by(multispace0).opt().map(|mtbl| {
189        if mtbl.is_some() {
190            RefKind::Mut
191        } else {
192            RefKind::Shared
193        }
194    });
195    tag("&")
196        .followed_by(multispace0)
197        .precedes(mutability.and(parse_pat_ty))
198        .map(|(mtbl, ty)| PatTy::Ref(mtbl, ty.into()))
199        .or(parse_pattern.map(PatTy::Pat))
200        .parse(i)
201}
202
203impl fmt::Display for PatTy {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        match self {
206            PatTy::Pat(p) => write!(f, "{p}"),
207            PatTy::Ref(RefKind::Shared, ty) => write!(f, "&{ty}"),
208            PatTy::Ref(RefKind::Mut, ty) => write!(f, "&mut {ty}"),
209        }
210    }
211}
212
213#[test]
214fn test_roundtrip() {
215    let idempotent_test_strings = [
216        "crate::foo::bar",
217        "blah::_",
218        "blah::_foo",
219        "a::b::Type",
220        "a::b::Type<_, _>",
221        "Clone",
222        "usize",
223        "foo::{impl Clone for usize}::clone",
224        "foo::{impl Clone for &&usize}",
225        "foo::{impl PartialEq<_> for Type<_, _>}",
226        "foo::{impl PartialEq<usize> for Box<u8>}",
227        "foo::{impl foo::Trait<core::option::Option<_>> for alloc::boxed::Box<_>}::method",
228    ];
229    let other_test_strings = [
230        ("blah::*", "blah::_"),
231        ("crate  ::  foo  ::bar ", "crate::foo::bar"),
232        ("a::b::Type < _  ,  _ >", "a::b::Type<_, _>"),
233        ("{ impl  Clone  for  usize }", "{impl Clone for usize}"),
234        ("{Clone for usize}", "{impl Clone for usize}"),
235    ];
236    let failures = [
237        "{implClone for usize}",
238        "{impl Clone forusize}",
239        "foo::{impl  for alloc::boxed::Box<_>}::method",
240        "foo::{impl foo::_ for alloc::boxed::Box<_>}::method",
241        "foo::{impl &Clone for usize}",
242    ];
243
244    let test_strings = idempotent_test_strings
245        .into_iter()
246        .map(|s| (s, s))
247        .chain(other_test_strings);
248    for (input, expected) in test_strings {
249        let pat = Pattern::parse(input).map_err(|e| e.to_string()).unwrap();
250        assert_eq!(pat.to_string(), expected);
251    }
252
253    for input in failures {
254        assert!(
255            Pattern::parse(input).is_err(),
256            "Pattern parsed correctly but shouldn't: `{input}`"
257        );
258    }
259}