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(
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 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}