Skip to main content

SciLex — a generic, linear-time maximal-munch lexer built on REAL

Project description

SciLex

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

Define an ordered set of token rules — each a kind paired with a REAL regular expression — and SciLex tokenizes source text by maximal munch (longest match wins, rule order breaks ties). Because REAL is a linear-time engine, tokenization is linear and ReDoS-safe by construction: no token rule can make the scanner backtrack catastrophically.

This is a deliberate fresh start under the same premises as REAL — purity, simplicity, and measured optimality. SciLex is a thin layer over REAL, not a re-implementation of pattern matching.

Scope (v1)

Included:

  • Token rules: a kind, a real::regex, and a skip flag (whitespace, comments).
  • Maximal-munch tokenization with rule-order priority on equal-length ties.
  • Source positions: byte offset, 1-based line and byte column.
  • Non-owning tokens: each lexeme views into the source.
  • Two ways to consume tokens: tokenize (eager, into a vector) and scan (a lazy single-pass range that produces one token at a time — no token vector is allocated; the parser-friendly access pattern).
  • Optional synthetic end-of-input token (eof_policy::append): emits a final end_of_input token at the real end position, so a parser always has a current token to match.
  • Optional indentation layout (scilex::layout, an opt-in header): rewrites a token stream with synthetic newline / indent / dedent tokens for indentation-significant languages; throws layout_error on an inconsistent dedent.
  • Lexical errors as exceptions carrying the failing position (lex_error).
  • Linear-time / ReDoS-safe tokenization, inherited from REAL.

