corvus-dotnet / corvus-regex-ecma262
ECMA-262 (JavaScript) regular expressions as a PHP extension: the grammar and the answers of RegExp, every flag, UTF-8 and UTF-16, backed by Rust.
Package info
github.com/corvus-dotnet/corvus-regex-ecma262-php
Type:php-ext
Ext name:ext-corvus_regex_ecma262
pkg:composer/corvus-dotnet/corvus-regex-ecma262
Requires
- php: >=8.2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-24 11:43:01 UTC
README
Regular expressions with the grammar and the answers of ECMA-262, the RegExp of JavaScript, for PHP: the
corvus-regex-ecma262 Rust crate, as a PHP extension with
builds for PHP 8.2 to 8.5 (NTS and ZTS) on Linux (glibc and musl, x86_64 and arm64), macOS (arm64) and Windows
(x86_64), installed with PIE.
- Every flag:
d,g,i,m,s,u,vandy, with the grammar of each. Withoutuorva pattern is read by the Annex B grammar and matched by UTF-16 code unit, as ECMA-262 matches it; with them, by code point. Thevflag has nested classes, the difference and the intersection of classes, strings in a class and the properties of strings. - ECMAScript 2025: lookbehind, named groups (and a name shared between alternatives), modifier groups such as
(?i:...), and every Unicode property ECMA-262 lists, from Unicode 17 tables the crate carries. - UTF-8 strings, with positions in bytes as PCRE's functions give them, and UTF-16 text (
Utf16), with positions in code units as JavaScript gives them. A pattern answers as it does in a browser or Node, which PCRE does not. - A pattern without a backreference is matched in time proportional to its size times the text, and a pattern can be
given a match timeout, as a
Regexof .NET can, which every method honours and which caps the backtracking memory too, so with a timeout neither a pattern nor a text from outside can make a program hang.
Compliance. Over the ECMA-262 test data (what V8 says
about its patterns and texts, test262's patterns and exec calls, the global search, replace and split among them,
and texts of bytes that are not UTF-8, which it refuses), through this extension over Utf16 text and UTF-8 strings, it
gives every answer right and none wrong, refuses no valid pattern, accepts no invalid one and reaches no limit. CI
runs the whole data through every build on its platform.
Install
pie install corvus-dotnet/corvus-regex-ecma262
PIE downloads the build for the PHP it installs into, and enables the extension.
Getting started
use Corvus\RegexEcma262\{MatchResult, Regex, Utf16}; // The flags are those of new RegExp($pattern, $flags). $date = new Regex('(?<year>\d{4})-(?<month>\d{2})', 'gu'); $date->test('due 2026-10'); // true $m = $date->exec('due 2026-10'); $m->group('year'); // '2026' [$m->start(), $m->end()]; // [4, 11] // Every match of a global search, as matchAll makes it. array_map(fn(MatchResult $m) => $m->group(), $date->matchAll('2026-10, 2027-01')); // ['2026-10', '2027-01'] // replace replaces every match with the g flag, and the first without it; a callable may give each replacement. $date->replace('2026-10, 2027-01', '$<month>/$1'); // '10/2026, 01/2027' $date->replace('2026-10', fn(MatchResult $m) => $m->group('month')); // '10' // split splices in what the groups captured; a group that did not take part is null. (new Regex('\s*(;)\s*'))->split('a ; b;c'); // ['a', ';', 'b', ';', 'c'] // UTF-16 text, positions in code units as in JavaScript. $date->exec(Utf16::fromUtf8('café 2026-12'))->start(); // 5
The API
new Regex($pattern, $flags = '', $timeout = null) |
Compiles a pattern as new RegExp($pattern, $flags) does. $timeout is the match timeout in seconds (0 for none; null for the default). |
Regex::setDefaultTimeout($seconds), Regex::defaultTimeout() |
The timeout of every pattern compiled without one: null (no limit) unless set. |
Regex::isValid($pattern, $flags = '') |
Whether a pattern is valid, without compiling it. |
test($subject, $offset = 0, $timeout = null) |
RegExp.prototype.test with lastIndex at $offset (at $offset alone with y). |
exec($subject, $offset = 0, $timeout = null) |
The first match at or after $offset, as exec finds it: a MatchResult, or null. |
matchAll($subject) |
A global search as matchAll makes it: an array of MatchResult. |
countMatches($subject) |
The number of matches of a global search, without making them. |
replace($subject, $replacement) |
String.prototype.replace: the first match, or every match with g; a template or a callable. |
replaceAll($subject, $replacement) |
String.prototype.replaceAll: every match. |
split($subject, $limit = null) |
String.prototype.split: an array of strings, null for a group that did not take part. |
source(), flags(), timeout(), groupCount(), groupNames() |
What the pattern is. |
A MatchResult has group($g = 0), start($g = 0) and end($g = 0) (a group by number or name; null when it did
not take part), groups() (the array preg_match fills: the match, then each group) and namedGroups(). A subject is
a string (UTF-8; one that is not throws ValueError) or a Utf16, built from UTF-16LE bytes or with
Utf16::fromUtf8(); the text of a group of a Utf16 subject is a Utf16. Without u or v a pattern is matched by
UTF-16 code unit, so over UTF-8 a match can start or end between the halves of a character beyond the Basic
Multilingual Plane; such a position is the character's third byte, and the half is written as U+FFFD.
Errors. InvalidPatternException, InvalidFlagsException, PatternTooLargeException and MatchLimitException
(a match that reached its timeout or its cap on backtracking memory) are each a RegexException, an Exception.
Timeouts. A pattern has a match timeout, as a Regex of .NET has: new Regex($pattern, $flags, 1.0), or for every
pattern compiled after it Regex::setDefaultTimeout(1.0). Every method that matches gives each of its matches the
timeout and throws MatchLimitException at it; test and exec take a $timeout of their own in its place. Without
a timeout (the default) a match has no limit of time or of memory, so a program that matches a pattern or a subject it
did not write should set one.
Benchmarks
The Industry workloads of dotnet/performance, this extension against PHP's PCRE functions and V8, are on the central page, corvus-oss.org/corvus-regex-ecma262-benchmarks, measured nightly on one runner with the engines of every other source.
Repository
src/lib.rs: the extension (ext-php-rs, over the crate);package.ps1: builds and packages it as PIE expects.tests/api.php: the tests;tools/dataset.php: the test-data runner.tools/bench: the benchmark harness and the script the image runs;Dockerfile: the image the central page runs.
A release is tagged with its version alone (0.1.1), as Composer and PIE read it, and carries every build. A change
to the benchmark harness alone needs no version: it is a harness release, gh release create harness-<version>.<n> --latest=false, which publishes the harness image and attaches no build (Composer and PIE ignore the tag, which is
not a version).
Licence
Apache-2.0. See LICENSE.