re-parser
A Rust library that parses regular expression patterns into a typed abstract syntax tree (AST).
re-parser does not execute regex patterns against input strings. Instead it gives you a structured representation of the pattern that you can inspect, transform, analyse, or use to drive your own matching engine.
Contents
Features
- Parses the full common regex syntax into a clean, exhaustive enum tree.
- Every node carries only the information it needs — no
Option-heavy structs. - Built-in width analysis:
min_width()andmax_width()on every node. - Precise, structured error types with byte positions.
- Zero runtime dependencies (only
thiserrorfor error derive).
Installation
Add the crate to your Cargo.toml:
[]
= { = "../re-parser" } # or version = "..." once published
Quick start
use parse;
Output:
min chars: 10
max chars: Some(10)
Concat(
[
Quantifier(EscapeClass(Digit), Exactly(4), true),
Literal('-'),
Quantifier(EscapeClass(Digit), Exactly(2), true),
Literal('-'),
Quantifier(EscapeClass(Digit), Exactly(2), true),
],
)
Tutorial
1. Parsing a pattern
The entry point is re_parser::parse. It takes a &str and returns
Result<ast::Regex, error::ParseError>.
use parse;
// Success
let ast = parse.unwrap;
// Failure — structured error with position
match parse
2. The AST node types
All types live in the re_parser::ast module.
Regex — the root enum
├── Literal(char) — a single character: a \n \.
├── AnyChar — dot: .
├── Anchor(Anchor) — ^ $ \b \B
├── EscapeClass(…) — \d \D \w \W \s \S
├── CharClass(…) — [abc] [^a-z] [\d_]
├── Group(Box<Regex>, GroupKind)
│ ├── Capturing — (...)
│ ├── Named(String) — (?P<name>...)
│ ├── NonCapturing — (?:...)
│ ├── LookaheadPos — (?=...)
│ ├── LookaheadNeg — (?!...)
│ ├── LookbehindPos — (?<=...)
│ └── LookbehindNeg — (?<!...)
├── Quantifier(Box<Regex>, QuantKind, bool)
│ ├── ZeroOrMore — * (bool = greedy)
│ ├── OneOrMore — +
│ ├── ZeroOrOne — ?
│ ├── Exactly(n) — {n}
│ ├── AtLeast(n) — {n,}
│ └── Between(n, m) — {n,m}
├── Concat(Vec<Regex>) — sequence: ab\d
└── Alternation(…) — a|b|c
3. Literals and any-char
use ;
assert_eq!;
assert_eq!;
// Escaped literal — the backslash is consumed by the parser
assert_eq!;
assert_eq!;
// Multiple characters become a Concat
if let Concat = parse.unwrap
4. Anchors and escape classes
use ;
use parse;
// Anchors — zero-width assertions
assert_eq!;
assert_eq!;
assert_eq!;
// Predefined character-class shorthands
assert_eq!;
assert_eq!;
assert_eq!;
Available shorthands: \d \D \w \W \s \S.
5. Character classes
A character class [...] is represented by Regex::CharClass, which holds a
Vec<CharClassItem> and a negated flag.
use ;
use parse;
// [a-z0-9_]
let CharClass = parse.unwrap else ;
assert!;
// items: [Range('a','z'), Range('0','9'), Literal('_')]
// [^\d] — negated class containing an escape shorthand
let CharClass = parse.unwrap else ;
assert!;
assert_eq!;
CharClassItem variants:
| Variant | Example |
|---|---|
Literal(char) |
[abc] |
Range(char, char) |
[a-z] |
EscapeClass(EscapeClass) |
[\d\w] |
6. Quantifiers
A quantifier wraps an inner node, a QuantKind, and a bool that is true
for greedy and false for lazy.
use ;
use parse;
// Greedy: a+
let Quantifier = parse.unwrap else ;
assert_eq!;
assert_eq!;
assert!;
// Lazy: a+?
let Quantifier = parse.unwrap else ;
assert!;
// Counted: \d{2,4}
let Quantifier = parse.unwrap else ;
assert_eq!;
| Syntax | QuantKind |
|---|---|
* |
ZeroOrMore |
+ |
OneOrMore |
? |
ZeroOrOne |
{n} |
Exactly(n) |
{n,} |
AtLeast(n) |
{n,m} |
Between(n, m) |
Append ? to any of the above to make it lazy: *?, +?, ??, {n,m}?.
7. Groups and lookarounds
use ;
use parse;
// Capturing
let Group = parse.unwrap else ;
assert_eq!;
// Named capturing
let Group = parse.unwrap else ;
assert_eq!;
// Non-capturing
let Group = parse.unwrap else ;
assert_eq!;
// Lookarounds — these are zero-width: they do not consume characters
// "foo(?=bar)" matches "foo" only when followed by "bar"
// "foo(?!bar)" matches "foo" only when NOT followed by "bar"
// "(?<=\d)px" matches "px" only when preceded by a digit
// "(?<!\d)px" matches "px" only when NOT preceded by a digit
8. Alternation and concatenation
use Regex;
use parse;
// Alternation
let Alternation = parse.unwrap else ;
assert_eq!;
// Concatenation
let Concat = parse.unwrap else ;
assert_eq!;
// Mix: (foo|bar)\d+
// → Concat([Group(Alternation([…]), Capturing), Quantifier(EscapeClass(Digit), OneOrMore, true)])
9. Width analysis
Every Regex node has two methods:
| Method | Return type | Meaning |
|---|---|---|
.min_width() |
usize |
Fewest characters the pattern can consume |
.max_width() |
Option<usize> |
Most characters consumed; None = unbounded |
Anchors (^, $, \b) and lookaround groups ((?=…), (?<=…), …) are
zero-width — they do not consume any characters.
use parse;
let ast = parse.unwrap; // ISO date
assert_eq!;
assert_eq!;
let ast = parse.unwrap;
assert_eq!; // "http://" + at least one non-space
assert_eq!; // unbounded
let ast = parse.unwrap; // anchors are zero-width
assert_eq!;
assert_eq!;
let ast = parse.unwrap; // lookahead is zero-width
assert_eq!;
assert_eq!;
The pattern_width convenience function parses and measures in one call and
returns a Width struct:
use pattern_width;
let w = pattern_width.unwrap;
println!; // "2..=8"
println!; // 2
println!; // Some(8)
assert!;
assert!;
assert!;
Width helper predicates:
| Method | Returns true when |
|---|---|
is_fixed() |
min == max |
is_nullable() |
min == 0 |
is_unbounded() |
max.is_none() |
Width rules at a glance:
| Node | min_width |
max_width |
|---|---|---|
Literal / AnyChar / \d / […] |
1 | Some(1) |
^ $ \b / lookaround |
0 | Some(0) |
expr* |
0 | None |
expr+ |
inner.min | None |
expr? |
0 | inner.max |
expr{n,m} |
n × inner.min | m × inner.max |
Concat |
Σ mins | Σ maxes (None if any child is unbounded) |
Alternation |
min of mins | max of maxes (None if any branch is unbounded) |
10. Walking the AST
Because Regex is a plain Rust enum you can walk it with a normal recursive
function — no visitor trait required.
use ;
use parse;
/// Recursively collect every literal character in the pattern.
let ast = parse.unwrap;
let mut chars = Vecnew;
literals;
let s: String = chars.into_iter.collect;
assert_eq!; // fixed characters in the pattern
You can also call min_width / max_width on any sub-node, not just the root:
use Regex;
use parse;
let ast = parse.unwrap;
if let Concat = &ast
// child[0] min=4 max=Some(4) — (\d{4})
// child[1] min=1 max=Some(1) — literal '-'
// child[2] min=2 max=Some(2) — (\d{2})
// child[3] min=1 max=Some(1) — literal '-'
// child[4] min=2 max=Some(2) — (\d{2})
11. Error handling
parse returns Result<Regex, ParseError>. All variants carry byte positions
so you can report them to users.
use ;
match parse
Full ParseError variant list:
| Variant | Cause |
|---|---|
UnexpectedEnd |
Pattern ends where more input was expected |
UnexpectedChar(char, pos) |
Character that cannot start a valid construct |
UnmatchedOpenParen(pos) |
( with no matching ) |
UnmatchedCloseParen(pos) |
) with no preceding ( |
UnmatchedOpenBracket(pos) |
[ with no matching ] |
InvalidQuantifier(pos, msg) |
Bad {n,m} syntax or min > max |
InvalidEscape(char, pos) |
Unrecognised \x sequence |
InvalidRange(lo, hi) |
[z-a] — start > end |
InvalidGroup(pos, msg) |
Unknown (?…) modifier |
InvalidGroupName(name) |
(?P<…>) name contains illegal characters |
Supported syntax
| Syntax | Description |
|---|---|
a |
Literal character |
. |
Any character (except newline) |
^ |
Start-of-string anchor |
$ |
End-of-string anchor |
\b \B |
Word / non-word boundary |
\d \D |
Digit / non-digit |
\w \W |
Word char / non-word char |
\s \S |
Whitespace / non-whitespace |
\n \t \r \f \v \0 |
Common escape sequences |
\. \* \+ … |
Escaped metacharacter → literal |
[abc] |
Character class — any of a, b, c |
[^abc] |
Negated class |
[a-z] |
Character range |
[\d_] |
Escape shorthands inside […] |
(…) |
Capturing group |
(?P<name>…) |
Named capturing group |
(?:…) |
Non-capturing group |
(?=…) (?!…) |
Positive / negative lookahead |
(?<=…) (?<!…) |
Positive / negative lookbehind |
* + ? |
Greedy quantifiers |
*? +? ?? |
Lazy (non-greedy) quantifiers |
{n} |
Exactly n repetitions |
{n,} |
At least n repetitions |
{n,m} |
Between n and m repetitions (inclusive) |
a|b |
Alternation |
API reference
Functions
// Parse a pattern into an AST
ast::Regex methods
Width
width::node_width
Thin wrapper: delegates to .min_width() / .max_width().
Running the examples
# Parsing fundamentals
# Every quantifier form, greedy and lazy
# All group kinds including lookarounds
# Character class expressions
# Recursive AST visitors (node count, literal extraction, pretty-printer)
# Real-world patterns: IPv4, ISO date, email, semver, URL
# min_width / max_width analysis
Run the test suite: