litcheck_filecheck/pattern/matcher/matchers/
smart.rs

1mod builder;
2mod instruction;
3mod operand;
4mod searcher;
5
6pub use self::builder::SmartMatcherBuilder;
7use self::instruction::{CaptureGroup, MatchOp};
8use self::operand::Operand;
9use self::searcher::SmartSearcher;
10
11use crate::common::*;
12
13use regex_automata::util::{
14    captures::{Captures, GroupInfo},
15    primitives::NonMaxUsize,
16};
17
18/// This is a combination, jack-of-all trades matcher, which is
19/// executes a set of smaller matches to form a large one. Some
20/// parts of the "pattern" are known statically, while others
21/// rely on runtime variable bindings and expression evaluation,
22/// hence "smart". This is the default matcher that is used any
23/// time match blocks or substitutions are present in a pattern.
24pub struct SmartMatcher<'a> {
25    span: SourceSpan,
26    /// The underlying "smart" components of this matcher.
27    ///
28    /// These parts are matched and evaluated left-to-right,
29    /// each one is fully processed before moving to the next
30    /// part. The matcher maintains its own workspace for
31    /// locally-bound variables and the like, so that if the
32    /// pattern fails, none of the state changes are applied
33    /// globally
34    parts: Vec<MatchOp<'a>>,
35    /// Capture group metadata for this regex
36    group_info: GroupInfo,
37}
38impl<'a> fmt::Debug for SmartMatcher<'a> {
39    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40        f.debug_struct("SmartMatcher")
41            .field("parts", &self.parts)
42            .field(
43                "group_info",
44                &self
45                    .group_info
46                    .all_names()
47                    .collect::<smallvec::SmallVec<[_; 4]>>(),
48            )
49            .finish()
50    }
51}
52impl<'a> Spanned for SmartMatcher<'a> {
53    fn span(&self) -> SourceSpan {
54        self.span
55    }
56}
57impl<'a> SmartMatcher<'a> {
58    fn new(
59        span: SourceSpan,
60        parts: Vec<MatchOp<'a>>,
61        group_info: Vec<Vec<Option<Cow<'a, str>>>>,
62    ) -> Self {
63        let group_info =
64            GroupInfo::new(group_info).expect("expected capturing groups to meet all requirements");
65        Self {
66            span,
67            parts,
68            group_info,
69        }
70    }
71
72    pub fn build<'config>(
73        span: SourceSpan,
74        config: &'config Config,
75        interner: &'config mut StringInterner,
76    ) -> SmartMatcherBuilder<'a, 'config> {
77        SmartMatcherBuilder::new(span, config, interner)
78    }
79
80    pub fn group_info(&self) -> &GroupInfo {
81        &self.group_info
82    }
83}
84impl<'a> MatcherMut for SmartMatcher<'a> {
85    fn try_match_mut<'input, 'context, C>(
86        &self,
87        input: Input<'input>,
88        context: &mut C,
89    ) -> DiagResult<MatchResult<'input>>
90    where
91        C: Context<'input, 'context> + ?Sized,
92    {
93        let mut captures = Captures::all(self.group_info().clone());
94        let searcher = SmartSearcher::new(self.span, input, &mut captures);
95        searcher.search_captures(&self.parts, context)?;
96        if let Some(matched) = captures.get_match() {
97            extract_captures_from_match(self.span, matched, &captures, &self.parts, context)
98        } else {
99            Ok(MatchResult::failed(
100                CheckFailedError::MatchNoneButExpected {
101                    span: self.span,
102                    match_file: context.match_file(),
103                    note: None,
104                },
105            ))
106        }
107    }
108}
109
110fn extract_captures_from_match<'a, 'input, 'context, C>(
111    pattern_span: SourceSpan,
112    matched: regex_automata::Match,
113    captures: &Captures,
114    patterns: &[MatchOp<'a>],
115    context: &C,
116) -> DiagResult<MatchResult<'input>>
117where
118    C: Context<'input, 'context> + ?Sized,
119{
120    use crate::ast::Capture;
121
122    let overall_span = SourceSpan::from(matched.range());
123    let mut capture_infos = Vec::with_capacity(captures.group_len());
124    let input = context.search();
125    let slots = captures.slots();
126    for op in patterns {
127        match op {
128            MatchOp::Regex {
129                ref source,
130                captures: group_captures,
131                ..
132            } => {
133                let pattern_span = source.span();
134                for group in group_captures.iter() {
135                    if matches!(group.info, Capture::Ignore(_)) {
136                        continue;
137                    }
138                    match capture_group_to_capture_info(
139                        overall_span,
140                        pattern_span,
141                        group,
142                        captures,
143                        slots,
144                        context,
145                        &input,
146                    ) {
147                        Ok(None) => continue,
148                        Ok(Some(capture_info)) => {
149                            capture_infos.push(capture_info);
150                        }
151                        Err(error) => return Ok(MatchResult::failed(error)),
152                    }
153                }
154            }
155            MatchOp::Numeric {
156                span,
157                capture: Some(ref group),
158                ..
159            } => {
160                match capture_group_to_capture_info(
161                    overall_span,
162                    *span,
163                    group,
164                    captures,
165                    slots,
166                    context,
167                    &input,
168                ) {
169                    Ok(None) => continue,
170                    Ok(Some(capture_info)) => {
171                        capture_infos.push(capture_info);
172                    }
173                    Err(error) => return Ok(MatchResult::failed(error)),
174                }
175            }
176            MatchOp::Substitution { .. }
177            | MatchOp::Bind { .. }
178            | MatchOp::Drop
179            | MatchOp::Constraint { .. }
180            | MatchOp::Literal(_)
181            | MatchOp::Numeric { .. } => (),
182        }
183    }
184    Ok(MatchResult::ok(MatchInfo {
185        span: overall_span,
186        pattern_span,
187        pattern_id: 0,
188        captures: capture_infos,
189    }))
190}
191
192fn capture_group_to_capture_info<'input, 'context, C>(
193    overall_span: SourceSpan,
194    pattern_span: SourceSpan,
195    group: &CaptureGroup,
196    captures: &Captures,
197    slots: &[Option<NonMaxUsize>],
198    context: &C,
199    input: &Input<'input>,
200) -> Result<Option<CaptureInfo<'input>>, CheckFailedError>
201where
202    C: Context<'input, 'context> + ?Sized,
203{
204    let group_info = captures.group_info();
205
206    let (a, b) = group_info
207        .slots(group.pattern_id, group.group_id)
208        .expect("invalid capture group");
209    if let Some(start) = slots[a].map(|offset| offset.get()) {
210        let end = slots[b].expect("expected end offset to be present").get();
211        let captured = input.as_str(start..end);
212        let capture_span = SourceSpan::from(start..end);
213
214        super::regex::try_convert_capture_to_type(
215            group.pattern_id,
216            group.group_id,
217            pattern_span,
218            overall_span,
219            Span::new(capture_span, captured),
220            group.info,
221            captures,
222            context,
223        )
224        .map(Some)
225    } else {
226        Ok(None)
227    }
228}