Internals & FFI
How the promises are kept: a pool that owns everything, pointers that fit in int32, an ABI pinned by static asserts, and a binding contract every mirror is gated against.
Memory model — the pool
Every byte the parser allocates that ends up referenced by a document lives in the document’s pool. leptris_document_freedestroys the pool and releases everything in one call.
| allocation | where it lives |
|---|---|
| node structs (element, text, comment, CDATA, PI, doctype) | pool, allocated contiguously with content |
| node content strings | pool, contiguous with the struct (cache locality) |
| attribute names | pool hash table (interned; dedup across elements) |
| attribute values | pool, bypassing interning |
| DTD container + hash tables | pool, DTD subsystem owned by the document |
| XPath intermediates | pool, freed at result destruction |
The parse arena is one contiguous allocation, pre-sized from the document itself; element, attribute, text, and markup blocks are carved from it and the arena is retained for reuse across documents. Elements are 64 B, attributes 40 B — one cache line each — with zero-copy text views into the input buffer.
Tree edges are stored as int32_t byte offsets(compact pointers) with an overflow-table fallback for macOS ASLR. A recursion depth guard rejects deeply nested input with a parse error rather than crashing, and per-document strict mode lets strict and lenient parsing coexist in one thread.
ABI stability
All public types are opaque pointer typedefs — enforced at compile time:
_Static_assert(sizeof(LeptrisDocument) == sizeof(void*), "...");Enum values that bindings hard-code are pinned by the HeaderHygiene test (ctest -R HeaderHygiene). Node kinds are exposed through the public LeptrisNodeKind enum — bindings no longer hard-code numeric node types.
The FFI contract
- Opaque handles. Callers never see struct fields — only accessor functions.
- Status via output parameter. The return value is the primary result.
- Documented ownership. Every function returning a string or handle carries a
Memory:comment — the contract every binding builds on. - No exceptions across the boundary. Errors are status codes; no
longjmp. - No C-isms. No varargs, no platform attribute macros in the parsed surface.
Binding generators parse the headers in bindgen mode:
cc -DLEPTRIS_FOR_BINDGEN -E src/include/leptris.h # strips LEPTRIS_APIValidation and conformance
- XML 1.0 with namespaces; first-declaration-wins for redeclared DTD names; internal subsets take precedence over external ones.
- DTD validation — parameter entities, INCLUDE/IGNORE conditional sections, ENTITY/ENTITIES unparsed-entity checking, content-model memoization, and external subsets via application-supplied I/O (
leptris_document_get_dtd+leptris_dtd_parse_external_subset). Phases 1–7 of the validation ladder. - XInclude 1.0 with ownership-transfer splice — included documents are moved, not deep-copied; cycle detection via ancestor-URI tracking.
- C14N — Canonical XML 1.0, 1.1, and Exclusive, for digital signatures and hashing.
- XPath 1.0 — 438/438 on the W3C suite; see the XPath page.
Semantics are pinned by 586 tests plus large-document and allocation-failure suites.scripts/validate.sh in the repo runs the full battery;VALIDATION.md lists every command.
Parse modes — whitespace semantics
By default leptris keeps whitespace-only text nodes — the faithful-DOM behavior of libxml2/Nokogiri, and the only mode that round-trips pretty-printed XML byte-for-byte. For apples-to-apples comparisons against pugixml (which discards them by default), useleptris_parse_string_flags(..., LEPTRIS_PARSE_DROP_WS_TEXT, ...)— the equivalent of libxml2’s XML_PARSE_NOBLANKS.
Continuous integration
| workflow | trigger | what it does |
|---|---|---|
test.yml | every push/PR | build; run the full spec suite |
asan.yml | every push/PR | AddressSanitizer build; verify zero leaks and zero errors |
fuzz-nightly.yml | nightly cron | libFuzzer for 5 minutes; reports crashes |
Benchmark CI uploads JSON + Markdown results per push, and the perf ledger records measured dead ends alongside wins — see thebenchmarks section for headline numbers and the honest floors.
Where next
- FFI design document — the full contract.
- Gem architecture — the Ruby mirror in detail.
- Validation commands — reproduce every claim.
This is the curated guide. The repositories are canonical: when this page and the repo disagree, the repo wins. Full documentation lives with the source.