Skip to main content

SciLex

A small, header-only C++20 contextual lexer built on REAL.

  • Linear-time and ReDoS-safe by construction (via REAL).
  • Modes — contextual lexing: the same byte lexes differently by context (f-strings, XML tag/content, YAML block/flow).
  • Layout Awareness — mode-aware indentation (NEWLINE / INDENT / DEDENT).
  • Eager tokenize or lazy scan; positioned errors with a context snippet.
  • C++20 header-only + abi3 Python binding (CPython 3.10+).
  • Zero dependencies beyond REAL headers.

Define an ordered set of token rules — each a (kind, regex, skip) triple — and SciLex tokenizes by maximal munch: the longest anchored match wins, with rule order breaking ties. A rule can also opt into modes (contextual lexing), so the same byte lexes differently by context. Because it is a thin layer over REAL, tokenization is linear and ReDoS-safe by construction.

What that covers today: significant indentation, plus contexts like f-strings, YAML flow collections, and bracket continuation (modes + Layout Awareness Level A). Cases that need a deeper lexing↔indentation coupling — YAML block scalars | / >, heredocs — are Level B: documented, not in this version.

This follows the same design principles as REAL: purity, simplicity, and measured optimality.

Capabilities

  • Ordered token rules: (kind, real::regex, skip)
  • Maximal-munch matching (longest match wins, rule order for ties)
  • Contextual lexing (modes) — per-rule in_mode + a push / pop / set mode stack
  • DFA fast path (opt-in)dfa_modes accelerates DFA-able modes 3–27× (dense grammars ~15–27×) with one real::dfa pass; best-effort (Pike is the floor, with fallback), identical token stream
  • Layout Awareness — mode-aware indentation (NEWLINE / INDENT / DEDENT)
  • Source positions (byte offset, line, column); each token carries its mode
  • Eager (tokenize) and lazy (scan) APIs
  • Optional END_OF_INPUT token
  • Positioned errors with a context snippet
  • Linear-time / ReDoS-safe (via REAL)
  • Nine example grammars — three of them modal (f-strings, XML, YAML)

The three modal grammars differ in shape and each documents its own scope; modes resolve the contexts above, but the one contextual case still outside the model — lexing steered by indentation (block scalars, heredocs) — is Level B.

Not yet: block scalars / heredocs (Layout Awareness Level B), a compile-time static_lexer (a baked DFA — the Phase-0 spike found this wants build-time codegen, not constexpr), codepoint columns.

See the guided tour for details.

C++ API

#include <scilex/scilex.hpp>

std::vector<scilex::rule> rules = {
    {0, real::regex("\\s+"), true},           // whitespace (skip)
    {1, real::regex("if")},                   // keyword before identifier
    {2, real::regex("[a-z_][a-z0-9_]*")},     // identifier
    {3, real::regex("[0-9]+")},               // number
};

scilex::lexer lexer(std::move(rules));
// Opt a mode into the DFA fast path (best-effort; 3–27× on DFA-able modes):
//   scilex::lexer lexer(std::move(rules), /*insignificant=*/ {}, /*dfa_modes=*/ {"default"});

// Eager
for (const auto& tok : lexer.tokenize("if x + 42")) { ... }

// Lazy (preferred for parsers)
for (const auto& tok : lexer.scan("if x + 42")) { ... }

See docs/design.dox for the complete C++ API (lexer, token, position, layout, lex_error).

Python binding

An abi3 CPython extension (CPython 3.10+, Limited API).

import scilex

lx = scilex.Lexer([
    (0, r"\s+", True),                 # whitespace (skip)
    (1, r"[0-9]+", False),             # number
    (2, r"[A-Za-z_][A-Za-z0-9_]*", False),
])
# Opt a mode into the DFA fast path (best-effort; 3–27× on DFA-able modes):
#   lx = scilex.Lexer([...], dfa_modes=("default",))   # lx.dfa_modes_active -> the modes accelerated

# Eager
tokens = lx.tokenize("foo 42", eof=True)

# Lazy (generator)
for tok in lx.scan("foo 42"):
    print(tok.kind, tok.lexeme, tok.position)