Not in v1 (and honestly excluded — no phantom features):

  • Modes / context-sensitive lexing.
  • Compile-time static_lexer (built on REAL's static_regex).
  • Codepoint columns (columns are byte-based, matching REAL's UTF-8 model).
  • Command-line tool, JSON specification language.

These may come later, each only if it earns its place — measured, tested, and kept minimal.

Dependencies

SciLex is header-only and depends only on REAL's headers. By default the build looks for them in a sibling checkout:

~/Projects/
├── real-v1/      # REAL
└── scilex-v1/    # SciLex  (uses ../real-v1/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.6.6.

Build

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

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

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, 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/scilex/__init__.py, then commits, tags and pushes from a clean main. The pushed tag drives .github/workflows/release.yml, which checks the tag matches the version, builds abi3 wheels (cibuildwheel, Linux/macOS/Windows) and the self-contained sdist, builds the full API reference (Doxygen + the embedded coverage report + the guided tour), and publishes the distributions to PyPI via Trusted Publishing (OIDC, no stored secret). It then populates the GitHub /releases page with auto-generated notes, the wheels/sdist, and the API-reference tarball (scilex-doc.tar.gz). The pushed tag is the single thing that triggers a publish; SciLex remains consumable as source too (sibling checkout / FetchContent / get_include()).

A separate .github/workflows/docs.yml workflow also deploys the API reference to GitHub Pages on each push to main (enable Pages once with "GitHub Actions" as the source).

Note. The first release, v2026.6.0, shipped only the early binding (eager/lazy tokenize, eof, layout, positioned errors) with wheels + sdist. The first-byte dispatch, the error .context snippet, and the documentation artifact / Pages deployment arrived after and ship from v2026.6.1 onward.

One-time PyPI setup. Publishing needs a PyPI Trusted Publisher configured once for the project (publisher RECHE23/scilex, workflow release.yml, environment pypi) and a matching pypi GitHub environment — no API token is stored.

Python binding

SciLex ships an abi3 CPython extension (use the C++ lexer from Python). pip install scilex installs one cp310-abi3 wheel per platform (CPython 3.10+; the self-contained sdist compiles where no wheel matches, pulling REAL's headers from the real-regex build dependency). For a source checkout:

make python        # build the extension in place
make python-test   # run the binding test suite
import scilex
lx = scilex.Lexer([
    (0, r"\s+", True),                       # (kind, pattern, skip) — skipped
    (1, r"[0-9]+", False),                   # number
    (2, r"[A-Za-z_][A-Za-z0-9_]*", False),   # identifier
])

# Eager: a list of rich Token objects (kind, lexeme, structured position).
[(t.kind, t.lexeme) for t in lx.tokenize("foo 42")]      # [(2, 'foo'), (1, '42')]

# Lazy: a generator yielding one Token at a time — nothing else is held.
for tok in lx.scan("foo 42"):
    tok.kind, tok.lexeme, tok.position.line, tok.position.column

# A lexical error carries the failing position and a source-context snippet.
try:
    lx.tokenize("foo @")
except scilex.error as e:
    e.position                                            # Position(line=1, column=5, offset=4)
    e.context                                             # 'foo ‹@›'  (the offending byte, fenced)
    str(e)                                                # 'no rule matches at line 1, column 5: foo ‹@›'

# eof=True appends a terminal END_OF_INPUT token (a parser always has a token).
lx.tokenize("42", eof=True)[-1].kind == scilex.END_OF_INPUT

For indentation-significant languages, Layout rewrites an eof=True token stream with NEWLINE / INDENT / DEDENT tokens read from each line's leading column:

lx = scilex.Lexer([(0, r"\s+", True), (1, r"\w+", False), (2, r":", False)])
laid = scilex.Layout().apply(lx.tokenize("if x:\n    a\nb", eof=True))
[t.kind for t in laid]
# [1, 1, 2, NEWLINE, INDENT, 1, NEWLINE, DEDENT, 1, NEWLINE, END_OF_INPUT]

scilex.get_include() returns the header directory so a C++ project can compile against SciLex located through its Python install (add real.get_include() too, since SciLex's headers include REAL's).

Example

#include <scilex/scilex.hpp>
#include <vector>

enum kind { WS, KW_IF, ID, NUM, PLUS };

std::vector<scilex::rule> rules;
rules.push_back({WS,    real::regex("\\s+"),  true}); // skipped
rules.push_back({KW_IF, real::regex("if")});          // before ID: wins ties
rules.push_back({ID,    real::regex("[a-z]+")});
rules.push_back({NUM,   real::regex("[0-9]+")});
rules.push_back({PLUS,  real::regex("\\+")});

const scilex::lexer lexer(std::move(rules));

// Eager: all tokens in a vector.
for (const scilex::token& t : lexer.tokenize("if x + 42")) {
    // t.kind, t.lexeme, t.start.{offset,line,column}
}

// Lazy: one token at a time, nothing else materialized.
for (const scilex::token& t : lexer.scan("if x + 42")) {
    // ...
}

Design

A guided tour of how SciLex works — maximal munch, the REAL foundation, indentation layout, the C++/Python API, and the honest known limitations — lives in docs/design.dox, rendered into the API reference by make doc.

Performance

See BENCHMARKS.md for a reproducible, honest baseline (make bench). The short of it: on benign input Python's re is faster (a mature C backtracking engine), but SciLex is linear-time and ReDoS-safe by construction — on a pathological pattern like (a+)+b it stays flat (~78 µs at 1000 chars) where re explodes exponentially (seconds, then never finishing). SciLex trades raw throughput on easy inputs for a guarantee that holds on every input.

License

MIT — see LICENSE.

Author

René Chenard — rene.chenard@gmail.com

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

scilex-2026.6.1.tar.gz (33.5 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.6.1-cp310-abi3-win_amd64.whl (245.8 kB view details)

Uploaded CPython 3.10+Windows x86-64

scilex-2026.6.1-cp310-abi3-win32.whl (243.7 kB view details)

Uploaded CPython 3.10+Windows x86

scilex-2026.6.1-cp310-abi3-musllinux_1_2_x86_64.whl (1.5 MB view details)

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

scilex-2026.6.1-cp310-abi3-musllinux_1_2_aarch64.whl (1.4 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

scilex-2026.6.1-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (449.8 kB view details)

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

scilex-2026.6.1-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl (441.4 kB view details)

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

scilex-2026.6.1-cp310-abi3-macosx_11_0_x86_64.whl (58.2 kB view details)

Uploaded CPython 3.10+macOS 11.0+ x86-64

scilex-2026.6.1-cp310-abi3-macosx_11_0_arm64.whl (55.6 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

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

File metadata

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

File hashes

Hashes for scilex-2026.6.1.tar.gz
Algorithm Hash digest
SHA256 56e3968554a80d7e3c8bf2a0ce1af71c34295fffe8faec14a1c63127894c8abe
MD5 6d212eb6b5c166cfa58eb222862f9321
BLAKE2b-256 c599bb128a890fb82e2a9998ccbd2ee0f53356965afe840d3fe49bc44b14dded

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1.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.6.1-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: scilex-2026.6.1-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 245.8 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.6.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 2437d46299fba79ccd892bdb814df26010e2c5d77afeb63124662d8371412d4f
MD5 d2507c6489ba37565ff6e9271ed622e3
BLAKE2b-256 7e00e99e60054deab74b069891ec3ffb6fbfed957c6660dc122e0cabf850fc81

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1-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.6.1-cp310-abi3-win32.whl.

File metadata

  • Download URL: scilex-2026.6.1-cp310-abi3-win32.whl
  • Upload date:
  • Size: 243.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.6.1-cp310-abi3-win32.whl
Algorithm Hash digest
SHA256 19032a5caca24daeb2e2bcf57f3a4bbc221c0e0ed9e0637d40c48b1e42dda047
MD5 b84412cf01e53602bfe2cdb91602e489
BLAKE2b-256 de5710cc3bd17012c32f174cf493abe4e7d4d274906eddf3b7babe830484335e

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1-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.6.1-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for scilex-2026.6.1-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 c7f57b9ebc42325d0bb6bcd207cc48db639e9c6ef4e229743bd5c481146e93b8
MD5 42725d6c99969eba2c5dcdd097273e47
BLAKE2b-256 8ab90b4cc7d400f4825b458465e698a5ca589138ac49548bbaadcb1c5c794b05

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1-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.6.1-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for scilex-2026.6.1-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 7a53236c317d46d665f7e73a45b10ebb3584304077bf6ebef4c55e2925e04a6f
MD5 35f40a1d47be2fdb6e42f82e1870ae02
BLAKE2b-256 291d68c977eff56fe100af2646aec8cb6e623d242de96857594511b730cd008a

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1-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.6.1-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for scilex-2026.6.1-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e98bce157ac910c98c58d50929455797f985a46e2002f2c7c70ca14582a525e3
MD5 4e108de5a5ef26f5714572ecb86b7f77
BLAKE2b-256 338e31c277e29fd6086acc48a763809bdc9fbfe84481a23fb5bd5bbbbca4962b

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1-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.6.1-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for scilex-2026.6.1-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 89f623fdb44c67782bbb49bae53f77805e114cafe2e5d6bf7d2bf12ff198e60f
MD5 ed43478370eb7f3a4b0f472e47982819
BLAKE2b-256 b0a4b8a36459ec3d5fd22f508caafc6c5a39daf0bdf35460bc29168320830ac2

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1-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.6.1-cp310-abi3-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for scilex-2026.6.1-cp310-abi3-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 d7d43a0576566321a4362e6073ad040dcc782c4805557712649c4f27f88e40cb
MD5 6024911cbf800995b922df8c79c98f82
BLAKE2b-256 bf6be05c01e77246d67925698b486677668ba03995bd1d3566dfd1be36bd0d3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1-cp310-abi3-macosx_11_0_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.6.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for scilex-2026.6.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 968113ee98f03e9684907f91fcb7b21d245f7c3dd0a20b25f78b17abc4755520
MD5 dcca7805be303982db7c56afeb5bef0f
BLAKE2b-256 7c8ef2ba254f5c626f026c207d6e34d433f366c143f2faf8b1c3a5c534035811

See more details on using hashes here.

Provenance

The following attestation bundles were made for scilex-2026.6.1-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.

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