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            let matched =
98                extract_captures_from_match(self.span, matched, &captures, &self.parts, context)?;
99            matched.bind_captures_in(context);
100            Ok(matched)
101        } else {
102            Ok(MatchResult::failed(
103                CheckFailedError::MatchNoneButExpected {
104                    span: self.span,
105                    match_file: context.match_file(),
106                    note: None,
107                },
108            ))
109        }
110    }
111}
112
113fn extract_captures_from_match<'a, 'input, 'context, C>(
114    pattern_span: SourceSpan,
115    matched: regex_automata::Match,
116    captures: &Captures,
117    patterns: &[MatchOp<'a>],
118    context: &C,
119) -> DiagResult<MatchResult<'input>>
120where
121    C: Context<'input, 'context> + ?Sized,
122{
123    use crate::ast::Capture;
124
125    let overall_span = SourceSpan::from(matched.range());
126    let mut capture_infos = Vec::with_capacity(captures.group_len());
127    let input = context.search();
128    let slots = captures.slots();
129    for op in patterns {
130        match op {
131            MatchOp::Regex {
132                ref source,
133                captures: group_captures,
134                ..
135            } => {
136                let pattern_span = source.span();
137                for group in group_captures.iter() {
138                    if matches!(group.info, Capture::Ignore(_)) {
139                        continue;
140                    }
141                    match capture_group_to_capture_info(
142                        overall_span,
143                        pattern_span,
144                        group,
145                        captures,
146                        slots,
147                        context,
148                        &input,
149                    ) {
150                        Ok(None) => continue,
151                        Ok(Some(capture_info)) => {
152                            capture_infos.push(capture_info);
153                        }
154                        Err(error) => return Ok(MatchResult::failed(error)),
155                    }
156                }
157            }
158            MatchOp::Numeric {
159                span,
160                capture: Some(ref group),
161                ..
162            } => {
163                match capture_group_to_capture_info(
164                    overall_span,
165                    *span,
166                    group,
167                    captures,
168                    slots,
169                    context,
170                    &input,
171                ) {
172                    Ok(None) => continue,
173                    Ok(Some(capture_info)) => {
174                        capture_infos.push(capture_info);
175                    }
176                    Err(error) => return Ok(MatchResult::failed(error)),
177                }
178            }
179            MatchOp::Substitution { .. }
180            | MatchOp::Bind { .. }
181            | MatchOp::Drop
182            | MatchOp::Constraint { .. }
183            | MatchOp::Literal(_)
184            | MatchOp::Numeric { .. } => (),
185        }
186    }
187    Ok(MatchResult::ok(MatchInfo {
188        span: overall_span,
189        pattern_span,
190        pattern_id: 0,
191        captures: capture_infos,
192    }))
193}
194
195fn capture_group_to_capture_info<'input, 'context, C>(
196    overall_span: SourceSpan,
197    pattern_span: SourceSpan,
198    group: &CaptureGroup,
199    captures: &Captures,
200    slots: &[Option<NonMaxUsize>],
201    context: &C,
202    input: &Input<'input>,
203) -> Result<Option<CaptureInfo<'input>>, CheckFailedError>
204where
205    C: Context<'input, 'context> + ?Sized,
206{
207    let group_info = captures.group_info();
208
209    let (a, b) = group_info
210        .slots(group.pattern_id, group.group_id)
211        .expect("invalid capture group");
212    if let Some(start) = slots[a].map(|offset| offset.get()) {
213        let end = slots[b].expect("expected end offset to be present").get();
214        let captured = input.as_str(start..end);
215        let capture_span = SourceSpan::from(start..end);
216
217        super::regex::try_convert_capture_to_type(
218            group.pattern_id,
219            group.group_id,
220            pattern_span,
221            overall_span,
222            Span::new(capture_span, captured),
223            group.info,
224            captures,
225            context,
226        )
227        .map(Some)
228    } else {
229        Ok(None)
230    }
231}