litcheck_filecheck/pattern/matcher/
result.rs

1use crate::{ast::Capture, common::*};
2
3#[derive(Debug)]
4pub struct MatchResult<'input> {
5    pub ty: MatchType,
6    pub info: Option<MatchInfo<'input>>,
7}
8impl<'input> MatchResult<'input> {
9    pub fn new(ty: MatchType, info: Option<MatchInfo<'input>>) -> Self {
10        Self { ty, info }
11    }
12
13    /// An expected pattern was succesfully found in the input
14    pub fn ok(info: MatchInfo<'input>) -> Self {
15        Self {
16            ty: MatchType::MatchFoundAndExpected,
17            info: Some(info),
18        }
19    }
20
21    /// A negative match assertion, i.e. CHECK-NOT, successfully
22    /// matched by not finding the pattern in the input. Such a
23    /// "match" is empty.
24    pub fn empty() -> Self {
25        Self {
26            ty: MatchType::MatchNoneAndExcluded,
27            info: None,
28        }
29    }
30
31    /// A match failed with the given error
32    pub fn failed(error: CheckFailedError) -> Self {
33        Self {
34            ty: MatchType::Failed(error),
35            info: None,
36        }
37    }
38
39    /// A match was found, but failed with the given error,
40    pub fn match_found_but_failed(error: CheckFailedError, info: MatchInfo<'input>) -> Self {
41        Self {
42            ty: MatchType::Failed(error),
43            info: Some(info),
44        }
45    }
46
47    /// Returns true if the match succeeded.
48    ///
49    /// This does not necessarily mean there is a corresponding [MatchInfo],
50    /// such as in the case of negative matches like CHECK-NOT.
51    pub fn is_ok(&self) -> bool {
52        self.ty.is_ok()
53    }
54
55    /// Returns true if this result represents a successful CHECK-NOT match.
56    pub fn is_empty(&self) -> bool {
57        matches!(self.ty, MatchType::MatchNoneAndExcluded)
58    }
59
60    #[inline]
61    pub fn pattern_id(&self) -> Option<usize> {
62        self.info.as_ref().map(|info| info.pattern_id)
63    }
64
65    #[inline]
66    pub fn matched_range(&self) -> Option<Range<usize>> {
67        self.info.as_ref().map(|info| info.matched_range())
68    }
69
70    pub fn unwrap_err(self) -> CheckFailedError {
71        match self.ty {
72            MatchType::Failed(err) => err,
73            ty => panic!("attempted to unwrap error from {ty}"),
74        }
75    }
76
77    pub fn into_result(self) -> Result<Option<MatchInfo<'input>>, CheckFailedError> {
78        match self {
79            Self {
80                ty: MatchType::Failed(err),
81                ..
82            } => Err(err),
83            Self {
84                info: Some(info), ..
85            } => Ok(Some(info)),
86            Self { info: None, .. } => Ok(None),
87        }
88    }
89
90    pub fn bind_captures_in<'context, C>(&self, context: &mut C)
91    where
92        C: Context<'input, 'context> + ?Sized,
93    {
94        if matches!(self.ty, MatchType::MatchFoundAndExpected) {
95            if let Some(info) = self.info.as_ref() {
96                for capture in info.captures.iter() {
97                    if let Some(var) = capture.capture.variable_name() {
98                        context.env_mut().insert(var, capture.value.clone());
99                    }
100                }
101            }
102        }
103    }
104}
105
106#[derive(Debug)]
107pub enum MatchType {
108    /// Indicates a good match for an expected pattern.
109    MatchFoundAndExpected,
110    /// Indicates no match for an excluded pattern.
111    MatchNoneAndExcluded,
112    /// The match failed for some reason
113    Failed(CheckFailedError),
114}
115impl MatchType {
116    pub fn is_ok(&self) -> bool {
117        matches!(
118            self,
119            Self::MatchFoundAndExpected | Self::MatchNoneAndExcluded
120        )
121    }
122
123    pub fn match_was_found(&self) -> bool {
124        match self {
125            Self::MatchFoundAndExpected => true,
126            Self::Failed(err) => err.match_was_found(),
127            Self::MatchNoneAndExcluded => false,
128        }
129    }
130}
131impl fmt::Display for MatchType {
132    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133        match self {
134            Self::MatchFoundAndExpected => f.write_str("match found for expected pattern"),
135            Self::MatchNoneAndExcluded => f.write_str("excluded pattern was never matched"),
136            Self::Failed(err) => write!(f, "{err}"),
137        }
138    }
139}
140
141#[derive(Debug, PartialEq)]
142pub struct MatchInfo<'input> {
143    /// The span of the matched input
144    pub span: SourceSpan,
145    /// The span of the pattern that was matched
146    pub pattern_span: SourceSpan,
147    /// The index of the pattern that was matched, if applicable
148    ///
149    /// For single pattern matchers, this is always 0
150    pub pattern_id: usize,
151    /// For matchers which capture parts of the input, this
152    /// contains the captured values and their information
153    pub captures: Vec<CaptureInfo<'input>>,
154}
155impl<'input> MatchInfo<'input> {
156    pub fn new(span: impl Into<SourceSpan>, pattern_span: SourceSpan) -> Self {
157        Self::new_with_pattern(span, pattern_span, 0)
158    }
159
160    pub fn new_with_pattern(
161        span: impl Into<SourceSpan>,
162        pattern_span: SourceSpan,
163        pattern_id: usize,
164    ) -> Self {
165        Self {
166            span: span.into(),
167            pattern_span,
168            pattern_id,
169            captures: Vec::new(),
170        }
171    }
172
173    pub fn with_pattern(mut self, pattern_id: usize) -> Self {
174        self.pattern_id = pattern_id;
175        self
176    }
177
178    pub fn with_captures(mut self, captures: Vec<CaptureInfo<'input>>) -> Self {
179        self.captures = captures;
180        self
181    }
182
183    pub fn matched_range(&self) -> Range<usize> {
184        let start = self.span.offset();
185        Range::new(start, start + self.span.len())
186    }
187
188    /// Extract the value of a variable binding that was captured by this match
189    pub fn extract(&self, name: Symbol) -> Option<&Value<'input>> {
190        self.captures.iter().find_map(|cap| {
191            if cap.capture.name().filter(|v| *v == name).is_some() {
192                Some(&cap.value)
193            } else {
194                None
195            }
196        })
197    }
198
199    pub fn into_static(self) -> MatchInfo<'static> {
200        MatchInfo {
201            captures: self
202                .captures
203                .into_iter()
204                .map(CaptureInfo::into_static)
205                .collect(),
206            ..self
207        }
208    }
209}
210
211#[derive(Clone, Debug, PartialEq)]
212pub struct CaptureInfo<'input> {
213    /// The span of the capture in the input
214    pub span: SourceSpan,
215    /// The span of the pattern from which this capture originated
216    pub pattern_span: SourceSpan,
217    /// The index of the capture
218    pub index: usize,
219    /// The captured value
220    pub value: Value<'input>,
221    /// The original capture metadata, or the default "ignore"
222    pub capture: Capture,
223}
224impl<'input> Spanned for CaptureInfo<'input> {
225    fn span(&self) -> SourceSpan {
226        self.span
227    }
228}
229impl CaptureInfo<'_> {
230    pub fn into_static(self) -> CaptureInfo<'static> {
231        CaptureInfo {
232            value: match self.value {
233                Value::Undef => Value::Undef,
234                Value::Str(s) => Value::Str(s.into_owned().into()),
235                Value::Num(expr) => Value::Num(expr),
236            },
237            ..self
238        }
239    }
240}