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
91#[derive(Debug)]
92pub enum MatchType {
93    /// Indicates a good match for an expected pattern.
94    MatchFoundAndExpected,
95    /// Indicates no match for an excluded pattern.
96    MatchNoneAndExcluded,
97    /// The match failed for some reason
98    Failed(CheckFailedError),
99}
100impl MatchType {
101    pub fn is_ok(&self) -> bool {
102        matches!(
103            self,
104            Self::MatchFoundAndExpected | Self::MatchNoneAndExcluded
105        )
106    }
107
108    pub fn match_was_found(&self) -> bool {
109        match self {
110            Self::MatchFoundAndExpected => true,
111            Self::Failed(err) => err.match_was_found(),
112            Self::MatchNoneAndExcluded => false,
113        }
114    }
115}
116impl fmt::Display for MatchType {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        match self {
119            Self::MatchFoundAndExpected => f.write_str("match found for expected pattern"),
120            Self::MatchNoneAndExcluded => f.write_str("excluded pattern was never matched"),
121            Self::Failed(err) => write!(f, "{err}"),
122        }
123    }
124}
125
126#[derive(Debug, PartialEq)]
127pub struct MatchInfo<'input> {
128    /// The span of the matched input
129    pub span: SourceSpan,
130    /// The span of the pattern that was matched
131    pub pattern_span: SourceSpan,
132    /// The index of the pattern that was matched, if applicable
133    ///
134    /// For single pattern matchers, this is always 0
135    pub pattern_id: usize,
136    /// For matchers which capture parts of the input, this
137    /// contains the captured values and their information
138    pub captures: Vec<CaptureInfo<'input>>,
139}
140impl<'input> MatchInfo<'input> {
141    pub fn new(span: impl Into<SourceSpan>, pattern_span: SourceSpan) -> Self {
142        Self::new_with_pattern(span, pattern_span, 0)
143    }
144
145    pub fn new_with_pattern(
146        span: impl Into<SourceSpan>,
147        pattern_span: SourceSpan,
148        pattern_id: usize,
149    ) -> Self {
150        Self {
151            span: span.into(),
152            pattern_span,
153            pattern_id,
154            captures: Vec::new(),
155        }
156    }
157
158    pub fn with_pattern(mut self, pattern_id: usize) -> Self {
159        self.pattern_id = pattern_id;
160        self
161    }
162
163    pub fn with_captures(mut self, captures: Vec<CaptureInfo<'input>>) -> Self {
164        self.captures = captures;
165        self
166    }
167
168    pub fn matched_range(&self) -> Range<usize> {
169        let start = self.span.offset();
170        Range::new(start, start + self.span.len())
171    }
172
173    /// Extract the value of a variable binding that was captured by this match
174    pub fn extract(&self, name: Symbol) -> Option<&Value<'input>> {
175        self.captures.iter().find_map(|cap| {
176            if cap.capture.name().filter(|v| *v == name).is_some() {
177                Some(&cap.value)
178            } else {
179                None
180            }
181        })
182    }
183
184    pub fn into_static(self) -> MatchInfo<'static> {
185        MatchInfo {
186            captures: self
187                .captures
188                .into_iter()
189                .map(CaptureInfo::into_static)
190                .collect(),
191            ..self
192        }
193    }
194}
195
196#[derive(Clone, Debug, PartialEq)]
197pub struct CaptureInfo<'input> {
198    /// The span of the capture in the input
199    pub span: SourceSpan,
200    /// The span of the pattern from which this capture originated
201    pub pattern_span: SourceSpan,
202    /// The index of the capture
203    pub index: usize,
204    /// The captured value
205    pub value: Value<'input>,
206    /// The original capture metadata, or the default "ignore"
207    pub capture: Capture,
208}
209impl<'input> Spanned for CaptureInfo<'input> {
210    fn span(&self) -> SourceSpan {
211        self.span
212    }
213}
214impl CaptureInfo<'_> {
215    pub fn into_static(self) -> CaptureInfo<'static> {
216        CaptureInfo {
217            value: match self.value {
218                Value::Undef => Value::Undef,
219                Value::Str(s) => Value::Str(s.into_owned().into()),
220                Value::Num(expr) => Value::Num(expr),
221            },
222            ..self
223        }
224    }
225}