fancy_regex/lib.rs
1// Copyright 2016 The Fancy Regex Authors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21/*!
22An implementation of regexes, supporting a relatively rich set of features, including backreferences
23and lookaround.
24
25It builds on top of the excellent [regex] crate. If you are not
26familiar with it, make sure you read its documentation and maybe you don't even need fancy-regex.
27
28If your regex or parts of it does not use any special features, the matching is delegated to the
29regex crate. That means it has linear runtime. But if you use "fancy" features such as
30backreferences or look-around, an engine with backtracking needs to be used. In that case, the regex
31can be slow and take exponential time to run because of what is called "catastrophic backtracking".
32This depends on the regex and the input.
33
34# Usage
35
36The API should feel very similar to the regex crate, and involves compiling a regex and then using
37it to find matches in text.
38
39## Example: Matching text
40
41An example with backreferences to check if a text consists of two identical words:
42
43```rust
44use fancy_regex::Regex;
45
46let re = Regex::new(r"^(\w+) (\1)$").unwrap();
47let result = re.is_match("foo foo");
48
49assert!(result.is_ok());
50let did_match = result.unwrap();
51assert!(did_match);
52```
53
54Note that like in the regex crate, the regex needs anchors like `^` and `$` to match against the
55entire input text.
56
57## Example: Finding the position of matches
58
59```rust
60use fancy_regex::Regex;
61
62let re = Regex::new(r"(\d)\1").unwrap();
63let result = re.find("foo 22");
64
65assert!(result.is_ok(), "execution was successful");
66let match_option = result.unwrap();
67
68assert!(match_option.is_some(), "found a match");
69let m = match_option.unwrap();
70
71assert_eq!(m.start(), 4);
72assert_eq!(m.end(), 6);
73assert_eq!(m.as_str(), "22");
74```
75
76## Example: Capturing groups
77
78```rust
79use fancy_regex::Regex;
80
81let re = Regex::new(r"(?<!AU)\$(\d+)").unwrap();
82let result = re.captures("AU$10, $20");
83
84let captures = result.expect("Error running regex").expect("No match found");
85let group = captures.get(1).expect("No group");
86assert_eq!(group.as_str(), "20");
87```
88
89# Syntax
90
91The regex syntax is based on the [regex] crate's, with some additional supported syntax.
92
93Escapes:
94
95`\h`
96: hex digit (`[0-9A-Fa-f]`) \
97`\H`
98: not hex digit (`[^0-9A-Fa-f]`) \
99`\e`
100: escape control character (`\x1B`) \
101`\K`
102: keep text matched so far out of the overall match ([docs](https://www.regular-expressions.info/keep.html))\
103`\G`
104: anchor to where the previous match ended ([docs](https://www.regular-expressions.info/continue.html))
105
106Backreferences:
107
108`\1`
109: match the exact string that the first capture group matched \
110`\2`
111: backref to the second capture group, etc
112
113Named capture groups:
114
115`(?<name>exp)`
116: match *exp*, creating capture group named *name* \
117`\k<name>`
118: match the exact string that the capture group named *name* matched \
119`(?P<name>exp)`
120: same as `(?<name>exp)` for compatibility with Python, etc. \
121`(?P=name)`
122: same as `\k<name>` for compatibility with Python, etc.
123
124Look-around assertions for matching without changing the current position:
125
126`(?=exp)`
127: look-ahead, succeeds if *exp* matches to the right of the current position \
128`(?!exp)`
129: negative look-ahead, succeeds if *exp* doesn't match to the right \
130`(?<=exp)`
131: look-behind, succeeds if *exp* matches to the left of the current position \
132`(?<!exp)`
133: negative look-behind, succeeds if *exp* doesn't match to the left
134
135Atomic groups using `(?>exp)` to prevent backtracking within `exp`, e.g.:
136
137```
138# use fancy_regex::Regex;
139let re = Regex::new(r"^a(?>bc|b)c$").unwrap();
140assert!(re.is_match("abcc").unwrap());
141// Doesn't match because `|b` is never tried because of the atomic group
142assert!(!re.is_match("abc").unwrap());
143```
144
145Conditionals - if/then/else:
146
147`(?(1))`
148: continue only if first capture group matched \
149`(?(<name>))`
150: continue only if capture group named *name* matched \
151`(?(1)true_branch|false_branch)`
152: if the first capture group matched then execute the true_branch regex expression, else execute false_branch ([docs](https://www.regular-expressions.info/conditional.html)) \
153`(?(condition)true_branch|false_branch)`
154: if the condition matches then execute the true_branch regex expression, else execute false_branch from the point just before the condition was evaluated
155
156[regex]: https://crates.io/crates/regex
157*/
158
159#![doc(html_root_url = "https://docs.rs/fancy-regex/0.11.0")]
160#![deny(missing_docs)]
161#![deny(missing_debug_implementations)]
162
163use std::fmt;
164use std::fmt::{Debug, Formatter};
165use std::ops::{Index, Range};
166use std::str::FromStr;
167use std::sync::Arc;
168use std::usize;
169
170mod analyze;
171mod compile;
172mod error;
173mod expand;
174mod parse;
175mod replacer;
176mod vm;
177
178use crate::analyze::analyze;
179use crate::compile::compile;
180use crate::parse::{ExprTree, NamedGroups, Parser};
181use crate::vm::{Prog, OPTION_SKIPPED_EMPTY_MATCH};
182
183pub use crate::error::{CompileError, Error, ParseError, Result, RuntimeError};
184pub use crate::expand::Expander;
185pub use crate::replacer::{NoExpand, Replacer, ReplacerRef};
186use std::borrow::Cow;
187
188const MAX_RECURSION: usize = 64;
189
190// the public API
191
192/// A builder for a `Regex` to allow configuring options.
193#[derive(Debug)]
194pub struct RegexBuilder(RegexOptions);
195
196/// A compiled regular expression.
197#[derive(Clone)]
198pub struct Regex {
199 inner: RegexImpl,
200 named_groups: Arc<NamedGroups>,
201}
202
203// Separate enum because we don't want to expose any of this
204#[derive(Clone)]
205enum RegexImpl {
206 // Do we want to box this? It's pretty big...
207 Wrap {
208 inner: regex::Regex,
209 options: RegexOptions,
210 },
211 Fancy {
212 prog: Prog,
213 n_groups: usize,
214 options: RegexOptions,
215 },
216}
217
218/// A single match of a regex or group in an input text
219#[derive(Copy, Clone, Debug, Eq, PartialEq)]
220pub struct Match<'t> {
221 text: &'t str,
222 start: usize,
223 end: usize,
224}
225
226/// An iterator over all non-overlapping matches for a particular string.
227///
228/// The iterator yields a `Result<Match>`. The iterator stops when no more
229/// matches can be found.
230///
231/// `'r` is the lifetime of the compiled regular expression and `'t` is the
232/// lifetime of the matched string.
233#[derive(Debug)]
234pub struct Matches<'r, 't> {
235 re: &'r Regex,
236 text: &'t str,
237 last_end: usize,
238 last_match: Option<usize>,
239}
240
241impl<'r, 't> Matches<'r, 't> {
242 /// Return the text being searched.
243 pub fn text(&self) -> &'t str {
244 self.text
245 }
246
247 /// Return the underlying regex.
248 pub fn regex(&self) -> &'r Regex {
249 &self.re
250 }
251}
252
253impl<'r, 't> Iterator for Matches<'r, 't> {
254 type Item = Result<Match<'t>>;
255
256 /// Adapted from the `regex` crate. Calls `find_from_pos` repeatedly.
257 /// Ignores empty matches immediately after a match.
258 fn next(&mut self) -> Option<Self::Item> {
259 if self.last_end > self.text.len() {
260 return None;
261 }
262
263 let option_flags = if let Some(last_match) = self.last_match {
264 if self.last_end > last_match {
265 OPTION_SKIPPED_EMPTY_MATCH
266 } else {
267 0
268 }
269 } else {
270 0
271 };
272 let mat =
273 match self
274 .re
275 .find_from_pos_with_option_flags(self.text, self.last_end, option_flags)
276 {
277 Err(error) => return Some(Err(error)),
278 Ok(None) => return None,
279 Ok(Some(mat)) => mat,
280 };
281
282 if mat.start == mat.end {
283 // This is an empty match. To ensure we make progress, start
284 // the next search at the smallest possible starting position
285 // of the next match following this one.
286 self.last_end = next_utf8(self.text, mat.end);
287 // Don't accept empty matches immediately following a match.
288 // Just move on to the next match.
289 if Some(mat.end) == self.last_match {
290 return self.next();
291 }
292 } else {
293 self.last_end = mat.end;
294 }
295
296 self.last_match = Some(mat.end);
297
298 Some(Ok(mat))
299 }
300}
301
302/// An iterator that yields all non-overlapping capture groups matching a
303/// particular regular expression.
304///
305/// The iterator stops when no more matches can be found.
306///
307/// `'r` is the lifetime of the compiled regular expression and `'t` is the
308/// lifetime of the matched string.
309#[derive(Debug)]
310pub struct CaptureMatches<'r, 't>(Matches<'r, 't>);
311
312impl<'r, 't> CaptureMatches<'r, 't> {
313 /// Return the text being searched.
314 pub fn text(&self) -> &'t str {
315 self.0.text
316 }
317
318 /// Return the underlying regex.
319 pub fn regex(&self) -> &'r Regex {
320 &self.0.re
321 }
322}
323
324impl<'r, 't> Iterator for CaptureMatches<'r, 't> {
325 type Item = Result<Captures<'t>>;
326
327 /// Adapted from the `regex` crate. Calls `captures_from_pos` repeatedly.
328 /// Ignores empty matches immediately after a match.
329 fn next(&mut self) -> Option<Self::Item> {
330 if self.0.last_end > self.0.text.len() {
331 return None;
332 }
333
334 let captures = match self.0.re.captures_from_pos(self.0.text, self.0.last_end) {
335 Err(error) => return Some(Err(error)),
336 Ok(None) => return None,
337 Ok(Some(captures)) => captures,
338 };
339
340 let mat = captures
341 .get(0)
342 .expect("`Captures` is expected to have entire match at 0th position");
343 if mat.start == mat.end {
344 self.0.last_end = next_utf8(self.0.text, mat.end);
345 if Some(mat.end) == self.0.last_match {
346 return self.next();
347 }
348 } else {
349 self.0.last_end = mat.end;
350 }
351
352 self.0.last_match = Some(mat.end);
353
354 Some(Ok(captures))
355 }
356}
357
358/// A set of capture groups found for a regex.
359#[derive(Debug)]
360pub struct Captures<'t> {
361 inner: CapturesImpl<'t>,
362 named_groups: Arc<NamedGroups>,
363}
364
365#[derive(Debug)]
366enum CapturesImpl<'t> {
367 Wrap {
368 text: &'t str,
369 locations: regex::CaptureLocations,
370 },
371 Fancy {
372 text: &'t str,
373 saves: Vec<usize>,
374 },
375}
376
377/// Iterator for captured groups in order in which they appear in the regex.
378#[derive(Debug)]
379pub struct SubCaptureMatches<'c, 't> {
380 caps: &'c Captures<'t>,
381 i: usize,
382}
383
384#[derive(Clone, Debug)]
385struct RegexOptions {
386 pattern: String,
387 backtrack_limit: usize,
388 delegate_size_limit: Option<usize>,
389 delegate_dfa_size_limit: Option<usize>,
390}
391
392impl Default for RegexOptions {
393 fn default() -> Self {
394 RegexOptions {
395 pattern: String::new(),
396 backtrack_limit: 1_000_000,
397 delegate_size_limit: None,
398 delegate_dfa_size_limit: None,
399 }
400 }
401}
402
403impl RegexBuilder {
404 /// Create a new regex builder with a regex pattern.
405 ///
406 /// If the pattern is invalid, the call to `build` will fail later.
407 pub fn new(pattern: &str) -> Self {
408 let mut builder = RegexBuilder(RegexOptions::default());
409 builder.0.pattern = pattern.to_string();
410 builder
411 }
412
413 /// Build the `Regex`.
414 ///
415 /// Returns an [`Error`](enum.Error.html) if the pattern could not be parsed.
416 pub fn build(&self) -> Result<Regex> {
417 Regex::new_options(self.0.clone())
418 }
419
420 /// Limit for how many times backtracking should be attempted for fancy regexes (where
421 /// backtracking is used). If this limit is exceeded, execution returns an error with
422 /// [`Error::BacktrackLimitExceeded`](enum.Error.html#variant.BacktrackLimitExceeded).
423 /// This is for preventing a regex with catastrophic backtracking to run for too long.
424 ///
425 /// Default is `1_000_000` (1 million).
426 pub fn backtrack_limit(&mut self, limit: usize) -> &mut Self {
427 self.0.backtrack_limit = limit;
428 self
429 }
430
431 /// Set the approximate size limit of the compiled regular expression.
432 ///
433 /// This option is forwarded from the wrapped `regex` crate. Note that depending on the used
434 /// regex features there may be multiple delegated sub-regexes fed to the `regex` crate. As
435 /// such the actual limit is closer to `<number of delegated regexes> * delegate_size_limit`.
436 pub fn delegate_size_limit(&mut self, limit: usize) -> &mut Self {
437 self.0.delegate_size_limit = Some(limit);
438 self
439 }
440
441 /// Set the approximate size of the cache used by the DFA.
442 ///
443 /// This option is forwarded from the wrapped `regex` crate. Note that depending on the used
444 /// regex features there may be multiple delegated sub-regexes fed to the `regex` crate. As
445 /// such the actual limit is closer to `<number of delegated regexes> *
446 /// delegate_dfa_size_limit`.
447 pub fn delegate_dfa_size_limit(&mut self, limit: usize) -> &mut Self {
448 self.0.delegate_dfa_size_limit = Some(limit);
449 self
450 }
451}
452
453impl fmt::Debug for Regex {
454 /// Shows the original regular expression.
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 write!(f, "{}", self.as_str())
457 }
458}
459
460impl fmt::Display for Regex {
461 /// Shows the original regular expression
462 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
463 write!(f, "{}", self.as_str())
464 }
465}
466
467impl FromStr for Regex {
468 type Err = Error;
469
470 /// Attempts to parse a string into a regular expression
471 fn from_str(s: &str) -> Result<Regex> {
472 Regex::new(s)
473 }
474}
475
476impl Regex {
477 /// Parse and compile a regex with default options, see `RegexBuilder`.
478 ///
479 /// Returns an [`Error`](enum.Error.html) if the pattern could not be parsed.
480 pub fn new(re: &str) -> Result<Regex> {
481 let options = RegexOptions {
482 pattern: re.to_string(),
483 ..RegexOptions::default()
484 };
485 Self::new_options(options)
486 }
487
488 fn new_options(options: RegexOptions) -> Result<Regex> {
489 let raw_tree = Expr::parse_tree(&options.pattern)?;
490
491 // wrapper to search for re at arbitrary start position,
492 // and to capture the match bounds
493 let tree = ExprTree {
494 expr: Expr::Concat(vec![
495 Expr::Repeat {
496 child: Box::new(Expr::Any { newline: true }),
497 lo: 0,
498 hi: usize::MAX,
499 greedy: false,
500 },
501 Expr::Group(Box::new(raw_tree.expr)),
502 ]),
503 ..raw_tree
504 };
505
506 let info = analyze(&tree)?;
507
508 let inner_info = &info.children[1].children[0]; // references inner expr
509 if !inner_info.hard {
510 // easy case, wrap regex
511
512 // we do our own to_str because escapes are different
513 let mut re_cooked = String::new();
514 // same as raw_tree.expr above, but it was moved, so traverse to find it
515 let raw_e = match tree.expr {
516 Expr::Concat(ref v) => match v[1] {
517 Expr::Group(ref child) => child,
518 _ => unreachable!(),
519 },
520 _ => unreachable!(),
521 };
522 raw_e.to_str(&mut re_cooked, 0);
523 let inner = compile::compile_inner(&re_cooked, &options)?;
524 return Ok(Regex {
525 inner: RegexImpl::Wrap { inner, options },
526 named_groups: Arc::new(tree.named_groups),
527 });
528 }
529
530 let prog = compile(&info)?;
531 Ok(Regex {
532 inner: RegexImpl::Fancy {
533 prog,
534 n_groups: info.end_group,
535 options,
536 },
537 named_groups: Arc::new(tree.named_groups),
538 })
539 }
540
541 /// Returns the original string of this regex.
542 pub fn as_str(&self) -> &str {
543 match &self.inner {
544 RegexImpl::Wrap { options, .. } => &options.pattern,
545 RegexImpl::Fancy { options, .. } => &options.pattern,
546 }
547 }
548
549 /// Check if the regex matches the input text.
550 ///
551 /// # Example
552 ///
553 /// Test if some text contains the same word twice:
554 ///
555 /// ```rust
556 /// # use fancy_regex::Regex;
557 ///
558 /// let re = Regex::new(r"(\w+) \1").unwrap();
559 /// assert!(re.is_match("mirror mirror on the wall").unwrap());
560 /// ```
561 pub fn is_match(&self, text: &str) -> Result<bool> {
562 match &self.inner {
563 RegexImpl::Wrap { ref inner, .. } => Ok(inner.is_match(text)),
564 RegexImpl::Fancy {
565 ref prog, options, ..
566 } => {
567 let result = vm::run(prog, text, 0, 0, options)?;
568 Ok(result.is_some())
569 }
570 }
571 }
572
573 /// Returns an iterator for each successive non-overlapping match in `text`.
574 ///
575 /// If you have capturing groups in your regex that you want to extract, use the [Regex::captures_iter()]
576 /// method.
577 ///
578 /// # Example
579 ///
580 /// Find all words followed by an exclamation point:
581 ///
582 /// ```rust
583 /// # use fancy_regex::Regex;
584 ///
585 /// let re = Regex::new(r"\w+(?=!)").unwrap();
586 /// let mut matches = re.find_iter("so fancy! even with! iterators!");
587 /// assert_eq!(matches.next().unwrap().unwrap().as_str(), "fancy");
588 /// assert_eq!(matches.next().unwrap().unwrap().as_str(), "with");
589 /// assert_eq!(matches.next().unwrap().unwrap().as_str(), "iterators");
590 /// assert!(matches.next().is_none());
591 /// ```
592 pub fn find_iter<'r, 't>(&'r self, text: &'t str) -> Matches<'r, 't> {
593 Matches {
594 re: &self,
595 text,
596 last_end: 0,
597 last_match: None,
598 }
599 }
600
601 /// Find the first match in the input text.
602 ///
603 /// If you have capturing groups in your regex that you want to extract, use the [Regex::captures()]
604 /// method.
605 ///
606 /// # Example
607 ///
608 /// Find a word that is followed by an exclamation point:
609 ///
610 /// ```rust
611 /// # use fancy_regex::Regex;
612 ///
613 /// let re = Regex::new(r"\w+(?=!)").unwrap();
614 /// assert_eq!(re.find("so fancy!").unwrap().unwrap().as_str(), "fancy");
615 /// ```
616 pub fn find<'t>(&self, text: &'t str) -> Result<Option<Match<'t>>> {
617 self.find_from_pos(text, 0)
618 }
619
620 /// Returns the first match in `text`, starting from the specified byte position `pos`.
621 ///
622 /// # Examples
623 ///
624 /// Finding match starting at a position:
625 ///
626 /// ```
627 /// # use fancy_regex::Regex;
628 /// let re = Regex::new(r"(?m:^)(\d+)").unwrap();
629 /// let text = "1 test 123\n2 foo";
630 /// let mat = re.find_from_pos(text, 7).unwrap().unwrap();
631 ///
632 /// assert_eq!(mat.start(), 11);
633 /// assert_eq!(mat.end(), 12);
634 /// ```
635 ///
636 /// Note that in some cases this is not the same as using the `find`
637 /// method and passing a slice of the string, see [Regex::captures_from_pos()] for details.
638 pub fn find_from_pos<'t>(&self, text: &'t str, pos: usize) -> Result<Option<Match<'t>>> {
639 self.find_from_pos_with_option_flags(text, pos, 0)
640 }
641
642 fn find_from_pos_with_option_flags<'t>(
643 &self,
644 text: &'t str,
645 pos: usize,
646 option_flags: u32,
647 ) -> Result<Option<Match<'t>>> {
648 match &self.inner {
649 RegexImpl::Wrap { inner, .. } => Ok(inner
650 .find_at(text, pos)
651 .map(|m| Match::new(text, m.start(), m.end()))),
652 RegexImpl::Fancy { prog, options, .. } => {
653 let result = vm::run(prog, text, pos, option_flags, options)?;
654 Ok(result.map(|saves| Match::new(text, saves[0], saves[1])))
655 }
656 }
657 }
658
659 /// Returns an iterator over all the non-overlapping capture groups matched in `text`.
660 ///
661 /// # Examples
662 ///
663 /// Finding all matches and capturing parts of each:
664 ///
665 /// ```rust
666 /// # use fancy_regex::Regex;
667 ///
668 /// let re = Regex::new(r"(\d{4})-(\d{2})").unwrap();
669 /// let text = "It was between 2018-04 and 2020-01";
670 /// let mut all_captures = re.captures_iter(text);
671 ///
672 /// let first = all_captures.next().unwrap().unwrap();
673 /// assert_eq!(first.get(1).unwrap().as_str(), "2018");
674 /// assert_eq!(first.get(2).unwrap().as_str(), "04");
675 /// assert_eq!(first.get(0).unwrap().as_str(), "2018-04");
676 ///
677 /// let second = all_captures.next().unwrap().unwrap();
678 /// assert_eq!(second.get(1).unwrap().as_str(), "2020");
679 /// assert_eq!(second.get(2).unwrap().as_str(), "01");
680 /// assert_eq!(second.get(0).unwrap().as_str(), "2020-01");
681 ///
682 /// assert!(all_captures.next().is_none());
683 /// ```
684 pub fn captures_iter<'r, 't>(&'r self, text: &'t str) -> CaptureMatches<'r, 't> {
685 CaptureMatches(self.find_iter(text))
686 }
687
688 /// Returns the capture groups for the first match in `text`.
689 ///
690 /// If no match is found, then `Ok(None)` is returned.
691 ///
692 /// # Examples
693 ///
694 /// Finding matches and capturing parts of the match:
695 ///
696 /// ```rust
697 /// # use fancy_regex::Regex;
698 ///
699 /// let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();
700 /// let text = "The date was 2018-04-07";
701 /// let captures = re.captures(text).unwrap().unwrap();
702 ///
703 /// assert_eq!(captures.get(1).unwrap().as_str(), "2018");
704 /// assert_eq!(captures.get(2).unwrap().as_str(), "04");
705 /// assert_eq!(captures.get(3).unwrap().as_str(), "07");
706 /// assert_eq!(captures.get(0).unwrap().as_str(), "2018-04-07");
707 /// ```
708 pub fn captures<'t>(&self, text: &'t str) -> Result<Option<Captures<'t>>> {
709 self.captures_from_pos(text, 0)
710 }
711
712 /// Returns the capture groups for the first match in `text`, starting from
713 /// the specified byte position `pos`.
714 ///
715 /// # Examples
716 ///
717 /// Finding captures starting at a position:
718 ///
719 /// ```
720 /// # use fancy_regex::Regex;
721 /// let re = Regex::new(r"(?m:^)(\d+)").unwrap();
722 /// let text = "1 test 123\n2 foo";
723 /// let captures = re.captures_from_pos(text, 7).unwrap().unwrap();
724 ///
725 /// let group = captures.get(1).unwrap();
726 /// assert_eq!(group.as_str(), "2");
727 /// assert_eq!(group.start(), 11);
728 /// assert_eq!(group.end(), 12);
729 /// ```
730 ///
731 /// Note that in some cases this is not the same as using the `captures`
732 /// method and passing a slice of the string, see the capture that we get
733 /// when we do this:
734 ///
735 /// ```
736 /// # use fancy_regex::Regex;
737 /// let re = Regex::new(r"(?m:^)(\d+)").unwrap();
738 /// let text = "1 test 123\n2 foo";
739 /// let captures = re.captures(&text[7..]).unwrap().unwrap();
740 /// assert_eq!(captures.get(1).unwrap().as_str(), "123");
741 /// ```
742 ///
743 /// This matched the number "123" because it's at the beginning of the text
744 /// of the string slice.
745 ///
746 pub fn captures_from_pos<'t>(&self, text: &'t str, pos: usize) -> Result<Option<Captures<'t>>> {
747 let named_groups = self.named_groups.clone();
748 match &self.inner {
749 RegexImpl::Wrap { inner, .. } => {
750 let mut locations = inner.capture_locations();
751 let result = inner.captures_read_at(&mut locations, text, pos);
752 Ok(result.map(|_| Captures {
753 inner: CapturesImpl::Wrap { text, locations },
754 named_groups,
755 }))
756 }
757 RegexImpl::Fancy {
758 prog,
759 n_groups,
760 options,
761 ..
762 } => {
763 let result = vm::run(prog, text, pos, 0, options)?;
764 Ok(result.map(|mut saves| {
765 saves.truncate(n_groups * 2);
766 Captures {
767 inner: CapturesImpl::Fancy { text, saves },
768 named_groups,
769 }
770 }))
771 }
772 }
773 }
774
775 /// Returns the number of captures, including the implicit capture of the entire expression.
776 pub fn captures_len(&self) -> usize {
777 match &self.inner {
778 RegexImpl::Wrap { inner, .. } => inner.captures_len(),
779 RegexImpl::Fancy { n_groups, .. } => *n_groups,
780 }
781 }
782
783 /// Returns an iterator over the capture names.
784 pub fn capture_names(&self) -> CaptureNames {
785 let mut names = Vec::new();
786 names.resize(self.captures_len(), None);
787 for (name, &i) in self.named_groups.iter() {
788 names[i] = Some(name.as_str());
789 }
790 CaptureNames(names.into_iter())
791 }
792
793 // for debugging only
794 #[doc(hidden)]
795 pub fn debug_print(&self) {
796 match &self.inner {
797 RegexImpl::Wrap { inner, .. } => println!("wrapped {:?}", inner),
798 RegexImpl::Fancy { prog, .. } => prog.debug_print(),
799 }
800 }
801
802 /// Replaces the leftmost-first match with the replacement provided.
803 /// The replacement can be a regular string (where `$N` and `$name` are
804 /// expanded to match capture groups) or a function that takes the matches'
805 /// `Captures` and returns the replaced string.
806 ///
807 /// If no match is found, then a copy of the string is returned unchanged.
808 ///
809 /// # Replacement string syntax
810 ///
811 /// All instances of `$name` in the replacement text is replaced with the
812 /// corresponding capture group `name`.
813 ///
814 /// `name` may be an integer corresponding to the index of the
815 /// capture group (counted by order of opening parenthesis where `0` is the
816 /// entire match) or it can be a name (consisting of letters, digits or
817 /// underscores) corresponding to a named capture group.
818 ///
819 /// If `name` isn't a valid capture group (whether the name doesn't exist
820 /// or isn't a valid index), then it is replaced with the empty string.
821 ///
822 /// The longest possible name is used. e.g., `$1a` looks up the capture
823 /// group named `1a` and not the capture group at index `1`. To exert more
824 /// precise control over the name, use braces, e.g., `${1}a`.
825 ///
826 /// To write a literal `$` use `$$`.
827 ///
828 /// # Examples
829 ///
830 /// Note that this function is polymorphic with respect to the replacement.
831 /// In typical usage, this can just be a normal string:
832 ///
833 /// ```rust
834 /// # use fancy_regex::Regex;
835 /// let re = Regex::new("[^01]+").unwrap();
836 /// assert_eq!(re.replace("1078910", ""), "1010");
837 /// ```
838 ///
839 /// But anything satisfying the `Replacer` trait will work. For example,
840 /// a closure of type `|&Captures| -> String` provides direct access to the
841 /// captures corresponding to a match. This allows one to access
842 /// capturing group matches easily:
843 ///
844 /// ```rust
845 /// # use fancy_regex::{Regex, Captures};
846 /// let re = Regex::new(r"([^,\s]+),\s+(\S+)").unwrap();
847 /// let result = re.replace("Springsteen, Bruce", |caps: &Captures| {
848 /// format!("{} {}", &caps[2], &caps[1])
849 /// });
850 /// assert_eq!(result, "Bruce Springsteen");
851 /// ```
852 ///
853 /// But this is a bit cumbersome to use all the time. Instead, a simple
854 /// syntax is supported that expands `$name` into the corresponding capture
855 /// group. Here's the last example, but using this expansion technique
856 /// with named capture groups:
857 ///
858 /// ```rust
859 /// # use fancy_regex::Regex;
860 /// let re = Regex::new(r"(?P<last>[^,\s]+),\s+(?P<first>\S+)").unwrap();
861 /// let result = re.replace("Springsteen, Bruce", "$first $last");
862 /// assert_eq!(result, "Bruce Springsteen");
863 /// ```
864 ///
865 /// Note that using `$2` instead of `$first` or `$1` instead of `$last`
866 /// would produce the same result. To write a literal `$` use `$$`.
867 ///
868 /// Sometimes the replacement string requires use of curly braces to
869 /// delineate a capture group replacement and surrounding literal text.
870 /// For example, if we wanted to join two words together with an
871 /// underscore:
872 ///
873 /// ```rust
874 /// # use fancy_regex::Regex;
875 /// let re = Regex::new(r"(?P<first>\w+)\s+(?P<second>\w+)").unwrap();
876 /// let result = re.replace("deep fried", "${first}_$second");
877 /// assert_eq!(result, "deep_fried");
878 /// ```
879 ///
880 /// Without the curly braces, the capture group name `first_` would be
881 /// used, and since it doesn't exist, it would be replaced with the empty
882 /// string.
883 ///
884 /// Finally, sometimes you just want to replace a literal string with no
885 /// regard for capturing group expansion. This can be done by wrapping a
886 /// byte string with `NoExpand`:
887 ///
888 /// ```rust
889 /// # use fancy_regex::Regex;
890 /// use fancy_regex::NoExpand;
891 ///
892 /// let re = Regex::new(r"(?P<last>[^,\s]+),\s+(\S+)").unwrap();
893 /// let result = re.replace("Springsteen, Bruce", NoExpand("$2 $last"));
894 /// assert_eq!(result, "$2 $last");
895 /// ```
896 pub fn replace<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str> {
897 self.replacen(text, 1, rep)
898 }
899
900 /// Replaces all non-overlapping matches in `text` with the replacement
901 /// provided. This is the same as calling `replacen` with `limit` set to
902 /// `0`.
903 ///
904 /// See the documentation for `replace` for details on how to access
905 /// capturing group matches in the replacement string.
906 pub fn replace_all<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str> {
907 self.replacen(text, 0, rep)
908 }
909
910 /// Replaces at most `limit` non-overlapping matches in `text` with the
911 /// replacement provided. If `limit` is 0, then all non-overlapping matches
912 /// are replaced.
913 ///
914 /// See the documentation for `replace` for details on how to access
915 /// capturing group matches in the replacement string.
916 pub fn replacen<'t, R: Replacer>(
917 &self,
918 text: &'t str,
919 limit: usize,
920 mut rep: R,
921 ) -> Cow<'t, str> {
922 // If we know that the replacement doesn't have any capture expansions,
923 // then we can fast path. The fast path can make a tremendous
924 // difference:
925 //
926 // 1) We use `find_iter` instead of `captures_iter`. Not asking for
927 // captures generally makes the regex engines faster.
928 // 2) We don't need to look up all of the capture groups and do
929 // replacements inside the replacement string. We just push it
930 // at each match and be done with it.
931 if let Some(rep) = rep.no_expansion() {
932 let mut it = self.find_iter(text).enumerate().peekable();
933 if it.peek().is_none() {
934 return Cow::Borrowed(text);
935 }
936 let mut new = String::with_capacity(text.len());
937 let mut last_match = 0;
938 for (i, m) in it {
939 let m = m.unwrap();
940 if limit > 0 && i >= limit {
941 break;
942 }
943 new.push_str(&text[last_match..m.start()]);
944 new.push_str(&rep);
945 last_match = m.end();
946 }
947 new.push_str(&text[last_match..]);
948 return Cow::Owned(new);
949 }
950
951 // The slower path, which we use if the replacement needs access to
952 // capture groups.
953 let mut it = self.captures_iter(text).enumerate().peekable();
954 if it.peek().is_none() {
955 return Cow::Borrowed(text);
956 }
957 let mut new = String::with_capacity(text.len());
958 let mut last_match = 0;
959 for (i, cap) in it {
960 let cap = cap.unwrap();
961 if limit > 0 && i >= limit {
962 break;
963 }
964 // unwrap on 0 is OK because captures only reports matches
965 let m = cap.get(0).unwrap();
966 new.push_str(&text[last_match..m.start()]);
967 rep.replace_append(&cap, &mut new);
968 last_match = m.end();
969 }
970 new.push_str(&text[last_match..]);
971 Cow::Owned(new)
972 }
973}
974
975impl<'t> Match<'t> {
976 /// Returns the starting byte offset of the match in the text.
977 #[inline]
978 pub fn start(&self) -> usize {
979 self.start
980 }
981
982 /// Returns the ending byte offset of the match in the text.
983 #[inline]
984 pub fn end(&self) -> usize {
985 self.end
986 }
987
988 /// Returns the range over the starting and ending byte offsets of the match in text.
989 #[inline]
990 pub fn range(&self) -> Range<usize> {
991 self.start..self.end
992 }
993
994 /// Returns the matched text.
995 #[inline]
996 pub fn as_str(&self) -> &'t str {
997 &self.text[self.start..self.end]
998 }
999
1000 /// Creates a new match from the given text and byte offsets.
1001 fn new(text: &'t str, start: usize, end: usize) -> Match<'t> {
1002 Match { text, start, end }
1003 }
1004}
1005
1006impl<'t> From<Match<'t>> for &'t str {
1007 fn from(m: Match<'t>) -> &'t str {
1008 m.as_str()
1009 }
1010}
1011
1012impl<'t> From<Match<'t>> for Range<usize> {
1013 fn from(m: Match<'t>) -> Range<usize> {
1014 m.range()
1015 }
1016}
1017
1018#[allow(clippy::len_without_is_empty)] // follow regex's API
1019impl<'t> Captures<'t> {
1020 /// Get the capture group by its index in the regex.
1021 ///
1022 /// If there is no match for that group or the index does not correspond to a group, `None` is
1023 /// returned. The index 0 returns the whole match.
1024 pub fn get(&self, i: usize) -> Option<Match<'t>> {
1025 match &self.inner {
1026 CapturesImpl::Wrap { text, locations } => {
1027 locations
1028 .get(i)
1029 .map(|(start, end)| Match { text, start, end })
1030 }
1031 CapturesImpl::Fancy { text, ref saves } => {
1032 let slot = i * 2;
1033 if slot >= saves.len() {
1034 return None;
1035 }
1036 let lo = saves[slot];
1037 if lo == std::usize::MAX {
1038 return None;
1039 }
1040 let hi = saves[slot + 1];
1041 Some(Match {
1042 text,
1043 start: lo,
1044 end: hi,
1045 })
1046 }
1047 }
1048 }
1049
1050 /// Returns the match for a named capture group. Returns `None` the capture
1051 /// group did not match or if there is no group with the given name.
1052 pub fn name(&self, name: &str) -> Option<Match<'t>> {
1053 self.named_groups.get(name).and_then(|i| self.get(*i))
1054 }
1055
1056 /// Expands all instances of `$group` in `replacement` to the corresponding
1057 /// capture group `name`, and writes them to the `dst` buffer given.
1058 ///
1059 /// `group` may be an integer corresponding to the index of the
1060 /// capture group (counted by order of opening parenthesis where `\0` is the
1061 /// entire match) or it can be a name (consisting of letters, digits or
1062 /// underscores) corresponding to a named capture group.
1063 ///
1064 /// If `group` isn't a valid capture group (whether the name doesn't exist
1065 /// or isn't a valid index), then it is replaced with the empty string.
1066 ///
1067 /// The longest possible name is used. e.g., `$1a` looks up the capture
1068 /// group named `1a` and not the capture group at index `1`. To exert more
1069 /// precise control over the name, use braces, e.g., `${1}a`.
1070 ///
1071 /// To write a literal `$`, use `$$`.
1072 ///
1073 /// For more control over expansion, see [`Expander`].
1074 ///
1075 /// [`Expander`]: expand/struct.Expander.html
1076 pub fn expand(&self, replacement: &str, dst: &mut String) {
1077 Expander::default().append_expansion(dst, replacement, self);
1078 }
1079
1080 /// Iterate over the captured groups in order in which they appeared in the regex. The first
1081 /// capture corresponds to the whole match.
1082 pub fn iter<'c>(&'c self) -> SubCaptureMatches<'c, 't> {
1083 SubCaptureMatches { caps: self, i: 0 }
1084 }
1085
1086 /// How many groups were captured. This is always at least 1 because group 0 returns the whole
1087 /// match.
1088 pub fn len(&self) -> usize {
1089 match &self.inner {
1090 CapturesImpl::Wrap { locations, .. } => locations.len(),
1091 CapturesImpl::Fancy { saves, .. } => saves.len() / 2,
1092 }
1093 }
1094}
1095
1096/// Copied from [`regex::Captures`]...
1097///
1098/// Get a group by index.
1099///
1100/// `'t` is the lifetime of the matched text.
1101///
1102/// The text can't outlive the `Captures` object if this method is
1103/// used, because of how `Index` is defined (normally `a[i]` is part
1104/// of `a` and can't outlive it); to do that, use `get()` instead.
1105///
1106/// # Panics
1107///
1108/// If there is no group at the given index.
1109impl<'t> Index<usize> for Captures<'t> {
1110 type Output = str;
1111
1112 fn index(&self, i: usize) -> &str {
1113 self.get(i)
1114 .map(|m| m.as_str())
1115 .unwrap_or_else(|| panic!("no group at index '{}'", i))
1116 }
1117}
1118
1119/// Copied from [`regex::Captures`]...
1120///
1121/// Get a group by name.
1122///
1123/// `'t` is the lifetime of the matched text and `'i` is the lifetime
1124/// of the group name (the index).
1125///
1126/// The text can't outlive the `Captures` object if this method is
1127/// used, because of how `Index` is defined (normally `a[i]` is part
1128/// of `a` and can't outlive it); to do that, use `name` instead.
1129///
1130/// # Panics
1131///
1132/// If there is no group named by the given value.
1133impl<'t, 'i> Index<&'i str> for Captures<'t> {
1134 type Output = str;
1135
1136 fn index<'a>(&'a self, name: &'i str) -> &'a str {
1137 self.name(name)
1138 .map(|m| m.as_str())
1139 .unwrap_or_else(|| panic!("no group named '{}'", name))
1140 }
1141}
1142
1143impl<'c, 't> Iterator for SubCaptureMatches<'c, 't> {
1144 type Item = Option<Match<'t>>;
1145
1146 fn next(&mut self) -> Option<Option<Match<'t>>> {
1147 if self.i < self.caps.len() {
1148 let result = self.caps.get(self.i);
1149 self.i += 1;
1150 Some(result)
1151 } else {
1152 None
1153 }
1154 }
1155}
1156
1157// TODO: might be nice to implement ExactSizeIterator etc for SubCaptures
1158
1159/// Regular expression AST. This is public for now but may change.
1160#[derive(Debug, PartialEq, Eq)]
1161pub enum Expr {
1162 /// An empty expression, e.g. the last branch in `(a|b|)`
1163 Empty,
1164 /// Any character, regex `.`
1165 Any {
1166 /// Whether it also matches newlines or not
1167 newline: bool,
1168 },
1169 /// Start of input text
1170 StartText,
1171 /// End of input text
1172 EndText,
1173 /// Start of a line
1174 StartLine,
1175 /// End of a line
1176 EndLine,
1177 /// The string as a literal, e.g. `a`
1178 Literal {
1179 /// The string to match
1180 val: String,
1181 /// Whether match is case-insensitive or not
1182 casei: bool,
1183 },
1184 /// Concatenation of multiple expressions, must match in order, e.g. `a.` is a concatenation of
1185 /// the literal `a` and `.` for any character
1186 Concat(Vec<Expr>),
1187 /// Alternative of multiple expressions, one of them must match, e.g. `a|b` is an alternative
1188 /// where either the literal `a` or `b` must match
1189 Alt(Vec<Expr>),
1190 /// Capturing group of expression, e.g. `(a.)` matches `a` and any character and "captures"
1191 /// (remembers) the match
1192 Group(Box<Expr>),
1193 /// Look-around (e.g. positive/negative look-ahead or look-behind) with an expression, e.g.
1194 /// `(?=a)` means the next character must be `a` (but the match is not consumed)
1195 LookAround(Box<Expr>, LookAround),
1196 /// Repeat of an expression, e.g. `a*` or `a+` or `a{1,3}`
1197 Repeat {
1198 /// The expression that is being repeated
1199 child: Box<Expr>,
1200 /// The minimum number of repetitions
1201 lo: usize,
1202 /// The maximum number of repetitions (or `usize::MAX`)
1203 hi: usize,
1204 /// Greedy means as much as possible is matched, e.g. `.*b` would match all of `abab`.
1205 /// Non-greedy means as little as possible, e.g. `.*?b` would match only `ab` in `abab`.
1206 greedy: bool,
1207 },
1208 /// Delegate a regex to the regex crate. This is used as a simplification so that we don't have
1209 /// to represent all the expressions in the AST, e.g. character classes.
1210 Delegate {
1211 /// The regex
1212 inner: String,
1213 /// How many characters the regex matches
1214 size: usize, // TODO: move into analysis result
1215 /// Whether the matching is case-insensitive or not
1216 casei: bool,
1217 },
1218 /// Back reference to a capture group, e.g. `\1` in `(abc|def)\1` references the captured group
1219 /// and the whole regex matches either `abcabc` or `defdef`.
1220 Backref(usize),
1221 /// Atomic non-capturing group, e.g. `(?>ab|a)` in text that contains `ab` will match `ab` and
1222 /// never backtrack and try `a`, even if matching fails after the atomic group.
1223 AtomicGroup(Box<Expr>),
1224 /// Keep matched text so far out of overall match
1225 KeepOut,
1226 /// Anchor to match at the position where the previous match ended
1227 ContinueFromPreviousMatchEnd,
1228 /// Conditional expression based on whether the numbered capture group matched or not
1229 BackrefExistsCondition(usize),
1230 /// If/Then/Else Condition. If there is no Then/Else, these will just be empty expressions.
1231 Conditional {
1232 /// The conditional expression to evaluate
1233 condition: Box<Expr>,
1234 /// What to execute if the condition is true
1235 true_branch: Box<Expr>,
1236 /// What to execute if the condition is false
1237 false_branch: Box<Expr>,
1238 },
1239}
1240
1241/// Type of look-around assertion as used for a look-around expression.
1242#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1243pub enum LookAround {
1244 /// Look-ahead assertion, e.g. `(?=a)`
1245 LookAhead,
1246 /// Negative look-ahead assertion, e.g. `(?!a)`
1247 LookAheadNeg,
1248 /// Look-behind assertion, e.g. `(?<=a)`
1249 LookBehind,
1250 /// Negative look-behind assertion, e.g. `(?<!a)`
1251 LookBehindNeg,
1252}
1253
1254/// An iterator over capture names in a [Regex]. The iterator
1255/// returns the name of each group, or [None] if the group has
1256/// no name. Because capture group 0 cannot have a name, the
1257/// first item returned is always [None].
1258pub struct CaptureNames<'r>(std::vec::IntoIter<Option<&'r str>>);
1259
1260impl Debug for CaptureNames<'_> {
1261 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1262 f.write_str("<CaptureNames>")
1263 }
1264}
1265
1266impl<'r> Iterator for CaptureNames<'r> {
1267 type Item = Option<&'r str>;
1268
1269 fn next(&mut self) -> Option<Self::Item> {
1270 self.0.next()
1271 }
1272}
1273
1274// silly to write my own, but this is super-fast for the common 1-digit
1275// case.
1276fn push_usize(s: &mut String, x: usize) {
1277 if x >= 10 {
1278 push_usize(s, x / 10);
1279 s.push((b'0' + (x % 10) as u8) as char);
1280 } else {
1281 s.push((b'0' + (x as u8)) as char);
1282 }
1283}
1284
1285fn is_special(c: char) -> bool {
1286 match c {
1287 '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$'
1288 | '#' => true,
1289 _ => false,
1290 }
1291}
1292
1293fn push_quoted(buf: &mut String, s: &str) {
1294 for c in s.chars() {
1295 if is_special(c) {
1296 buf.push('\\');
1297 }
1298 buf.push(c);
1299 }
1300}
1301
1302/// Escapes special characters in `text` with '\\'. Returns a string which, when interpreted
1303/// as a regex, matches exactly `text`.
1304pub fn escape(text: &str) -> Cow<str> {
1305 // Using bytes() is OK because all special characters are single bytes.
1306 match text.bytes().filter(|&b| is_special(b as char)).count() {
1307 0 => Cow::Borrowed(text),
1308 n => {
1309 // The capacity calculation is exact because '\\' is a single byte.
1310 let mut buf = String::with_capacity(text.len() + n);
1311 push_quoted(&mut buf, text);
1312 Cow::Owned(buf)
1313 }
1314 }
1315}
1316
1317impl Expr {
1318 /// Parse the regex and return an expression (AST) and a bit set with the indexes of groups
1319 /// that are referenced by backrefs.
1320 pub fn parse_tree(re: &str) -> Result<ExprTree> {
1321 Parser::parse(re)
1322 }
1323
1324 /// Convert expression to a regex string in the regex crate's syntax.
1325 ///
1326 /// # Panics
1327 ///
1328 /// Panics for expressions that are hard, i.e. can not be handled by the regex crate.
1329 pub fn to_str(&self, buf: &mut String, precedence: u8) {
1330 match *self {
1331 Expr::Empty => (),
1332 Expr::Any { newline } => buf.push_str(if newline { "(?s:.)" } else { "." }),
1333 Expr::Literal { ref val, casei } => {
1334 if casei {
1335 buf.push_str("(?i:");
1336 }
1337 push_quoted(buf, val);
1338 if casei {
1339 buf.push_str(")");
1340 }
1341 }
1342 Expr::StartText => buf.push('^'),
1343 Expr::EndText => buf.push('$'),
1344 Expr::StartLine => buf.push_str("(?m:^)"),
1345 Expr::EndLine => buf.push_str("(?m:$)"),
1346 Expr::Concat(ref children) => {
1347 if precedence > 1 {
1348 buf.push_str("(?:");
1349 }
1350 for child in children {
1351 child.to_str(buf, 2);
1352 }
1353 if precedence > 1 {
1354 buf.push(')')
1355 }
1356 }
1357 Expr::Alt(ref children) => {
1358 if precedence > 0 {
1359 buf.push_str("(?:");
1360 }
1361 for (i, child) in children.iter().enumerate() {
1362 if i != 0 {
1363 buf.push('|');
1364 }
1365 child.to_str(buf, 1);
1366 }
1367 if precedence > 0 {
1368 buf.push(')');
1369 }
1370 }
1371 Expr::Group(ref child) => {
1372 buf.push('(');
1373 child.to_str(buf, 0);
1374 buf.push(')');
1375 }
1376 Expr::Repeat {
1377 ref child,
1378 lo,
1379 hi,
1380 greedy,
1381 } => {
1382 if precedence > 2 {
1383 buf.push_str("(?:");
1384 }
1385 child.to_str(buf, 3);
1386 match (lo, hi) {
1387 (0, 1) => buf.push('?'),
1388 (0, usize::MAX) => buf.push('*'),
1389 (1, usize::MAX) => buf.push('+'),
1390 (lo, hi) => {
1391 buf.push('{');
1392 push_usize(buf, lo);
1393 if lo != hi {
1394 buf.push(',');
1395 if hi != usize::MAX {
1396 push_usize(buf, hi);
1397 }
1398 }
1399 buf.push('}');
1400 }
1401 }
1402 if !greedy {
1403 buf.push('?');
1404 }
1405 if precedence > 2 {
1406 buf.push(')');
1407 }
1408 }
1409 Expr::Delegate {
1410 ref inner, casei, ..
1411 } => {
1412 // at the moment, delegate nodes are just atoms
1413 if casei {
1414 buf.push_str("(?i:");
1415 }
1416 buf.push_str(inner);
1417 if casei {
1418 buf.push_str(")");
1419 }
1420 }
1421 _ => panic!("attempting to format hard expr"),
1422 }
1423 }
1424}
1425
1426// precondition: ix > 0
1427fn prev_codepoint_ix(s: &str, mut ix: usize) -> usize {
1428 let bytes = s.as_bytes();
1429 loop {
1430 ix -= 1;
1431 // fancy bit magic for ranges 0..0x80 + 0xc0..
1432 if (bytes[ix] as i8) >= -0x40 {
1433 break;
1434 }
1435 }
1436 ix
1437}
1438
1439fn codepoint_len(b: u8) -> usize {
1440 match b {
1441 b if b < 0x80 => 1,
1442 b if b < 0xe0 => 2,
1443 b if b < 0xf0 => 3,
1444 _ => 4,
1445 }
1446}
1447
1448/// Returns the smallest possible index of the next valid UTF-8 sequence
1449/// starting after `i`.
1450/// Adapted from a function with the same name in the `regex` crate.
1451fn next_utf8(text: &str, i: usize) -> usize {
1452 let b = match text.as_bytes().get(i) {
1453 None => return i + 1,
1454 Some(&b) => b,
1455 };
1456 i + codepoint_len(b)
1457}
1458
1459// If this returns false, then there is no possible backref in the re
1460
1461// Both potential implementations are turned off, because we currently
1462// always need to do a deeper analysis because of 1-character
1463// look-behind. If we could call a find_from_pos method of regex::Regex,
1464// it would make sense to bring this back.
1465/*
1466pub fn detect_possible_backref(re: &str) -> bool {
1467 let mut last = b'\x00';
1468 for b in re.as_bytes() {
1469 if b'0' <= *b && *b <= b'9' && last == b'\\' { return true; }
1470 last = *b;
1471 }
1472 false
1473}
1474
1475pub fn detect_possible_backref(re: &str) -> bool {
1476 let mut bytes = re.as_bytes();
1477 loop {
1478 match memchr::memchr(b'\\', &bytes[..bytes.len() - 1]) {
1479 Some(i) => {
1480 bytes = &bytes[i + 1..];
1481 let c = bytes[0];
1482 if b'0' <= c && c <= b'9' { return true; }
1483 }
1484 None => return false
1485 }
1486 }
1487}
1488*/
1489
1490/// The internal module only exists so that the toy example can access internals for debugging and
1491/// experimenting.
1492#[doc(hidden)]
1493pub mod internal {
1494 pub use crate::analyze::analyze;
1495 pub use crate::compile::compile;
1496 pub use crate::vm::{run_default, run_trace, Insn, Prog};
1497}
1498
1499#[cfg(test)]
1500mod tests {
1501 use crate::parse::make_literal;
1502 use crate::Expr;
1503 use crate::Regex;
1504 use std::borrow::Cow;
1505 use std::usize;
1506 //use detect_possible_backref;
1507
1508 // tests for to_str
1509
1510 fn to_str(e: Expr) -> String {
1511 let mut s = String::new();
1512 e.to_str(&mut s, 0);
1513 s
1514 }
1515
1516 #[test]
1517 fn to_str_concat_alt() {
1518 let e = Expr::Concat(vec![
1519 Expr::Alt(vec![make_literal("a"), make_literal("b")]),
1520 make_literal("c"),
1521 ]);
1522 assert_eq!(to_str(e), "(?:a|b)c");
1523 }
1524
1525 #[test]
1526 fn to_str_rep_concat() {
1527 let e = Expr::Repeat {
1528 child: Box::new(Expr::Concat(vec![make_literal("a"), make_literal("b")])),
1529 lo: 2,
1530 hi: 3,
1531 greedy: true,
1532 };
1533 assert_eq!(to_str(e), "(?:ab){2,3}");
1534 }
1535
1536 #[test]
1537 fn to_str_group_alt() {
1538 let e = Expr::Group(Box::new(Expr::Alt(vec![
1539 make_literal("a"),
1540 make_literal("b"),
1541 ])));
1542 assert_eq!(to_str(e), "(a|b)");
1543 }
1544
1545 #[test]
1546 fn as_str_debug() {
1547 let s = r"(a+)b\1";
1548 let regex = Regex::new(s).unwrap();
1549 assert_eq!(s, regex.as_str());
1550 assert_eq!(s, format!("{:?}", regex));
1551 }
1552
1553 #[test]
1554 fn display() {
1555 let s = r"(a+)b\1";
1556 let regex = Regex::new(s).unwrap();
1557 assert_eq!(s, format!("{}", regex));
1558 }
1559
1560 #[test]
1561 fn from_str() {
1562 let s = r"(a+)b\1";
1563 let regex = s.parse::<Regex>().unwrap();
1564 assert_eq!(regex.as_str(), s);
1565 }
1566
1567 #[test]
1568 fn to_str_repeat() {
1569 fn repeat(lo: usize, hi: usize, greedy: bool) -> Expr {
1570 Expr::Repeat {
1571 child: Box::new(make_literal("a")),
1572 lo,
1573 hi,
1574 greedy,
1575 }
1576 }
1577
1578 assert_eq!(to_str(repeat(2, 2, true)), "a{2}");
1579 assert_eq!(to_str(repeat(2, 2, false)), "a{2}?");
1580 assert_eq!(to_str(repeat(2, 3, true)), "a{2,3}");
1581 assert_eq!(to_str(repeat(2, 3, false)), "a{2,3}?");
1582 assert_eq!(to_str(repeat(2, usize::MAX, true)), "a{2,}");
1583 assert_eq!(to_str(repeat(2, usize::MAX, false)), "a{2,}?");
1584 assert_eq!(to_str(repeat(0, 1, true)), "a?");
1585 assert_eq!(to_str(repeat(0, 1, false)), "a??");
1586 assert_eq!(to_str(repeat(0, usize::MAX, true)), "a*");
1587 assert_eq!(to_str(repeat(0, usize::MAX, false)), "a*?");
1588 assert_eq!(to_str(repeat(1, usize::MAX, true)), "a+");
1589 assert_eq!(to_str(repeat(1, usize::MAX, false)), "a+?");
1590 }
1591
1592 #[test]
1593 fn escape() {
1594 // Check that strings that need no quoting are borrowed, and that non-special punctuation
1595 // is not quoted.
1596 match crate::escape("@foo") {
1597 Cow::Borrowed(s) => assert_eq!(s, "@foo"),
1598 _ => panic!("Value should be borrowed."),
1599 }
1600
1601 // Check typical usage.
1602 assert_eq!(crate::escape("fo*o").into_owned(), "fo\\*o");
1603
1604 // Check that multibyte characters are handled correctly.
1605 assert_eq!(crate::escape("fø*ø").into_owned(), "fø\\*ø");
1606 }
1607
1608 /*
1609 #[test]
1610 fn detect_backref() {
1611 assert_eq!(detect_possible_backref("a0a1a2"), false);
1612 assert_eq!(detect_possible_backref("a0a1\\a2"), false);
1613 assert_eq!(detect_possible_backref("a0a\\1a2"), true);
1614 assert_eq!(detect_possible_backref("a0a1a2\\"), false);
1615 }
1616 */
1617}