Skip to main content

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

Project description

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
  • 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, 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));

// 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),
])

# 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.

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.6.9.

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 normal input re is faster. On adversarial input SciLex stays linear while re explodes. See the benchmarks for details.

License

MIT — see LICENSE.

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

scilex-2026.6.5.tar.gz (51.6 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.5-cp310-abi3-win_amd64.whl (262.9 kB view details)

Uploaded CPython 3.10+Windows x86-64

scilex-2026.6.5-cp310-abi3-win32.whl (259.5 kB view details)

Uploaded CPython 3.10+Windows x86

scilex-2026.6.5-cp310-abi3-musllinux_1_2_x86_64.whl (1.7 MB view details)

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

scilex-2026.6.5-cp310-abi3-musllinux_1_2_aarch64.whl (1.6 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

scilex-2026.6.5-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (640.7 kB view details)

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

scilex-2026.6.5-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl (630.7 kB view details)

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

scilex-2026.6.5-cp310-abi3-macosx_11_0_x86_64.whl (76.6 kB view details)

Uploaded CPython 3.10+macOS 11.0+ x86-64

scilex-2026.6.5-cp310-abi3-macosx_11_0_arm64.whl (73.4 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

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

File metadata

  • Download URL: scilex-2026.6.5.tar.gz
  • Upload date:
  • Size: 51.6 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.5.tar.gz
Algorithm Hash digest
SHA256 d20633493d31d868e25af2db58e04e24b8bc15ce2f4f58f26797f42d7d6f3bb2
MD5 acfa5974d5f2ebcff022ac46b39d46f4
BLAKE2b-256 58a57698e7ec9d9ea5e3740a3411ed53f14e71f01a7cc50faee114ffa8433e55

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: scilex-2026.6.5-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 262.9 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.5-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 516edbbee6fd41fcd804f0a50be768abeb3645668c4432029bda71fd0692ea2d
MD5 50b5af63dbf7b5eab7ebecdd7be0c560
BLAKE2b-256 65a65c6bab3a7b74433fdd24b4556fed3ac56ff8da82344fd31497d31b069854

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: scilex-2026.6.5-cp310-abi3-win32.whl
  • Upload date:
  • Size: 259.5 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.5-cp310-abi3-win32.whl
Algorithm Hash digest
SHA256 7ed3e0a1f2693bbc4fd4fbf7020778c7ec363f52cb7d180c49dbf7efac133877
MD5 a5ab5f1ce2176ba24606252d6a7fbb9a
BLAKE2b-256 f31b6178e2817ce3b0a42e8180ab4f3f974e695029234c7bcba07740e392ad25

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for scilex-2026.6.5-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 5c3f402e497d44a04c84b1a66d9d2685a0ea9581a14a2d2a7711d2af7ab55a96
MD5 57795e1adf9201e1efd8693ce4ee9dab
BLAKE2b-256 c4a09faa022958367799a1cb30aff26400922f7cc6e94c3b5da52ab2080d2c2e

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for scilex-2026.6.5-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 15e175e5ee65f8d5c715c7fbf629af31245f50789a69479c3544e4a37a0c59fb
MD5 f421203b400f3b4f06101fcd98f8c1e0
BLAKE2b-256 90e3aabb67fd2173f285b6a820f397e9b8410a2baabef09faa4bd3b36ce7fc4d

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for scilex-2026.6.5-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 133b364be86daf9cb2610ef0863b886db06f5195d7a27f1ce1371eb0778df0d3
MD5 4b714f903081ef8b2d29642702f406ba
BLAKE2b-256 c7ae20bb340fa15c5491063297d5a4f679149344b09e10c22c62ad3b0247ad57

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for scilex-2026.6.5-cp310-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 725f56db0295a5811105b2bf00ffafde45ca99ebab8a127f299c227e5ecd5a1f
MD5 7b922f0e65537fddb8b70e921c76cfa0
BLAKE2b-256 cfa2fb1bb6ce29996963c4ecc598dd1b246fac7d968f2692e32a48eea77e252b

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for scilex-2026.6.5-cp310-abi3-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 06c80cf450fbb840c92e98d81bfce00c9b61b20075dfeecf2a7ad616a45ad959
MD5 9ec0dcf0f7602fe6bf7fb05da54774b4
BLAKE2b-256 605fbe29b0a62ad8001430a7d30ad36d27b6b0e56ec173fd4c112cce533ccc56

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for scilex-2026.6.5-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 68ff7a364ff96a8ed5c6aee1714f60a4d93bfb5d735d6c8c407210c11f1ea3f2
MD5 b41b07538da9eed8b2fdb1d3a5cb7810
BLAKE2b-256 fde1fcc431f637683f3a876096de526a97fd252d38416e9a106e68756612fc41

See more details on using hashes here.

Provenance

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