Readable patterns¶
One line: A pattern longer than one line needs the same things code needs — whitespace, comments, names, and pieces that can be reused — and the regex language has all four ((?x), #, (?<name>…), (?(DEFINE)…)) in Perl and PCRE2, most of them in Python, Ruby and Java, and almost none of them in JavaScript and Go, where readability has to come from the host language instead.
Level: 201 · you are about to write a pattern someone else will have to review
The four tools¶
| Tool | Does | Has it |
|---|---|---|
(?x) free-spacing |
whitespace ignored, so the pattern can be laid out | Python, Perl, Ruby, Java, Rust, PCRE2 — not JavaScript, not Go |
# comment |
under (?x), to end of line; (?#…) anywhere |
#: the same list; (?#…): Python, Perl, Ruby, PCRE2 |
(?<name>…) |
a group with a name, so year instead of 3 |
all eight — with two spellings that do not overlap (named group) |
(?(DEFINE)…) + (?&name) |
define a sub-pattern once, call it by name | Perl, PCRE2 only |
Measured¶
Verified output of topic_readable_patterns_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
construct python perl node ruby java go rust fancy
free-spacing (?x) 2026-09 2026-09 - 2026-09 2026-09 - 2026-09 2026-09
comment under (?x) 2026 2026 - 2026 2026 - 2026 2026
(?#…) comment 42 42 - 42 - - - 42
named group (?<y>…) - 2026 2026 2026 2026 2026 2026 2026
named group (?P<y>…) 2026 2026 - - - 2026 2026 2026
scoped flag (?i:…) JANuary JANuary n/a JANuary JANuary JANuary JANuary JANuary
DEFINE and a call - 09:30 - - - - - -
Reading the table¶
- Rows 1–2. Free-spacing and its
#comment work in six engines. JavaScript and Go refuse(?x)outright. In those two, the pattern is built from pieces in the host language — which is not a worse answer, just a different one. - Row 3.
(?#…), the comment that needs no flag: Python, Perl, Ruby andfancy-regex. - Rows 4–5. The two name spellings, and the fact that no engine except Perl, Go, Rust and
fancy-regextakes both. - Row 6,
(?i:jan)uary. A scoped flag keeps a case-insensitive part small and visible. JavaScript isn/abecause its support arrived after ES2018 and Node 20 and 24 disagree. - Row 7, DEFINE.
(?x)(?(DEFINE)(?<d>\d\d))^(?&d):(?&d)$— a two-digit sub-pattern defined once and called twice — matches09:30in Perl and nowhere else here. In PCRE2, and so in ABAP, this is how a large pattern gets written as named parts.
When the engine has no (?x)¶
Build the pattern from named pieces in the language, and let the language's formatter and comments do the work:
JavaScript: const YEAR = String.raw`(?<year>\d{4})`;
const re = new RegExp(`^${YEAR}-${MONTH}-${DAY}$`, "u");
Go: re := regexp.MustCompile(`^` + year + `-` + month + `-` + day + `$`)
Rust: let re = Regex::new(&format!(r"^{year}-{month}-{day}$")).unwrap();
This works everywhere, including the engines that have (?x), and it has one property (?x) lacks: the pieces can be tested on their own.
The rules¶
- Anything over about forty characters gets
(?x)or is assembled from named parts. Both, in a pattern that has to be maintained. - Names, not numbers, for every group that code refers to. Numbers are for
(?:…)— that is, for groups nobody refers to. - A comment says what the piece matches, not what the syntax means.
# ISO week number, 01–53is useful;# two digitsis the pattern again. - Never add
(?x)to an existing pattern without checking every space and#in it — see comment. - A pattern that needs
(?(DEFINE)…)to be readable is usually a parser wearing a regex. It is legitimate in PCRE2 and ABAP, where the pattern is the whole program; elsewhere, write the parser.
Keywords on this page¶
Inline flags · Comment · Named group · Non-capturing group · Conditional — for DEFINE · Recursion and subroutine calls — for (?&name).
See also¶
- Flags, and where you can turn one on — the lesson, with Perl's
xx. - Conditionals, and the
(?(DEFINE)…)block — the IPv4 pattern assembled from DEFINE. - Testing patterns.