Skip to main content

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