Portability between engines¶
One line: A pattern moving from one engine to another fails in one of three ways — it refuses to compile (the good one), it compiles and means something else (the bad one), or it compiles, means the same thing, and matches differently because \w or $ or (?i) is defined differently underneath — and this page is the checklist for all three, with a table that shows twelve constructs doing each.
Level: 301 · you maintain the same pattern in two languages, or a pattern in a config file that more than one program reads
The three failure modes¶
| Mode | Example | You find out |
|---|---|---|
| Refused | (?P<n>…) in JavaScript; \p{L} in Python |
at compile time, with a message |
| Compiles, means something else | a++ in Rust; \Q in Perl-from-a-string; {,2} in JavaScript; (?R) in Rust |
when a test happens to hit the difference — often never |
| Same construct, different definition | \w on é; $ on a\n; (?i) on U+212A |
in production, on someone else's data |
The first is a compile error and costs five minutes. The second and third are the reason this library exists.
Measured¶
Verified output of topic_portability_between_engines_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
construct python perl node ruby java go rust fancy
(?P<n>…) Python-style name a a - - - a a a
(?<n>…) everyone else - a a a a a a a
\z true end - a no a a a a a
\Z, two meanings no a no a a - - a
\p{L} - \u{3B1} no \u{3B1} \u{3B1} \u{3B1} \u{3B1} \u{3B1}
\h horizontal space - \t no no \t - - no
a++ possessive no no - no no - aa no
(?>…) atomic no no - no no - - no
(?<=a|bc) two-width - d d d d - - d
{,2} open lower bound aa aa no aa - no - aa
(?R) recursion - (a(b)) - - - - (b) -
\Q…\E - no no no a.b a.b - -
Reading the table¶
- Rows 1–2, naming a group. No spelling that all eight accept.
(?P<n>…)is Python, Perl, Go, Rust;(?<n>…)is everyone but Python. PCRE2 — ABAP — takes both. - Rows 3–4, the end of the subject.
\zis missing from Python 3.12 (3.14 added it) and is the letter z in JavaScript.\Zsteps over a final newline in Perl, Ruby, Java andfancy-regex, does not in Python, and is refused by Go and Rust. Two escapes, four behaviours. - Rows 5–6, escapes that are letters somewhere.
\p{L}is refused by Python and is the textp{L}in JavaScript withoutu.\his horizontal space in Perl and Java, a hex digit in Ruby, the letterhin JavaScript, and refused by the rest. - Rows 7–8, possessive and atomic. Both do the same job; both are refused by JavaScript and Go; and
a++in Rust'sregexis(a+)+, which matches where the possessive form cannot. - Row 9, lookbehind width. Two alternatives of different lengths: fine in Perl, JavaScript, Ruby and Java; refused by Python and
fancy-regex. - Row 10,
{,2}. A quantifier in four engines, refused in two, and the literal text{,2}in JavaScript and Go. - Row 11,
(?R). Recursion in Perl; a CRLF flag in Rust, which then matches the inner(b). - Row 12,
\Q…\E. In the engine in Java and Go; in the string in Perl, so refused-or-literal when it arrives at the engine as text.
The checklist¶
Before a pattern crosses an engine boundary, look for these, in roughly the order they bite:
- Anchors.
^…$→\A…\z(or the language'sfullmatch), and check what\Zmeans on the far side. The$page. - Shorthands.
\w,\d,\s,\b— ASCII or Unicode on each side? If it matters, spell it out with\p{…}or a class, and check\p{…}exists. Shorthand classes. - Group names and the references to them, in the pattern and in the replacement. Named group · Replacement references.
- Flags.
mandsandx, and whether they can be inline. Inline flags. - Anything after
(?that is not:,=,!,<=,<!,<name>. Atomic, conditional, recursion, branch reset, verbs: each has a page and a column of-. - Lazy quantifiers heading for a POSIX tool.
sed,awkandgrep -Ehave none. Greedy and lazy. - Alternation order heading for or from a POSIX tool. Leftmost-first and leftmost-longest.
(?i)on non-ASCII text. Case-insensitive matching.
And one process rule: test the pattern in the engine that will run it, with the inputs that differ. A pattern debugged on regex101 in the wrong flavour is debugged against the wrong engine.
The lineages, as a prediction¶
Four families, and knowing which one an engine belongs to predicts most of the table above: Perl/PCRE2 (Perl, PCRE2, ABAP, PHP), the middle ground (Java, Ruby, JavaScript, .NET — each missing a different corner), Python's re (strict about lookbehind and forward references, none of \p), and the automata (Go, Rust's regex, RE2 — no backreferences, no lookaround, a linear-time guarantee). Who supports what is the measured version.
Keywords on this page¶
Every keyword page has a column of - cells, and that column is this topic. Start with named group, \A, \z, \Z, \p{…}, possessive quantifier, {n,m}, recursion, \Q…\E.
See also¶
- Choosing an engine — the same families, from the other direction.
- Testing patterns.