# Errors with context
try:
    lx.tokenize("foo @")
except scilex.error as e:
    e.position
    e.context

For significant indentation:

laid = scilex.Layout().apply(lx.tokenize(src, eof=True))

pip install scilex (wheels + sdist). Use scilex.get_include() to compile C++ code against the installed headers.

Build locally: make python && make python-test.

Contextual lexing — modes

A flat rule list can't separate contexts where the same byte means different things — { opens a Python f-string interpolation but a dict elsewhere; < opens an XML tag in content but is just a character inside CDATA. SciLex handles this with an opt-in mode stack: a rule may be restricted to named modes (in_mode) and may push / pop / set the mode when it wins. The engine is unchanged — maximal munch and the exact first-byte dispatch simply run per mode.

This unlocks, with no engine change:

  • f-stringsf"sum={a+b}": code ↔ string body ↔ interpolation, nesting through the stack;
  • XMLcontent ↔ tag (a shallow two-mode flip; CDATA and comments are single regex tokens, so an inner < is literal);
  • YAMLblock ↔ flow (significant indentation plus flow collections).
using op = scilex::mode_action::op;
scilex::rule open {.kind = OPEN, .pattern = real::regex("f\"")};
open.in_mode = {"default", "interp"};                      // active in code
open.action  = {.operation = op::push, .target = "fstr"};  // enters the f-string body
// "{" pushes "interp"; the closing quote pops "fstr"; the stack tracks nesting.
NAME, OPEN, TEXT, LB, RB, CLOSE = range(6)
fstr = scilex.Lexer([
    (NAME, r"[a-z]+", False, ["default", "interp"]),               # code, shared
    (OPEN, r'f"', False, ["default", "interp"], ("push", "fstr")),
    (TEXT, r'[^{}"]+', False, ["fstr"]),
    (LB, r"\{", False, ["fstr"], ("push", "interp")),         # "{" opens it from the body
    (CLOSE, r'"', False, ["fstr"], ("pop",)),
    (RB, r"\}", False, ["interp"], ("pop",)),
])
[t.kind for t in fstr.tokenize(r'f"hi {name}"')]   # OPEN TEXT LB NAME RB CLOSE

An action is None | ("push", mode) | ("set", mode) | ("pop",); a plain (kind, pattern, skip) rule needs neither field, so existing grammars are unaffected. See examples/python.hpp, examples/xml.hpp, examples/yaml.hpp for the three modal profiles in full.

DFA fast path (opt-in)

A mode can be accelerated by a real::dfa: instead of trying each candidate rule at every position, one DFA pass recognizes the winning rule — the same maximal munch, with the order tie-break baked into the automaton. On a mode where many rules share leading bytes that is 3–27× the regular path on the full token path (dense grammars ~15–27×).

scilex::lexer lexer(std::move(rules), /*insignificant=*/ {}, /*dfa_modes=*/ {"default"});
lexer.dfa_modes_active();   // the modes actually accelerated

It is best-effort and invisible: a mode whose rules need a zero-width assertion no DFA can represent, or whose DFA fails a build-time audit (a lazy quantifier — its match is the shortest span while a DFA takes the longest), silently stays on the regular Pike engine, absent from dfa_modes_active(). Either way the token stream is byte identical (Pike is the floor) and layout is unchanged. The DFA is built once, in the constructor. The sql and css example grammars ship with it on.

Unicode identifiers vs DFA speed — the grammar author's choice

A real trade-off worth stating plainly. Write an identifier rule as \w+ (or [^\W\d]\w*) with the default flags and it reads Unicode identifierscafé, 変数 — the faithful behaviour for a language like Python 3. But a Unicode \w \d \s \b compiles to a match-time code-point predicate that no DFA can represent, so a mode holding one leaves the DFA fast path: it is transparently demoted to the general engine (same tokens, visible via dfa_modes_active()). Concretely the general engine runs at ~7–14.5 MB/s while a DFA-able mode runs 3–27× that — the Unicode identifier costs you the DFA.

