Skip to main content

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(prefix: PatternPrefix<'a>, config: &Config) -> DiagResult<Self> {
47        match prefix {
48            PatternPrefix::Literal { prefix, .. } | PatternPrefix::Substring { prefix, .. } => {
49                if prefix.is_empty() {
50                    return Err(Report::from(InvalidCheckFileError::EmptyPattern(
51                        prefix.span(),
52                    )));
53                }
54                if prefix.trim().is_empty() {
55                    if prefix.chars().all(|c| c.is_ascii_whitespace()) {
56                        Ok(Pattern::Whitespace(AsciiWhitespaceMatcher::new(
57                            prefix.span(),
58                        )))
59                    } else {
60                        Ok(Pattern::Regex(RegexMatcher::new_nocapture(
61                            Span::new(prefix.span(), Cow::Borrowed(r"\s+")),
62                            config,
63                        )?))
64                    }
65                } else {
66                    Ok(Pattern::Substring(SubstringMatcher::new(prefix, config)?))
67                }
68            }
69            PatternPrefix::Regex { prefix, .. } if prefix.captures.is_empty() => Ok(
70                Pattern::Regex(RegexMatcher::new_nocapture(prefix.pattern, config)?),
71            ),
72            PatternPrefix::Regex { prefix, .. } => {
73                Ok(Pattern::Regex(RegexMatcher::new(prefix, config)?))
74            }
75            PatternPrefix::Dynamic { prefix, .. } => {
76                let mut builder = SmartMatcher::build(prefix.span(), config);
77                builder.lower_match(prefix.into_owned())?;
78                Ok(Self::Smart(builder.build()))
79            }
80        }
81    }
82
83    pub fn compile(mut pattern: CheckPattern<'a>, config: &Config) -> DiagResult<Self> {
84        pattern.compact();
85        match pattern {
86            CheckPattern::Literal(s) => {
87                Self::from_prefix(PatternPrefix::Literal { prefix: s, id: 0 }, config)
88            }
89            CheckPattern::Regex(s) => Ok(Pattern::Regex(RegexMatcher::new(s, config)?)),
90            CheckPattern::Match(s) => {
91                // At least one part requires expression evaluation
92                let (span, parts) = s.into_parts();
93                let mut builder = SmartMatcher::build(span, config);
94                for part in parts.into_iter() {
95                    match part {
96                        CheckPatternPart::Literal(s) => {
97                            builder.literal(s)?;
98                        }
99                        CheckPatternPart::Regex(s) => {
100                            builder.regex_pattern(s)?;
101                        }
102                        CheckPatternPart::Match(m) => {
103                            builder.lower_match(m)?;
104                        }
105                    }
106                }
107
108                let pattern = builder.build();
109
110                Ok(Pattern::Smart(pattern))
111            }
112            CheckPattern::Empty(span) => Ok(Pattern::Empty(AlwaysMatch::new(span))),
113        }
114    }
115
116    pub fn compile_static(
117        span: SourceSpan,
118        mut pattern: CheckPattern<'a>,
119        config: &Config,
120    ) -> DiagResult<SimpleMatcher<'a>> {
121        pattern.compact();
122
123        match pattern {
124            CheckPattern::Literal(lit) => Ok(SimpleMatcher::Substring(SubstringMatcher::new(
125                lit, config,
126            )?)),
127            CheckPattern::Regex(regex) if regex.captures.is_empty() => {
128                Ok(SimpleMatcher::Regex(RegexMatcher::new(regex, config)?))
129            }
130            pattern @ (CheckPattern::Regex(_) | CheckPattern::Match(_)) => {
131                let diag = Diag::new("invalid variable usage in pattern")
132                    .with_label(Label::new(span, "occurs in this pattern"))
133                    .and_labels(
134                        pattern
135                            .locate_variables()
136                            .map(|span| Label::new(span, "occurs here").into()),
137                    )
138                    .with_help("CHECK-LABEL patterns must be literals or regular expressions");
139                Err(Report::new(diag))
140            }
141            CheckPattern::Empty(_) => unreachable!(
142                "{pattern:?} is only valid for CHECK-EMPTY, and is not an actual pattern"
143            ),
144        }
145    }
146
147    pub fn compile_literal(pattern: CheckPattern<'a>, config: &Config) -> DiagResult<Self> {
148        match pattern {
149            CheckPattern::Literal(lit) => {
150                Ok(Pattern::Substring(SubstringMatcher::new(lit, config)?))
151            }
152            CheckPattern::Regex(_) | CheckPattern::Match(_) => {
153                unreachable!("the lexer will never emit tokens for these non-terminals")
154            }
155            CheckPattern::Empty(_) => unreachable!(
156                "{pattern:?} is only valid for CHECK-EMPTY, and is not an actual pattern"
157            ),
158        }
159    }
160}
161impl<'a> MatcherMut for Pattern<'a> {
162    fn try_match_mut<'input, 'context, C>(
163        &self,
164        input: Input<'input>,
165        context: &mut C,
166    ) -> DiagResult<MatchResult<'input>>
167    where
168        C: Context<'input, 'context> + ?Sized,
169    {
170        match self {
171            Self::Substring(matcher) => matcher.try_match(input, context),
172            Self::Regex(matcher) => matcher.try_match(input, context),
173            Self::Smart(matcher) => matcher.try_match_mut(input, context),
174            Self::Whitespace(matcher) => matcher.try_match(input, context),
175            Self::Empty(matcher) => matcher.try_match(input, context),
176        }
177    }
178}
179impl<'a> Spanned for Pattern<'a> {
180    fn span(&self) -> SourceSpan {
181        match self {
182            Self::Substring(matcher) => matcher.span(),
183            Self::Regex(matcher) => matcher.span(),
184            Self::Smart(matcher) => matcher.span(),
185            Self::Whitespace(matcher) => matcher.span(),
186            Self::Empty(matcher) => matcher.span(),
187        }
188    }
189}