php-regex / regex-transpiler
Transpiles PCRE patterns to JavaScript and Python regular expressions, with the losses reported.
Fund package maintenance!
Requires
- php: >=8.1
- ext-mbstring: *
- php-regex/regex-parser: 2.x-dev
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-27 10:05:46 UTC
README
PHPRegex Transpiler
Transpiles PCRE patterns to JavaScript regular expressions, HTML pattern attributes and Python regular expressions, with the losses reported.
Documentation: php-regex.com — the API reference documents transpile() in full.
Features
- Three targets,
javascript(aliasjs),html-pattern(aliashtml) andpython(aliaspy), each producing a paste-ready literal and a constructor call - Dialect rewrites:
(?P<name>)becomes(?<name>)in JavaScript,\k<name>becomes(?P=name)in Python,\hand\vbecome character classes - What a target reads otherwise is written out as PCRE reads it:
$, which also matches before a final newline,^and$underm, the dot, which leaves out\nalone, and, underu,\d,\w,\sand\b, which PHP reads in Unicode - Flags are mapped per target: JavaScript keeps
i,s,u, gainsuwhen Unicode escapes need it, and has/m,/D,/xand/Uapplied while printing; Python keepsiands, gainsre.ASCIIwithoutu, has/m,/D,/xand/Uapplied while printing, and spells flags inline or asreconstants; every target drops/S, which PHP has ignored since 7.3, with a note - Warnings list every rewrite to review; notes list the flags applied or dropped
- A construct the target cannot express without matching otherwise — a possessive quantifier, an atomic group or
\wunderuin Python, a backreference to a group that may not have matched in JavaScript — throws aTranspileExceptioncarrying the position in the pattern - Every target is checked against its own engine: the test suite runs the transpiled patterns in Node and Python on the subjects
preg_match()is run on, and compares the matches and the groups
Installation
composer require php-regex/regex-transpiler
PHP 8.1 or newer. php-regex/regex-parser is pulled in automatically.
Configuration
transpile() takes an optional TranspileOptions object: allowLookbehind
(bool, default true) — false makes lookbehind throw for JavaScript targets.
Usage
A PCRE pattern with a named group and a backreference, for a JavaScript codebase:
use PHPRegex\Transpiler\Transpiler; $transpiler = new Transpiler(); $result = $transpiler->transpile('/(?P<word>\w+)\k{word}/i', 'javascript'); echo $result->literal; // /(?<word>\w+)\k<word>/i // "$" also matches before a newline that ends the subject; JavaScript's does not. echo $transpiler->transpile('/^\d+$/', 'javascript')->literal; // /^\d+(?=\n?$)/
Without a parser the transpiler reads with RegexParser::create(); pass the
one your code reads with, as new Transpiler($parser), to read the pattern for
its target and limits.
For Python, the literal is a raw string carrying the flags inline, and the constructor is a re.compile() call.
Without u PCRE reads \w, \d, \s, \b and case in ASCII, so the pattern gains re.ASCII:
$result = $transpiler->transpile('/(?P<word>\w+)\k<word>/i', 'python'); echo $result->literal; // r'(?ai)(?P<word>\w+)(?P=word)' echo $result->constructor; // re.compile(r'(?P<word>\w+)(?P=word)', re.ASCII | re.IGNORECASE)
Every rewrite is reported, and what a target cannot express is refused rather than approximated:
$result = $transpiler->transpile('/\p{L}+/', 'javascript'); echo $result->literal; // /\p{L}+/u echo $result->warnings[0]; // Added /u for Unicode property escapes. // PCRE2 reads a bare script name as the script's extensions (from 10.40; the script alone before); JavaScript names them. echo $transpiler->transpile('/\p{Han}+/u', 'javascript')->literal; // /\p{Script_Extensions=Han}+/u try { $transpiler->transpile('/a++/', 'javascript'); } catch (\PHPRegex\Transpiler\Exception\TranspileException $e) { echo $e->getMessage(); // Possessive quantifiers are not supported in JavaScript. }
For an HTML form, the pattern attribute matches the whole value under the
v flag; the html-pattern target pads an unanchored side so the attribute
accepts what preg_match() finds, and escapes in classes what v reserves:
$result = $transpiler->transpile('/^[\w.-]+@[\w-]+\.[a-z]{2,}$/', 'html-pattern'); echo $result->literal; // ^[\w\.\-]+@[\w\-]+\.[a-z]{2,}$ echo $transpiler->transpile('/\d{4}/', 'html')->literal; // [\s\S]*(?:\d{4})[\s\S]*
No flag reaches the attribute. /i, (?i) and (?i:…) are spelled out:
each letter, class or property is written with every character the running
PCRE takes for it caselessly, so the attribute works in every browser that
has the v flag, and under u the Kelvin sign and the long s come along:
echo $transpiler->transpile('/^[a-z]+-\d+$/i', 'html')->literal; // ^[a-zA-Z]+-\d+$ echo $transpiler->transpile('/^ok$/iu', 'html')->literal; // ^[oO][kK\u212A]$
A backreference under /i is refused: it would match its group's text in
one case only. /s, /m and /D change nothing in a field value, which
holds no line break. /S, which PHP has ignored since 7.3, is dropped with a
note, there as for JavaScript, and /U or (?U) swaps greedy and lazy in the
quantifiers it governs: /<.+>/U is /<[^\n]+?>/ for JavaScript, the dot
written as PCRE reads it.
JavaScript and Python read characters, where PCRE without u reads bytes. A
multibyte character written whole (/café/) stays that character; a byte above
0x7F on its own, as an escape (\xE9, [\x80-\xFF]) or as invalid UTF-8, has
no equivalent there and is refused: add u, or write the character itself.
Documentation
- API reference — the
transpile()entry point and theTranspileResultfields - CLI guide — the
regex transpilecommand, its--targetoption and its exit codes - Backward compatibility promise — what stays stable across releases
Resources
- Upgrading from 1.x — moving from
yoeunes/regex-parser1.3 to 2.0, every change that needs one on your side - Documentation
- The parsing core it builds on: regex-parser
- Changelog
- Report issues and send pull requests in the main PHPRegex repository
Sponsors
If PHPRegex saves you time, consider sponsoring its maintenance.
License
MIT. See LICENSE.