1/*!
2Utilities for dealing with the syntax of a regular expression.
3
4This module currently only exposes a [`Config`] type that
5itself represents a wrapper around the configuration for a
6[`regex-syntax::ParserBuilder`](regex_syntax::ParserBuilder). The purpose of
7this wrapper is to make configuring syntax options very similar to how other
8configuration is done throughout this crate. Namely, instead of duplicating
9syntax options across every builder (of which there are many), we instead
10create small config objects like this one that can be passed around and
11composed.
12*/
13
14use alloc::{vec, vec::Vec};
15
16use regex_syntax::{
17 ast,
18 hir::{self, Hir},
19 Error, ParserBuilder,
20};
21
22/// A convenience routine for parsing a pattern into an HIR value with the
23/// default configuration.
24///
25/// # Example
26///
27/// This shows how to parse a pattern into an HIR value:
28///
29/// ```
30/// use regex_automata::util::syntax;
31///
32/// let hir = syntax::parse(r"([a-z]+)|([0-9]+)")?;
33/// assert_eq!(Some(1), hir.properties().static_explicit_captures_len());
34///
35/// # Ok::<(), Box<dyn std::error::Error>>(())
36/// ```
37pub fn parse(pattern: &str) -> Result<Hir, Error> {
38 parse_with(pattern, &Config::default())
39}
40
41/// A convenience routine for parsing many patterns into HIR value with the
42/// default configuration.
43///
44/// # Example
45///
46/// This shows how to parse many patterns into an corresponding HIR values:
47///
48/// ```
49/// use {
50/// regex_automata::util::syntax,
51/// regex_syntax::hir::Properties,
52/// };
53///
54/// let hirs = syntax::parse_many(&[
55/// r"([a-z]+)|([0-9]+)",
56/// r"foo(A-Z]+)bar",
57/// ])?;
58/// let props = Properties::union(hirs.iter().map(|h| h.properties()));
59/// assert_eq!(Some(1), props.static_explicit_captures_len());
60///
61/// # Ok::<(), Box<dyn std::error::Error>>(())
62/// ```
63pub fn parse_many<P: AsRef<str>>(patterns: &[P]) -> Result<Vec<Hir>, Error> {
64 parse_many_with(patterns, &Config::default())
65}
66
67/// A convenience routine for parsing a pattern into an HIR value using a
68/// `Config`.
69///
70/// # Example
71///
72/// This shows how to parse a pattern into an HIR value with a non-default
73/// configuration:
74///
75/// ```
76/// use regex_automata::util::syntax;
77///
78/// let hir = syntax::parse_with(
79/// r"^[a-z]+$",
80/// &syntax::Config::new().multi_line(true).crlf(true),
81/// )?;
82/// assert!(hir.properties().look_set().contains_anchor_crlf());
83///
84/// # Ok::<(), Box<dyn std::error::Error>>(())
85/// ```
86pub fn parse_with(pattern: &str, config: &Config) -> Result<Hir, Error> {
87 let mut builder = ParserBuilder::new();
88 config.apply(&mut builder);
89 builder.build().parse(pattern)
90}
91
92/// A convenience routine for parsing many patterns into HIR values using a
93/// `Config`.
94///
95/// # Example
96///
97/// This shows how to parse many patterns into an corresponding HIR values
98/// with a non-default configuration:
99///
100/// ```
101/// use {
102/// regex_automata::util::syntax,
103/// regex_syntax::hir::Properties,
104/// };
105///
106/// let patterns = &[
107/// r"([a-z]+)|([0-9]+)",
108/// r"\W",
109/// r"foo(A-Z]+)bar",
110/// ];
111/// let config = syntax::Config::new().unicode(false).utf8(false);
112/// let hirs = syntax::parse_many_with(patterns, &config)?;
113/// let props = Properties::union(hirs.iter().map(|h| h.properties()));
114/// assert!(!props.is_utf8());
115///
116/// # Ok::<(), Box<dyn std::error::Error>>(())
117/// ```
118pub fn parse_many_with<P: AsRef<str>>(
119 patterns: &[P],
120 config: &Config,
121) -> Result<Vec<Hir>, Error> {
122 let mut builder = ParserBuilder::new();
123 config.apply(&mut builder);
124 let mut hirs = vec![];
125 for p in patterns.iter() {
126 hirs.push(builder.build().parse(p.as_ref())?);
127 }
128 Ok(hirs)
129}
130
131/// A common set of configuration options that apply to the syntax of a regex.
132///
133/// This represents a group of configuration options that specifically apply
134/// to how the concrete syntax of a regular expression is interpreted. In
135/// particular, they are generally forwarded to the
136/// [`ParserBuilder`](https://docs.rs/regex-syntax/*/regex_syntax/struct.ParserBuilder.html)
137/// in the
138/// [`regex-syntax`](https://docs.rs/regex-syntax)
139/// crate when building a regex from its concrete syntax directly.
140///
141/// These options are defined as a group since they apply to every regex engine
142/// in this crate. Instead of re-defining them on every engine's builder, they
143/// are instead provided here as one cohesive unit.
144#[derive(Clone, Copy, Debug)]
145pub struct Config {
146 case_insensitive: bool,
147 multi_line: bool,
148 dot_matches_new_line: bool,
149 crlf: bool,
150 line_terminator: u8,
151 swap_greed: bool,
152 ignore_whitespace: bool,
153 unicode: bool,
154 utf8: bool,
155 nest_limit: u32,
156 octal: bool,
157}
158
159impl Config {
160 /// Return a new default syntax configuration.
161 pub fn new() -> Config {
162 // These defaults match the ones used in regex-syntax.
163 Config {
164 case_insensitive: false,
165 multi_line: false,
166 dot_matches_new_line: false,
167 crlf: false,
168 line_terminator: b'\n',
169 swap_greed: false,
170 ignore_whitespace: false,
171 unicode: true,
172 utf8: true,
173 nest_limit: 250,
174 octal: false,
175 }
176 }
177
178 /// Enable or disable the case insensitive flag by default.
179 ///
180 /// When Unicode mode is enabled, case insensitivity is Unicode-aware.
181 /// Specifically, it will apply the "simple" case folding rules as
182 /// specified by Unicode.
183 ///
184 /// By default this is disabled. It may alternatively be selectively
185 /// enabled in the regular expression itself via the `i` flag.
186 pub fn case_insensitive(mut self, yes: bool) -> Config {
187 self.case_insensitive = yes;
188 self
189 }
190
191 /// Enable or disable the multi-line matching flag by default.
192 ///
193 /// When this is enabled, the `^` and `$` look-around assertions will
194 /// match immediately after and immediately before a new line character,
195 /// respectively. Note that the `\A` and `\z` look-around assertions are
196 /// unaffected by this setting and always correspond to matching at the
197 /// beginning and end of the input.
198 ///
199 /// By default this is disabled. It may alternatively be selectively
200 /// enabled in the regular expression itself via the `m` flag.
201 pub fn multi_line(mut self, yes: bool) -> Config {
202 self.multi_line = yes;
203 self
204 }
205
206 /// Enable or disable the "dot matches any character" flag by default.
207 ///
208 /// When this is enabled, `.` will match any character. When it's disabled,
209 /// then `.` will match any character except for a new line character.
210 ///
211 /// Note that `.` is impacted by whether the "unicode" setting is enabled
212 /// or not. When Unicode is enabled (the default), `.` will match any UTF-8
213 /// encoding of any Unicode scalar value (sans a new line, depending on
214 /// whether this "dot matches new line" option is enabled). When Unicode
215 /// mode is disabled, `.` will match any byte instead. Because of this,
216 /// when Unicode mode is disabled, `.` can only be used when the "allow
217 /// invalid UTF-8" option is enabled, since `.` could otherwise match
218 /// invalid UTF-8.
219 ///
220 /// By default this is disabled. It may alternatively be selectively
221 /// enabled in the regular expression itself via the `s` flag.
222 pub fn dot_matches_new_line(mut self, yes: bool) -> Config {
223 self.dot_matches_new_line = yes;
224 self
225 }
226
227 /// Enable or disable the "CRLF mode" flag by default.
228 ///
229 /// By default this is disabled. It may alternatively be selectively
230 /// enabled in the regular expression itself via the `R` flag.
231 ///
232 /// When CRLF mode is enabled, the following happens:
233 ///
234 /// * Unless `dot_matches_new_line` is enabled, `.` will match any character
235 /// except for `\r` and `\n`.
236 /// * When `multi_line` mode is enabled, `^` and `$` will treat `\r\n`,
237 /// `\r` and `\n` as line terminators. And in particular, neither will
238 /// match between a `\r` and a `\n`.
239 pub fn crlf(mut self, yes: bool) -> Config {
240 self.crlf = yes;
241 self
242 }
243
244 /// Sets the line terminator for use with `(?u-s:.)` and `(?-us:.)`.
245 ///
246 /// Namely, instead of `.` (by default) matching everything except for `\n`,
247 /// this will cause `.` to match everything except for the byte given.
248 ///
249 /// If `.` is used in a context where Unicode mode is enabled and this byte
250 /// isn't ASCII, then an error will be returned. When Unicode mode is
251 /// disabled, then any byte is permitted, but will return an error if UTF-8
252 /// mode is enabled and it is a non-ASCII byte.
253 ///
254 /// In short, any ASCII value for a line terminator is always okay. But a
255 /// non-ASCII byte might result in an error depending on whether Unicode
256 /// mode or UTF-8 mode are enabled.
257 ///
258 /// Note that if `R` mode is enabled then it always takes precedence and
259 /// the line terminator will be treated as `\r` and `\n` simultaneously.
260 ///
261 /// Note also that this *doesn't* impact the look-around assertions
262 /// `(?m:^)` and `(?m:$)`. That's usually controlled by additional
263 /// configuration in the regex engine itself.
264 pub fn line_terminator(mut self, byte: u8) -> Config {
265 self.line_terminator = byte;
266 self
267 }
268
269 /// Enable or disable the "swap greed" flag by default.
270 ///
271 /// When this is enabled, `.*` (for example) will become ungreedy and `.*?`
272 /// will become greedy.
273 ///
274 /// By default this is disabled. It may alternatively be selectively
275 /// enabled in the regular expression itself via the `U` flag.
276 pub fn swap_greed(mut self, yes: bool) -> Config {
277 self.swap_greed = yes;
278 self
279 }
280
281 /// Enable verbose mode in the regular expression.
282 ///
283 /// When enabled, verbose mode permits insigificant whitespace in many
284 /// places in the regular expression, as well as comments. Comments are
285 /// started using `#` and continue until the end of the line.
286 ///
287 /// By default, this is disabled. It may be selectively enabled in the
288 /// regular expression by using the `x` flag regardless of this setting.
289 pub fn ignore_whitespace(mut self, yes: bool) -> Config {
290 self.ignore_whitespace = yes;
291 self
292 }
293
294 /// Enable or disable the Unicode flag (`u`) by default.
295 ///
296 /// By default this is **enabled**. It may alternatively be selectively
297 /// disabled in the regular expression itself via the `u` flag.
298 ///
299 /// Note that unless "allow invalid UTF-8" is enabled (it's disabled by
300 /// default), a regular expression will fail to parse if Unicode mode is
301 /// disabled and a sub-expression could possibly match invalid UTF-8.
302 ///
303 /// **WARNING**: Unicode mode can greatly increase the size of the compiled
304 /// DFA, which can noticeably impact both memory usage and compilation
305 /// time. This is especially noticeable if your regex contains character
306 /// classes like `\w` that are impacted by whether Unicode is enabled or
307 /// not. If Unicode is not necessary, you are encouraged to disable it.
308 pub fn unicode(mut self, yes: bool) -> Config {
309 self.unicode = yes;
310 self
311 }
312
313 /// When disabled, the builder will permit the construction of a regular
314 /// expression that may match invalid UTF-8.
315 ///
316 /// For example, when [`Config::unicode`] is disabled, then
317 /// expressions like `[^a]` may match invalid UTF-8 since they can match
318 /// any single byte that is not `a`. By default, these sub-expressions
319 /// are disallowed to avoid returning offsets that split a UTF-8
320 /// encoded codepoint. However, in cases where matching at arbitrary
321 /// locations is desired, this option can be disabled to permit all such
322 /// sub-expressions.
323 ///
324 /// When enabled (the default), the builder is guaranteed to produce a
325 /// regex that will only ever match valid UTF-8 (otherwise, the builder
326 /// will return an error).
327 pub fn utf8(mut self, yes: bool) -> Config {
328 self.utf8 = yes;
329 self
330 }
331
332 /// Set the nesting limit used for the regular expression parser.
333 ///
334 /// The nesting limit controls how deep the abstract syntax tree is allowed
335 /// to be. If the AST exceeds the given limit (e.g., with too many nested
336 /// groups), then an error is returned by the parser.
337 ///
338 /// The purpose of this limit is to act as a heuristic to prevent stack
339 /// overflow when building a finite automaton from a regular expression's
340 /// abstract syntax tree. In particular, construction currently uses
341 /// recursion. In the future, the implementation may stop using recursion
342 /// and this option will no longer be necessary.
343 ///
344 /// This limit is not checked until the entire AST is parsed. Therefore,
345 /// if callers want to put a limit on the amount of heap space used, then
346 /// they should impose a limit on the length, in bytes, of the concrete
347 /// pattern string. In particular, this is viable since the parser will
348 /// limit itself to heap space proportional to the length of the pattern
349 /// string.
350 ///
351 /// Note that a nest limit of `0` will return a nest limit error for most
352 /// patterns but not all. For example, a nest limit of `0` permits `a` but
353 /// not `ab`, since `ab` requires a concatenation AST item, which results
354 /// in a nest depth of `1`. In general, a nest limit is not something that
355 /// manifests in an obvious way in the concrete syntax, therefore, it
356 /// should not be used in a granular way.
357 pub fn nest_limit(mut self, limit: u32) -> Config {
358 self.nest_limit = limit;
359 self
360 }
361
362 /// Whether to support octal syntax or not.
363 ///
364 /// Octal syntax is a little-known way of uttering Unicode codepoints in
365 /// a regular expression. For example, `a`, `\x61`, `\u0061` and
366 /// `\141` are all equivalent regular expressions, where the last example
367 /// shows octal syntax.
368 ///
369 /// While supporting octal syntax isn't in and of itself a problem, it does
370 /// make good error messages harder. That is, in PCRE based regex engines,
371 /// syntax like `\1` invokes a backreference, which is explicitly
372 /// unsupported in Rust's regex engine. However, many users expect it to
373 /// be supported. Therefore, when octal support is disabled, the error
374 /// message will explicitly mention that backreferences aren't supported.
375 ///
376 /// Octal syntax is disabled by default.
377 pub fn octal(mut self, yes: bool) -> Config {
378 self.octal = yes;
379 self
380 }
381
382 /// Returns whether "unicode" mode is enabled.
383 pub fn get_unicode(&self) -> bool {
384 self.unicode
385 }
386
387 /// Returns whether "case insensitive" mode is enabled.
388 pub fn get_case_insensitive(&self) -> bool {
389 self.case_insensitive
390 }
391
392 /// Returns whether "multi line" mode is enabled.
393 pub fn get_multi_line(&self) -> bool {
394 self.multi_line
395 }
396
397 /// Returns whether "dot matches new line" mode is enabled.
398 pub fn get_dot_matches_new_line(&self) -> bool {
399 self.dot_matches_new_line
400 }
401
402 /// Returns whether "CRLF" mode is enabled.
403 pub fn get_crlf(&self) -> bool {
404 self.crlf
405 }
406
407 /// Returns the line terminator in this syntax configuration.
408 pub fn get_line_terminator(&self) -> u8 {
409 self.line_terminator
410 }
411
412 /// Returns whether "swap greed" mode is enabled.
413 pub fn get_swap_greed(&self) -> bool {
414 self.swap_greed
415 }
416
417 /// Returns whether "ignore whitespace" mode is enabled.
418 pub fn get_ignore_whitespace(&self) -> bool {
419 self.ignore_whitespace
420 }
421
422 /// Returns whether UTF-8 mode is enabled.
423 pub fn get_utf8(&self) -> bool {
424 self.utf8
425 }
426
427 /// Returns the "nest limit" setting.
428 pub fn get_nest_limit(&self) -> u32 {
429 self.nest_limit
430 }
431
432 /// Returns whether "octal" mode is enabled.
433 pub fn get_octal(&self) -> bool {
434 self.octal
435 }
436
437 /// Applies this configuration to the given parser.
438 pub(crate) fn apply(&self, builder: &mut ParserBuilder) {
439 builder
440 .unicode(self.unicode)
441 .case_insensitive(self.case_insensitive)
442 .multi_line(self.multi_line)
443 .dot_matches_new_line(self.dot_matches_new_line)
444 .crlf(self.crlf)
445 .line_terminator(self.line_terminator)
446 .swap_greed(self.swap_greed)
447 .ignore_whitespace(self.ignore_whitespace)
448 .utf8(self.utf8)
449 .nest_limit(self.nest_limit)
450 .octal(self.octal);
451 }
452
453 /// Applies this configuration to the given AST parser.
454 pub(crate) fn apply_ast(&self, builder: &mut ast::parse::ParserBuilder) {
455 builder
456 .ignore_whitespace(self.ignore_whitespace)
457 .nest_limit(self.nest_limit)
458 .octal(self.octal);
459 }
460
461 /// Applies this configuration to the given AST-to-HIR translator.
462 pub(crate) fn apply_hir(
463 &self,
464 builder: &mut hir::translate::TranslatorBuilder,
465 ) {
466 builder
467 .unicode(self.unicode)
468 .case_insensitive(self.case_insensitive)
469 .multi_line(self.multi_line)
470 .crlf(self.crlf)
471 .dot_matches_new_line(self.dot_matches_new_line)
472 .line_terminator(self.line_terminator)
473 .swap_greed(self.swap_greed)
474 .utf8(self.utf8);
475 }
476}
477
478impl Default for Config {
479 fn default() -> Config {
480 Config::new()
481 }
482}
483