Architecture
June 24, 2026 · View on GitHub
The internal design of char-classify: a single header holding table-driven,
constexpr character classification. For usage see README.md; for the repo map
and invariants see AGENTS.md.
Shape
One header, header-only, zero dependencies beyond the C++ standard library:
chars.hh a 256-entry classification table + constexpr accessors
The classification table
One constexpr unsigned int char_tab[256] packs a bitset of flags per byte:
IS_SPACE, IS_ALPHA, IS_UPPER, IS_DIGIT, IS_HEX_DIGIT, IS_ASCII,
IS_KEYWORD, and IS_NON_HEX, plus the low 16 bits carrying the hex nibble value
for hex digits. Each is_*(char) accessor is a constexpr masked lookup —
char_tab[(unsigned char)c] & IS_DIGIT, and so on. Because the predicates return
the masked flag bits, a true result is some non-zero int, not necessarily 1;
that is Xapiand's original contract, preserved here.
hexdigit returns the packed nibble value (hexdigit('a') == 10); hexdec
decodes two hex chars to a byte and advances the read pointer only when the result
is in range. is_keyword flags the byte set Xapiand treats as keyword characters
(-.:_0-9a-zA-Z(/\@<>=*[{").
Case folding and hex repr
Separate 256-entry tables drive tolower / toupper — full-byte case fold, no
locale — and char_repr, which writes the two-char lowercase hex of a byte
through a char** write pointer. Like the classifiers, these are constexpr, so
case folding and hex rendering can happen at compile time.
Why this shape
The whole reason this header exists is to do character classification without
<cctype>. <cctype> is locale-dependent and not constexpr, so it can't be
used in a static_assert or fold away at compile time. A single packed table and
a handful of masked lookups give the same answers, deterministically, in
constexpr context, with one cache-friendly table instead of a function call into
the C library. It is ASCII-oriented by design: the high half of the table carries
few flags, because the call sites that use it (lexing, hex decode, keyword
scanning) are ASCII. That focus is what keeps it small and fast.