litcheck_filecheck/pattern/
mod.rs

1mod id;
2mod iter;
3pub mod matcher;
4mod matches;
5mod prefix;
6pub mod search;
7pub(crate) mod visitors;
8
9pub use self::id::PatternIdentifier;
10pub use self::matcher::*;
11pub use self::matches::Matches;
12pub use self::prefix::PatternPrefix;
13pub use self::search::{DefaultSearcher, PatternSearcher, Searcher};
14
15use crate::{
16    ast::{CheckPattern, CheckPatternPart},
17    common::*,
18    errors::InvalidCheckFileError,
19};
20
21/// The compiled form of [CheckPattern]
22#[derive(Debug)]
23pub enum Pattern<'a> {
24    /// This pattern always succeeds
25    Empty(AlwaysMatch),
26    /// A literal string that must occur somewhere in the input
27    Substring(SubstringMatcher<'a>),
28    /// A regular expression that must occur somewhere in the input
29    Regex(RegexMatcher<'a>),
30    /// A hybrid match expression that must occur somewhere in the input
31    Smart(SmartMatcher<'a>),
32    /// A matcher for pure ASCII whitespace patterns
33    Whitespace(AsciiWhitespaceMatcher),
34}
35impl<'a> Pattern<'a> {
36    pub fn into_matcher_mut(self) -> AnyMatcherMut<'a> {
37        match self {
38            Self::Empty(matcher) => Box::new(matcher),
39            Self::Substring(matcher) => Box::new(matcher),
40            Self::Regex(matcher) => Box::new(matcher),
41            Self::Smart(matcher) => Box::new(matcher),
42            Self::Whitespace(matcher) => Box::new(matcher),
43        }
44    }
45
46    pub fn from_prefix(
47        prefix: PatternPrefix<'a>,
48        config: &Config,
49        interner: &mut StringInterner,
50    ) -> DiagResult<Self> {
51        match prefix {
52            PatternPrefix::Literal { prefix, .. } | PatternPrefix::Substring { prefix, .. } => {
53                if prefix.is_empty() {
54                    return Err(Report::from(InvalidCheckFileError::EmptyPattern(
55                        prefix.span(),
56                    )));
57                }
58                if prefix.trim().is_empty() {
59                    if prefix.chars().all(|c| c.is_ascii_whitespace()) {
60                        Ok(Pattern::Whitespace(AsciiWhitespaceMatcher::new(
61                            prefix.span(),
62                        )))
63                    } else {
64                        Ok(Pattern::Regex(RegexMatcher::new_nocapture(
65                            Span::new(prefix.span(), Cow::Borrowed(r"\s+")),
66                            config,
67                        )?))
68                    }
69                } else {
70                    Ok(Pattern::Substring(SubstringMatcher::new(prefix, config)?))
71                }
72            }
73            PatternPrefix::Regex { prefix, .. } if prefix.captures.is_empty() => Ok(
74                Pattern::Regex(RegexMatcher::new_nocapture(prefix.pattern, config)?),
75            ),
76            PatternPrefix::Regex { prefix, .. } => {
77                Ok(Pattern::Regex(RegexMatcher::new(prefix, config, interner)?))
78            }
79            PatternPrefix::Dynamic { prefix, .. } => {
80                let mut builder = SmartMatcher::build(prefix.span(), config, interner);
81                builder.lower_match(prefix.into_owned())?;
82                Ok(Self::Smart(builder.build()))
83            }
84        }
85    }
86
87    pub fn compile(
88        mut pattern: CheckPattern<'a>,
89        config: &Config,
90        interner: &mut StringInterner,
91    ) -> DiagResult<Self> {
92        pattern.compact(interner);
93        match pattern {
94            CheckPattern::Literal(s) => Self::from_prefix(
95                PatternPrefix::Literal { prefix: s, id: 0 },
96                config,
97                interner,
98            ),
99            CheckPattern::Regex(s) => Ok(Pattern::Regex(RegexMatcher::new(s, config, interner)?)),
100            CheckPattern::Match(s) => {
101                // At least one part requires expression evaluation
102                let (span, parts) = s.into_parts();
103                let mut builder = SmartMatcher::build(span, config, interner);
104                for part in parts.into_iter() {
105                    match part {
106                        CheckPatternPart::Literal(s) => {
107                            builder.literal(s)?;
108                        }
109                        CheckPatternPart::Regex(s) => {
110                            builder.regex_pattern(s)?;
111                        }
112                        CheckPatternPart::Match(m) => {
113                            builder.lower_match(m)?;
114                        }
115                    }
116                }
117
118                let pattern = builder.build();
119
120                Ok(Pattern::Smart(pattern))
121            }
122            CheckPattern::Empty(span) => Ok(Pattern::Empty(AlwaysMatch::new(span))),
123        }
124    }
125
126    pub fn compile_static(
127        span: SourceSpan,
128        mut pattern: CheckPattern<'a>,
129        config: &Config,
130        interner: &mut StringInterner,
131    ) -> DiagResult<SimpleMatcher<'a>> {
132        pattern.compact(interner);
133
134        match pattern {
135            CheckPattern::Literal(lit) => Ok(SimpleMatcher::Substring(SubstringMatcher::new(
136                lit, config,
137            )?)),
138            CheckPattern::Regex(regex) if regex.captures.is_empty() => Ok(SimpleMatcher::Regex(
139                RegexMatcher::new(regex, config, interner)?,
140            )),
141            pattern @ (CheckPattern::Regex(_) | CheckPattern::Match(_)) => {
142                let diag = Diag::new("invalid variable usage in pattern")
143                    .with_label(Label::new(span, "occurs in this pattern"))
144                    .and_labels(
145                        pattern
146                            .locate_variables()
147                            .map(|span| Label::new(span, "occurs here").into()),
148                    )
149                    .with_help("CHECK-LABEL patterns must be literals or regular expressions");
150                Err(Report::new(diag))
151            }
152            CheckPattern::Empty(_) => unreachable!(
153                "{pattern:?} is only valid for CHECK-EMPTY, and is not an actual pattern"
154            ),
155        }
156    }
157
158    pub fn compile_literal(pattern: CheckPattern<'a>, config: &Config) -> DiagResult<Self> {
159        match pattern {
160            CheckPattern::Literal(lit) => {
161                Ok(Pattern::Substring(SubstringMatcher::new(lit, config)?))
162            }
163            CheckPattern::Regex(_) | CheckPattern::Match(_) => {
164                unreachable!("the lexer will never emit tokens for these non-terminals")
165            }
166            CheckPattern::Empty(_) => unreachable!(
167                "{pattern:?} is only valid for CHECK-EMPTY, and is not an actual pattern"
168            ),
169        }
170    }
171}
172impl<'a> MatcherMut for Pattern<'a> {
173    fn try_match_mut<'input, 'context, C>(
174        &self,
175        input: Input<'input>,
176        context: &mut C,
177    ) -> DiagResult<MatchResult<'input>>
178    where
179        C: Context<'input, 'context> + ?Sized,
180    {
181        match self {
182            Self::Substring(ref matcher) => matcher.try_match(input, context),
183            Self::Regex(ref matcher) => matcher.try_match(input, context),
184            Self::Smart(ref matcher) => matcher.try_match_mut(input, context),
185            Self::Whitespace(ref matcher) => matcher.try_match(input, context),
186            Self::Empty(ref matcher) => matcher.try_match(input, context),
187        }
188    }
189}
190impl<'a> Spanned for Pattern<'a> {
191    fn span(&self) -> SourceSpan {
192        match self {
193            Self::Substring(ref matcher) => matcher.span(),
194            Self::Regex(ref matcher) => matcher.span(),
195            Self::Smart(ref matcher) => matcher.span(),
196            Self::Whitespace(ref matcher) => matcher.span(),
197            Self::Empty(ref matcher) => matcher.span(),
198        }
199    }
200}