LEPTRIS

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.

allocationwhere it lives
node structs (element, text, comment, CDATA, PI, doctype)pool, allocated contiguously with content
node content stringspool, contiguous with the struct (cache locality)
attribute namespool hash table (interned; dedup across elements)
attribute valuespool, bypassing interning
DTD container + hash tablespool, DTD subsystem owned by the document
XPath intermediatespool, 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:

c
_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:

bash
cc -DLEPTRIS_FOR_BINDGEN -E src/include/leptris.h   # strips LEPTRIS_API

Validation 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

workflowtriggerwhat it does
test.ymlevery push/PRbuild; run the full spec suite
asan.ymlevery push/PRAddressSanitizer build; verify zero leaks and zero errors
fuzz-nightly.ymlnightly cronlibFuzzer 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

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.