So: if your identifiers are ASCII by specification (JSON, SQL, C), pin (?a) inline in the pattern (or pass real::flags::ascii) to keep \w \d \s \b ASCII, small, and DFA-representable — what the examples/ grammars do. If you want Unicode identifiers, write \w+ and accept the general-engine floor. The two tokenize ASCII input identically; they differ only on non-ASCII input and on whether the mode can be a DFA. The python-unicode example (scilex --example python-unicode) is the faithful-Python-3 variant of python, identical but for that one rule.

Layout Awareness (Level A)

The layout pass is positional, and by default mode-blind. Layout Awareness Level A lets a mode be marked insignificant (Lexer(insignificant_modes=…)), so its tokens pass through without shaping indentation — and every token carries its mode (Token.mode) for the pass to read.

That lifts two real cases a decoupled positional pass otherwise gets wrong:

  • YAML multi-line flow[\n 1,\n 2\n] adds no spurious INDENT/DEDENT;
  • Python implicit continuation — a call/list/dict wrapped across lines inside () [] {} reads as continuation, not a new block.
laid = lexer.layout(lexer.tokenize(src, eof=True))   # uses the lexer's own policy

Two invariants hold: with no insignificant mode the result is byte-for-byte the positional pass (zero cost); and the mode is the single source of the policy (no per-rule flag).

Honest scope. Level A covers multi-line flow and implicit continuation. Block scalars (| / >) and heredocs need a reference indent carried in the mode frame — that is Level B, a designed next step, not yet built. The bundled grammars demonstrate the features; each examples/<lang>.hpp header documents its own scope.

CLI

scilex is a command-line lexer — make cli builds it, make install puts it on your PATH (PREFIX=/BINDIR= to choose where). It has two input modes.

Built-in grammars — a showcase over the nine example languages (JSON, Python, C++, SQL, CSS, Lisp, math, XML, YAML):

$ scilex --list                       # the built-in grammars
$ scilex --example json file.json     # lex a file …
$ scilex --example python --layout    # … or its bundled sample, with INDENT/DEDENT

