litcheck_filecheck/pattern/
mod.rs1mod 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#[derive(Debug)]
23pub enum Pattern<'a> {
24 Empty(AlwaysMatch),
26 Substring(SubstringMatcher<'a>),
28 Regex(RegexMatcher<'a>),
30 Smart(SmartMatcher<'a>),
32 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 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}