Search by

php-regex / regex-transpiler

yoeunes

Transpiles PCRE patterns to JavaScript and Python regular expressions, with the losses reported.

Package info

github.com/php-regex/regex-transpiler

Homepage

Issues

Documentation

pkg:composer/php-regex/regex-transpiler

Fund package maintenance!

yoeunes

Statistics

Installs: 0

Dependents: 4

Suggesters: 0

Stars: 0

2.x-dev 2026-10-27 06:25 UTC

This package is auto-updated.

Last update: 2026-10-27 10:05:46 UTC


README

PHPRegex Transpiler

Documentation Badge Author Badge GitHub Release Badge License Badge Packagist Downloads Badge GitHub Stars Badge Supported PHP Version Badge

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 (alias js), html-pattern (alias html) and python (alias py), 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, \h and \v become character classes
  • What a target reads otherwise is written out as PCRE reads it: $, which also matches before a final newline, ^ and $ under m, the dot, which leaves out \n alone, and, under u, \d, \w, \s and \b, which PHP reads in Unicode
  • Flags are mapped per target: JavaScript keeps i, s, u, gains u when Unicode escapes need it, and has /m, /D, /x and /U applied while printing; Python keeps i and s, gains re.ASCII without u, has /m, /D, /x and /U applied while printing, and spells flags inline or as re constants; 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 \w under u in Python, a backreference to a group that may not have matched in JavaScript — throws a TranspileException carrying 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

Resources

Sponsors

Sponsor

If PHPRegex saves you time, consider sponsoring its maintenance.

License

MIT. See LICENSE.