Your own grammar — the universal mode: bring a .lex file and lex anything. A grammar is one rule per line — name, a tab, regex, then an optional tab and skip (# comments and blank lines are ignored):

$ cat my.lex
WS	\s+	skip
NUMBER	[0-9]+(\.[0-9]+)?
IDENT	[A-Za-z_][A-Za-z0-9_]*
OP	<=|>=|==|!=|[-+*/%=<>]

$ echo 'x = 41 + 1' | scilex my.lex        # stdin when no file is given
IDENT	x	1:1
OP	=	1:3
NUMBER	41	1:5
OP	+	1:8
NUMBER	1	1:10

Output is one token per line — the kind, a tab, the lexeme, a tab, then line:col; --layout adds the indentation tokens. A malformed grammar is reported with a clear, positioned error (my.lex:3: invalid regex: …) — never a crash. See examples/sample.lex for a worked file.

This .lex format is a tool convenience parsed by the CLI; the library itself stays plain C++ rule lists (std::vector<scilex::rule>) — no spec language is embedded.

Dependencies

SciLex is header-only and depends only on REAL's headers (the package real-regex on PyPI / https://github.com/RECHE23/real-regex).

By default the build looks for them in a sibling checkout:

~/Projects/
├── real-regex/   # REAL (https://github.com/RECHE23/real-regex)
└── scilex/       # SciLex  (uses ../real-regex/include by default)

Point the build elsewhere with REAL_INCLUDE (Makefile) or -DSCILEX_REAL_INCLUDE=... (CMake) — for instance at the path printed by python -c "import real; print(real.get_include())" when REAL is installed via pip.

For CI or a reproducible build — where no on-disk layout can be assumed — fetch REAL with CMake FetchContent instead (make build FETCH=1, or -DSCILEX_FETCH_DEPS=ON); point it at a remote and pin a tag with -DSCILEX_REAL_REPO=https://… -DSCILEX_REAL_TAG=v2026.8.13.

Development

make test        # build and run the test suite
make coverage    # line-coverage summary + HTML report
make sanitize    # tests under AddressSanitizer + UndefinedBehaviorSanitizer
make lint        # clang-tidy
make format      # uncrustify, in place
make doc         # API reference (Doxygen) with embedded coverage

The API reference is published at https://reche23.github.io/scilex/.

Override the compiler with make test CXX=g++-14.

Coverage bar. SciLex holds the SciLang-stack gate — 100% on all four dimensions (lines, functions, regions and branches) of include/, checked by make coverage and enforced by make full-local-gate (using Apple clang 16). The published report on GitHub Pages / the doc tarball (built on clang 18) reads mid-90s (newer clang instruments more branches). This is the documented toolchain distinction; see the live report for exact figures. (REAL is the other documented exception to the 100% gate — see its README.)

scilex::scilex is the CMake target — add_subdirectory, FetchContent, or an installed config package. The config calls find_dependency(real), so installing REAL's config package alongside (on the same prefix) makes the whole chain resolve from one find_package:

# With REAL and SciLex installed under <prefix>:
find_package(scilex CONFIG REQUIRED)   # pulls in real:: transitively
target_link_libraries(app PRIVATE scilex::scilex)

Releasing

make release computes the next calendar version YYYY.M.PATCH (the patch resets each month; PEP 440 drops leading zeros). The pushed tag drives the release workflow — wheels + sdist + the API-reference tarball + a GitHub Release, published via Trusted Publishing — while docs.yml deploys the reference to GitHub Pages.

Design

A guided tour of how SciLex works (maximal munch, REAL foundation, layout, C++/Python API, current scope) lives in docs/design.dox (also rendered by make doc).

Performance

See BENCHMARKS.md. On the benign case measured there SciLex is now 1.39× faster than re (it was ~2× slower a stamp ago); on adversarial input SciLex stays linear while re explodes. See the benchmarks for details.

License

MIT — see LICENSE.

Author

René Chenard

Download files

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

Source Distribution

scilex-2026.8.0.tar.gz (75.7 kB view details)

Uploaded Source

Built Distributions

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

scilex-2026.8.0-cp310-abi3-win_amd64.whl (441.1 kB view details)

Uploaded CPython 3.10+Windows x86-64

scilex-2026.8.0-cp310-abi3-win32.whl (418.7 kB view details)

Uploaded CPython 3.10+Windows x86

scilex-2026.8.0-cp310-abi3-musllinux_1_2_x86_64.whl (3.5 MB view details)

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

scilex-2026.8.0-cp310-abi3-musllinux_1_2_aarch64.whl (3.4 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

scilex-2026.8.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.5 MB view details)

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

scilex-2026.8.0-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl (2.5 MB view details)

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

scilex-2026.8.0-cp310-abi3-macosx_11_0_arm64.whl (287.7 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

scilex-2026.8.0-cp310-abi3-macosx_10_9_x86_64.whl (301.5 kB view details)

Uploaded CPython 3.10+macOS 10.9+ x86-64

File details

Details for the file scilex-2026.8.0.tar.gz.

File metadata

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

File hashes

Hashes for scilex-2026.8.0.tar.gz
Algorithm Hash digest
SHA256 0c7f12f6d178e0c7a26ff354165508145836dc843ef2d7aedbf5f37b42c7604f
MD5 8dec2463ce495f37ff4446df42d21a45
BLAKE2b-256 95aaf52831fc786f3ede2e6e74d89c0be8b22ca1b2c99274ff2d68df39d3285b

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0.tar.gz:

Publisher: release.yml on RECHE23/scilex

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

File details

Details for the file scilex-2026.8.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: scilex-2026.8.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 441.1 kB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for scilex-2026.8.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 5570d1d74d4ed1ec1a10717ac4b275b1577d79fd3502dba3cd8e9af99534291a
MD5 bc734e63453753163e9a4a94742f9760
BLAKE2b-256 2c34e86e9e959966a8745550e7eba346cf7bb5c70a8e70c26c906b7457501642

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0-cp310-abi3-win_amd64.whl:

Publisher: release.yml on RECHE23/scilex

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

File details

Details for the file scilex-2026.8.0-cp310-abi3-win32.whl.

File metadata

  • Download URL: scilex-2026.8.0-cp310-abi3-win32.whl
  • Upload date:
  • Size: 418.7 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 scilex-2026.8.0-cp310-abi3-win32.whl
Algorithm Hash digest
SHA256 1d52f576d23b62a6dba71fa7941d3ca4c804c4a548ff1c8f31a0768de24a9090
MD5 23ef4f7c57032682baae21297fb1efb7
BLAKE2b-256 c7c82257e3fe39c3628f27f6e754f4ff1678a5c5af99a7a18882fc56f85619cd

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0-cp310-abi3-win32.whl:

Publisher: release.yml on RECHE23/scilex

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

File details

Details for the file scilex-2026.8.0-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for scilex-2026.8.0-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 6586e8bb014d9566b2d09f964019e8bb3f621eb537a8e5a0b35d5ed63e16d9f1
MD5 459febe46dbdc64ab1995f19b2b2b868
BLAKE2b-256 4502d40c1283cb55ed4ba023b2b9f4f03551eb24e9c8d98ba56947bd6fc97549

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0-cp310-abi3-musllinux_1_2_x86_64.whl:

Publisher: release.yml on RECHE23/scilex

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

File details

Details for the file scilex-2026.8.0-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for scilex-2026.8.0-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 8e13d810aa98168882d5ed95c93309eca51eb426d16a19a4723a630c3d08a67e
MD5 adbda5f9d292a35bf4b479021da1ac79
BLAKE2b-256 c30436f42bf4f4eaad112cb62f9b283f44107e49a34b33cf4c783e12fd281088

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0-cp310-abi3-musllinux_1_2_aarch64.whl:

Publisher: release.yml on RECHE23/scilex

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

File details

Details for the file scilex-2026.8.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for scilex-2026.8.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 9e59829ba45ed07643dca83bb58a6eea9f986e6e9bffdfc9f91b2bcfabedea58
MD5 fc9fa6f42bfe3c9b5a01f461bf5b72fd
BLAKE2b-256 bdbf7ad8b4bb25dbe8a9eba777f1c6d6e1a23aa52d8f8111a59169aa8859eed1

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl:

Publisher: release.yml on RECHE23/scilex

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

File details

Details for the file scilex-2026.8.0-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for scilex-2026.8.0-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 23720e67e030faa9f8df3f9798b52ae4b9565f64374dc767b10fd59a69cfaa82
MD5 873ee69a4f0991153e8b47a8a2af9d17
BLAKE2b-256 45eb50c12f15109bf96f690aec4d117170be1025b2091e5456b13f34b8917655

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl:

Publisher: release.yml on RECHE23/scilex

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

File details

Details for the file scilex-2026.8.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for scilex-2026.8.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2b58d17c7dbc4d5c361b3f813299b32c99abe1f05400cfd132f66735b580518c
MD5 c7b7c7b04deb547345484efb3c603f54
BLAKE2b-256 61bb14b42ba1e38e89fcec1ce39c3fb8cd3dbeadbccda98c6a5640660821f06f

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on RECHE23/scilex

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

File details

Details for the file scilex-2026.8.0-cp310-abi3-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for scilex-2026.8.0-cp310-abi3-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 709c68c4f3d6ba2f6eaaf269c8355595785ea52a5681c3b35d97901717fddba0
MD5 6142b9ea118b8226df7056ece52da6ee
BLAKE2b-256 b2f3042f838a7b1b9c45bb7c6f9bbcfcfacc7a86c090dc674938057d8d281f70

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.8.0-cp310-abi3-macosx_10_9_x86_64.whl:

Publisher: release.yml on RECHE23/scilex

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

Release history Release notifications | RSS feed

This release

2026.8.0 This release

9 files

2026.7.4

9 files

2026.7.3

9 files

2026.7.2

9 files

2026.7.1

9 files

2026.7.0

9 files

2026.6.9

9 files

2026.6.8

9 files

2026.6.7

9 files

2026.6.6

9 files

2026.6.5

9 files

2026.6.4

9 files

2026.6.3

9 files

2026.6.2

9 files

2026.6.1

9 files

2026.6.0

9 files

Supported by

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