Skip to main content

REAL — linear-time (ReDoS-safe) regex engine with an re-compatible API

Project description

REAL

Regular Expression Algorithmic Library — a header-only C++20 regex engine, constexpr from end to end, with an re-compatible Python binding.

  • Linear time, always. The engine is a Pike VM (Thompson NFA simulation): no backtracking, ReDoS-safe by construction.
  • Constexpr-friendly. Patterns known at compile time are parsed, compiled and matched at compile time.
  • Minimal memory. Static (sizes fixed at compile time, zero allocation), dynamic (storage sized exactly once at pattern compilation), or hybrid (compile-time pattern, runtime text, zero heap allocation).
  • Zero dependencies. One include.

Bounded lookarounds match in linear time: lookahead (?=...)/(?!...) and lookbehind (?<=...)/(?<!...). Each sub-pattern must be length-bounded (an unbounded sub such as (?=a*) is rejected) and is capture-free — groups inside a lookaround do not participate in the result, a deliberate divergence from re. Lookbehind accepts any bounded sub-pattern, including variable-width alternations such as (?<=a|bb), which re and PCRE reject as non-fixed-width. static_regex does not accept lookarounds yet.

Unsupported syntax is rejected with real::regex_error rather than silently diverging. Not yet: backreferences, atomic/possessive groups, Unicode property classes, Unicode case folding.

Matching is linear in the input length: a Thompson NFA simulation (Pike VM) with marked states, so a pattern such as (a+)+b cannot trigger exponential backtracking. A literal prefilter and several whole-pattern fast paths (literals, fixed-width sequences, ./negated-class runs, alternations of straight-line branches) keep the constant factor low without leaving the linear-time guarantee.

make bench-python compares throughput against Python's re, and make bench-engines compares against std::regex, PCRE2 and RE2 in one C++ process (each engine's match counts are checked equal). Figures depend on the platform, pattern and input; reproduce them locally rather than trusting a number here. A measured baseline, with the exact machine and engine versions, is archived in BENCHMARKS.md.

Supported syntax

