AGENTS.md
June 24, 2026 · View on GitHub
Working notes for agents modifying this repository. For the design read
ARCHITECTURE.md; for usage read README.md. This file covers the repo layout,
how to build and test, the invariants you must not break, and the traps that are
easy to fall into.
Repo map
chars.hh Table-driven constexpr char classification (is_*, hexdigit/hexdec, tolower/toupper, char_repr). Header. Verbatim from Xapiand.
test/test.cc Runnable smoke test: classification, hex helpers, case folding.
CMakeLists.txt INTERFACE library `char_classify` (+ alias char_classify::char_classify); CTest test `char_classify`.
LICENSE MIT, Copyright (c) 2015-2019 Dubalu LLC.
README.md What it is, install, usage.
ARCHITECTURE.md Internal design of the classification table, trade-offs.
Everything is header-only. There is no .cc to compile except the test. The
CMake target is a pure INTERFACE library that only adds the source dir to the
include path and requests cxx_std_20.
Build and run the test
cmake -B build && cmake --build build && ctest --test-dir build
Expected output ends with all char-classify tests passed, exit 0. The test
target is char_classify_test; the registered CTest name is char_classify.
Conventions
- C++20. The target requests
cxx_std_20to stay uniform with the sibling libraries; the header itself is older-standard-friendly, but don't drop the target below it. - Zero external dependencies. The only includes are the C++ standard library.
Do not reach for
<cctype>— it is locale-dependent and notconstexpr, which is exactly what this header exists to avoid. There is no optional seam here. - Filename is stable. The header keeps its original Xapiand name (
chars.hh) so a consumer that already#includes it just needs this repo on the include path. Don't rename it. - Tabs for indentation, double quotes in code, no em dashes in prose.
Load-bearing invariants
- The classification is table-driven and
constexpr.char_tab[256]packs a bitset of flags per byte; eachis_*(char)is a masked lookup.tolower/toupper/char_reprare driven by their own 256-entry tables. All of it isconstexprso callers can classify at compile time — the test asserts most of it withstatic_assert. Don't introduce a runtime-only path. is_*returns anint, not abool. The predicates return the masked flag bits, so a true result is some non-zeroint, not necessarily1. This is Xapiand's original contract; callers compare to0when they need a strict boolean. Don't "clean it up" tobool— it would change the contract and the packed-flag idiom the table relies on.hexdigitreturns the nibble value,hexdecdecodes a pair.hexdigit('a') == 10;hexdec(&p)reads two hex chars, returns the byte, and advancesponly if the result is in range. Keep both.
How to extend
- Add a char predicate. Add a flag bit to
char_taband a thinconstexpraccessor that masks it; don't reach for<cctype>. - Always extend the smoke test.
test/test.ccis the only executable check. Preferstatic_assertfor theconstexprpredicates so a regression is a compile error, not a silent runtime pass.
Traps
- The high half of the table (bytes 0x80–0xFF) is mostly
IS_NON_HEXonly. Non-ASCII bytes carry few flags; don't assume a Latin-1 letter classifies as alpha. This header is ASCII-oriented by design. char_reprwrites lowercase hex and advances the write pointer by two. It takes achar**; make sure the buffer has room for the two chars.
Standalone vs. Xapiand
This is a standalone extraction from
Xapiand. chars.hh had zero dependencies
already, so it was copied verbatim — there is no decoupling delta. Keep it that
way; any change here should be reconcilable with upstream as a plain edit.