Syntax Meaning
abc literal bytes (UTF-8 patterns match their UTF-8 bytes)
\. \* \\ escaped metacharacter, matched literally
. any codepoint except \n
[abc] [a-z] [^abc] character class (members must be ASCII); [^…] matches any codepoint outside the set
\d \D \w \W \s \S digit / word / space classes (ASCII sets, like Python's re.ASCII)
\n \t \r \f \v \a \0 \xHH control and hex escapes
x* x+ x? quantifiers (greedy; append ? for lazy)
x{n} x{n,} x{,m} x{n,m} counted repetition (greedy or lazy; counts capped at 1000)
a|b alternation, leftmost branch preferred
(…) (?:…) capturing / non-capturing group
(?P<name>…) (?<name>…) named capturing group (Python and .NET styles)
^ $ line/text anchors (Python semantics: $ also matches before a final \n)
\A \Z strict text start / end
\b \B word boundary / non-boundary (ASCII word characters)
\< \> start / end of word (REAL extension, not in Python re)
(?imsx) prefix global flags: i case-insensitive (ASCII), m multiline, s dotall, x verbose (ignore unescaped whitespace and # comments outside classes) — also real::flags on the constructor

Unicode model: matching is UTF-8 byte-based, but every construct consumes whole codepoints (multi-byte sequences compile to byte-level alternatives), so match boundaries never split a character. Class members and the \d \w \s sets are ASCII by design; [^…], \D \W \S and . do match non-ASCII codepoints.

Divergence from Python: when a nullable loop body ends with an empty iteration — e.g. (a*)* on "aa" — Python captures that final empty iteration (''); REAL, like Perl/PCRE, keeps the last non-empty one ("aa"). Group 0 is identical either way.

C++ API

#include <real/real.hpp>

real::regex rx("hello");     // runtime pattern, storage sized exactly once
rx.match("hello world");     // anchored at the start   (Python re.match)
rx.fullmatch("hello");       // whole text              (Python re.fullmatch)
rx.search("say hello");      // leftmost match anywhere (Python re.search)

match/fullmatch/search return a real::match_result: matched(), operator bool, start(g), end(g), m[g] (a std::string_view into the searched text, which must outlive the result), and the same accessors by group name (m["year"], group_index).

for (auto& m : rx.find_iter(text)) {  }      // lazy, Python finditer rules
rx.find_all(text);                            // eager vector<match_result>
rx.replace(text, "$2:$1");                    // $&, $1…, ${name}, $$ — re.sub
rx.replace(text, "#", 2);                     // count limit
rx.split(text);                               // Python re.split, with groups

Empty matches follow Python's rules: they are yielded (even right after a non-empty match) and the scan then advances one whole codepoint. find_iter/find_all cannot be called on a temporary regex, and match/search/split cannot take a temporary std::string.

Three memory modes

// Static: pattern compiled at compile time into exactly-sized constexpr
// arrays; an invalid pattern is a *compile error*.
constexpr real::static_regex<"(\\d{4})-(\\d{2})"> date;
static_assert(date.search("on 2026-06-10")[1] == "2026");  // constexpr match

// Hybrid: compile-time pattern, runtime text — matching performs zero heap
// allocations (state lives on the stack).
date.search(runtime_text);

// Dynamic: everything at runtime; the program is sized exactly once at
// compilation, match state is per-run scratch.
real::regex rx2(user_pattern, real::flags::icase);

DFA over a rule set (opt-in)

#include <real/dfa.hpp>   // opt-in: not pulled in by <real/real.hpp>

const std::array patterns {real::regex("\\s+"), real::regex("[0-9]+"),
                           real::regex("[A-Za-z_][A-Za-z0-9_]*")};
real::dfa d(std::span<const real::regex>(patterns));   // built once, then immutable
auto hit = d.match("foo");   // -> {rule_index = 2, length = 3}; std::nullopt if none

real::dfa fuses a set of patterns into one capture-free, maximal-munch DFA: a single left-to-right pass recognizes the winning rule (longest match; ties to the earliest pattern; empty excluded) instead of running each pattern in turn — linear-time and ReDoS-safe like the engine, built at run time and then immutable. It is the accelerated rule dispatch a lexer wants (SciLex's dfa_modes is built on it). A pattern carrying a zero-width assertion no DFA can represent ($, \b, multiline ^/$) throws real::dfa_error; lazy and greedy accept the same language, so feed it longest-match-faithful rules.

The pure library is standard C++20 with no platform dependencies. real::real is the CMake target, available three ways — add_subdirectory, FetchContent, or an installed config package:

# After `cmake --install <build> --prefix <prefix>`:
find_package(real CONFIG REQUIRED)
target_link_libraries(app PRIVATE real::real)

Installation

REAL is header-only, so "installing" just places the headers and the package metadata where a consumer can find them. After cmake --install <build> --prefix <prefix>, there are three ways to consume it from C++:

# 1. CMake — find_package against the installed config package:
find_package(real CONFIG REQUIRED)
target_link_libraries(app PRIVATE real::real)
# 2. pkg-config — for Make / Meson / autotools (and the system packagers):
c++ -std=c++20 $(pkg-config --cflags real) app.cpp -o app
# 3. Direct copy — vendor include/ into your tree, no build system needed:
c++ -std=c++20 -I/path/to/real/include app.cpp -o app

REAL requires C++20 or later. Every header asserts it (#include <real/...> fails fast with a clear message under an older standard), and pkg-config has no field to convey a language standard — so the consumer must pass -std=c++20 (or newer) itself, as shown above.

Python binding

An re-compatible module backed by the C++ engine (CPython Limited API, one abi3 extension, zero dependencies):

import real

real.search(r"(?P<y>\d{4})-(?P<m>\d{2})", "on 2026-06-10").groupdict()
real.compile(r"\w+").findall(text)         # findall/finditer/split/sub/subn
real.sub(r"\s+", " ", text)                # templates: \1, \g<name>, callables
real.search(r"(\w+)=(\w+)", "k=v").expand(r"\2:\1")   # Match.expand -> "v:k"
real.compile(rb"[^;]+").findall(raw)       # bytes patterns: raw-byte semantics

str matching is UTF-8 with character indices in start/end/span; bytes patterns get re's exact raw-byte semantics. Unsupported re features raise real.error at compile time. Build with make python && make python-test.

pip install real-regex installs one cp310-abi3 wheel per platform (CPython 3.10+; the self-contained sdist compiles where no wheel matches).

Embedding the C++ library through the Python package

The wheel also ships the C++ headers, so a project can compile against REAL located through its Python install — the convention used by petsc4py and slepc4py:

c++ -std=c++20 $(python -c "import real; print(real.get_include())") app.cpp

real.get_config() returns the version, the include directory and the required C++ standard.

Releasing. Run make release. It computes the next calendar version YYYY.M.PATCH — the patch resets each month, the first release of a month is .0 (PEP 440 drops leading zeros, so 2026.6.1, never 2026.06.001) — bumps it in pyproject.toml and python/real/__init__.py, then commits, tags and pushes. The tag drives release.yml, which checks the tag matches the version, builds abi3 wheels (cibuildwheel, Linux/macOS/Windows) and the sdist, and publishes to PyPI via Trusted Publishing (OIDC, no stored secret). The pushed tag is the single thing that triggers a publish.

Development

make help        # list all targets
make test        # build and run the test suite
make coverage    # line coverage report (LLVM)
make sanitize    # tests under ASan + UBSan
make lint        # clang-tidy
make misra       # MISRA C++:2023-oriented analysis
make fuzz        # libFuzzer robustness fuzzing (clang)
make doc         # API reference (Doxygen)
make format      # Uncrustify, in place
make format-check  # Uncrustify, dry-run; exits non-zero on diff

The API reference is published at https://reche23.github.io/real-regex/.

Select the compiler with make test CXX=g++-14. Every behaviour is tested at runtime and in constexpr (static_assert) under Clang and GCC; an equivalence suite checks the prefilter and fast paths never change results; a parity suite and a randomized differential fuzzer compare Python outputs against re.

Coverage bar. REAL holds a high line-coverage bar (mid-90s on include/), checked with make coverage. It deliberately does not adopt the 100%-on-all-four-dimensions (lines, functions, regions, and branches) gate used by the SciLang-stack libraries built on top of it: as the oldest and most complex engine here, its dual runtime/constexpr execution and Pike-VM branch structure leave some regions and branches impractical to drive to 100% without contrived tests. That lower-but-still-high bar is a deliberate, documented exception, not an oversight — REAL keeps its own gate (above) and its broad public CI.

CI exercises:

Platform Architecture Compiler
Linux x86-64 GCC, Clang
Linux AArch64 GCC
macOS Apple Silicon (arm64) Apple Clang
Windows x86-64 MSVC

IntelLLVM (icpx), x86-64 macOS and the BSDs share the Clang flag set and are supported by the build configuration but not exercised in CI.

License

MIT — Copyright (c) 2026 René Chenard

Author

René Chenard

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

real_regex-2026.6.17.tar.gz (115.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

real_regex-2026.6.17-cp310-abi3-win_amd64.whl (324.4 kB view details)

Uploaded CPython 3.10+Windows x86-64

real_regex-2026.6.17-cp310-abi3-win32.whl (319.9 kB view details)

Uploaded CPython 3.10+Windows x86

real_regex-2026.6.17-cp310-abi3-musllinux_1_2_x86_64.whl (1.7 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

real_regex-2026.6.17-cp310-abi3-musllinux_1_2_aarch64.whl (1.6 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

real_regex-2026.6.17-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (649.5 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

real_regex-2026.6.17-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl (638.8 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.24+ ARM64manylinux: glibc 2.28+ ARM64

real_regex-2026.6.17-cp310-abi3-macosx_11_0_arm64.whl (134.9 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

real_regex-2026.6.17-cp310-abi3-macosx_10_9_x86_64.whl (138.4 kB view details)

Uploaded CPython 3.10+macOS 10.9+ x86-64

File details

Details for the file real_regex-2026.6.17.tar.gz.

File metadata

  • Download URL: real_regex-2026.6.17.tar.gz
  • Upload date:
  • Size: 115.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for real_regex-2026.6.17.tar.gz
Algorithm Hash digest
SHA256 05fdf9e6e7143620fb4ef514113e821bb271328a2c816943ec29130a53939e4c
MD5 a5c624199af7a20571db1663b45e9fec
BLAKE2b-256 406fb2af5bfb06dc88f8f923ee06066020c065154a34264754ac2b28dc28caa5

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17.tar.gz:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file real_regex-2026.6.17-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for real_regex-2026.6.17-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 e73e29dfb947cf97c5ce07e1b27fb21f2f753caf05726921099389467614e4b1
MD5 66b8e7ea62e8a7dc3be1779ae2344651
BLAKE2b-256 9261a1cec96c71235207a494485b931d3b37daf0d32f8fb20614bc2872d904ea

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17-cp310-abi3-win_amd64.whl:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file real_regex-2026.6.17-cp310-abi3-win32.whl.

File metadata

  • Download URL: real_regex-2026.6.17-cp310-abi3-win32.whl
  • Upload date:
  • Size: 319.9 kB
  • Tags: CPython 3.10+, Windows x86
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for real_regex-2026.6.17-cp310-abi3-win32.whl
Algorithm Hash digest
SHA256 014663063f2877fe70a90ccd5519687dfe51a46a7e02de9c886e059a40ad2891
MD5 aa865a061ac8e4cc9b99cce188d70f9e
BLAKE2b-256 323ce2e516bf625a188084291ae5cf2f651df1158796de6fc89fa920339d38ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17-cp310-abi3-win32.whl:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file real_regex-2026.6.17-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for real_regex-2026.6.17-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 9b611eb6afebe0b5dd2eebd6b327fafc15177cac6b229b6cd584ecc2ddc77683
MD5 a10c78db3a36fb1db985fcb67c3cc072
BLAKE2b-256 110cf91e6037880c2c60875e281971782c0e9b10004f64d0ce8670a21b2aa4c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17-cp310-abi3-musllinux_1_2_x86_64.whl:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file real_regex-2026.6.17-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for real_regex-2026.6.17-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 8fe71d9972285a756d021608bba8dad7243981521a897f7b7b90d352a2bebf77
MD5 deeb2e0162be16bd73aaebb7890e63d8
BLAKE2b-256 b5a3986d38986f37425b445c188c52066b069ea35f872efc4189d6c7194cc7d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17-cp310-abi3-musllinux_1_2_aarch64.whl:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file real_regex-2026.6.17-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for real_regex-2026.6.17-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d341a9d7844a82b4fc06b32061483b34565671810a4bf983d1907824b907a45e
MD5 621ec59b40bb22ae551aa67002f3546e
BLAKE2b-256 6100cfa171286316e80e8f9ba5b42ef579b9af796ce951b58beeb7f1c9dbaad1

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file real_regex-2026.6.17-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for real_regex-2026.6.17-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 3a962987b66a9872d3697c7c07f75bd7a3e7f6a0eb004d847329b77e4e26d2ad
MD5 7cb621487c338547d0ef42b991c6187c
BLAKE2b-256 ef4c33f905a49b5113e0c4140d92f6bea55d7371355644075dc2743ed69d551b

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file real_regex-2026.6.17-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for real_regex-2026.6.17-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7708c646647cb0cdb5e89c95b85be913743f8dc2c33040029fae30097434a1a5
MD5 9543f5743ba006887e09bb10c7a14825
BLAKE2b-256 7d0e796806ec486c29653415a3f6a6e2e2ef1caea60f925145288ac7a902c26d

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file real_regex-2026.6.17-cp310-abi3-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for real_regex-2026.6.17-cp310-abi3-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 64a67b379bd2d8676e3462e85cb09b19e3e2a44353e05e4eb8eeed659f6fe9a0
MD5 d681f24924b17b76412fc2b4fe7f00e3
BLAKE2b-256 7d6e4d969976673e0a8e21d3ab1bba45832a9468cb0b2f706a0fe3ac5a5dfa84

See more details on using hashes here.

Provenance

The following attestation bundles were made for real_regex-2026.6.17-cp310-abi3-macosx_10_9_x86_64.whl:

Publisher: release.yml on RECHE23/real-regex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page