ENSIFER 0.1.20 ============ A small Model-T-style enfilade in C with an S7 Scheme FFI. The point of this package is to make the core mechanism usable without making the reader learn the whole Xanadu/Green vocabulary first. Start with [`doc/USAGE`](doc/USAGE) if you are new to the code. Read [`doc/ensifer.7`](doc/ensifer.7) for the conceptual overview, [`doc/enfilade.3`](doc/enfilade.3) for the C API, and [`doc/enfilade-s7.7`](doc/enfilade-s7.7) for Scheme. The tumbler primitive is documented separately in [`doc/tumbler.3`](doc/tumbler.3) and [`doc/tumbler-s7.7`](doc/tumbler-s7.7). THE IDEA -------- An enfilade is a tree indexed by tumbler fields. A tumbler is written as a dotted sequence of unsigned integers: 1.2.3 Putting: 1.2.3 -> datum creates the path automatically: root | +-- 1 | +-- 2 | +-- 3 -> datum A key may have both a datum and a child enfilade. Lookup returns the datum at that key first, then the contents of the child. That is the whole basic mechanism. QUICK START ----------- Configure and build the library, S7 module, and bundled REPL: ./configure make Start the REPL; the enfilade functions are initialized automatically: ./build/bin/logan Install under `/usr/local` (or set another prefix with `./configure --prefix=/path`): make install Stage an install for packaging or inspection with `DESTDIR`: make install DESTDIR=/tmp/ensifer-stage Run the regression suite with `make check`. `make clean` removes generated build files; `make distclean` also removes configure output. To load the shared module into another S7 host: (load "./build/lib/enfilade.so" (inlet 'init_func 'init_enfilade_s7)) RANGE ----- ----- `enfilade-get-range` is deliberately unchanged from the existing interface. It ranges over the numeric keys at the current enfilade level: (enfilade-get-range e 1 3) The bounds are inclusive. A negative end means "through the end". This is not a dotted-tumbler interval expression. TUMBLERS -------- The C layer now has a small standalone tumbler module. The public primitive is: #include "tumbler.h" It supplies: tumbler_parse() tumbler_free() tumbler_to_string() tumbler_compare() tumbler_add() tumbler_subtract_strong() tumbler_subtract_weak() tumbler_subtract() The implementation uses `uint64_t` fields. It therefore has finite field width even though the historical Xanadu representation used arbitrary-precision Humbers. This is a deliberate boundary for the small reference build. Whole-tumbler comparison follows the infinitesimal-style ordering used by the small tumbler implementations derived from Udanax work. Missing fields are compared as zero, giving the familiar ordering: 1 < 1.1 < 1.1.1 < 1.2 < 2 Arithmetic implements Xanadu's non-commutative tumbler addition and the strong, weak, and generalized difference operations. See `doc/tumbler.3` for the rules and examples. C DATA LIFETIME --------------- `enfilade_t` never owns the datum pointer supplied to `put_enfilade()`. For ordinary C code: int value = 42; enfilade_t *e = create_enfilade(); put_enfilade(e, "1.2.3", &value); /* use e while value remains alive */ destroy_enfilade(e); /* value may now go out of scope */ The enfilade frees its own index nodes, never the datum pointed to by an entry. The caller therefore controls datum lifetime. For S7, the datum is an S7 object. The c-object mark function walks every stored datum and marks it, so Scheme objects remain reachable through the enfilade and are reclaimed normally after the enfilade itself is released. C API ----- Create and destroy: enfilade_t *create_enfilade(void); void destroy_enfilade(enfilade_t *enf); Store and remove: enf_result_t put_enfilade(enfilade_t *enf, const char *tumbler, void *datum); enf_result_t remove_enfilade(enfilade_t *enf, const char *tumbler); Lookup: void *getXtumbler_enfilade(enfilade_t *enf, const char *tumbler); enf_result_t getXtumbler_values_enfilade(enfilade_t *enf, const char *tumbler, void ***items, size_t *count); Range: enf_result_t getXrange_values_enfilade(enfilade_t *enf, int64_t start, int64_t end, void ***items, size_t *count); Traversal: void enfilade_visit_data(enfilade_t *enf, enfilade_datum_visitor visitor, void *context); The two array-returning functions allocate the array of datum pointers. The caller frees that array with `free()`. The datum pointers are not transferred or freed. `remove_enfilade()` removes only the datum at the exact final key. If that key also names a child enfilade, the child remains. Empty intermediate children are pruned. S7 FFI ------ The module defines the c-object type `` and these Scheme functions: make-enfilade enfilade-put-data enfilade-remove enfilade-get-by-tumbler enfilade-get-range tumbler-compare tumbler-add tumbler-subtract Load the shared object with: (load "./build/lib/enfilade.so" (inlet 'init_func 'init_enfilade_s7)) The module does not depend on S7's `s7_repl()` startup path or on `cload.scm`. `build/bin/logan` is a direct small REPL with the module initialized at startup. ERRORS ------ Malformed tumbler text and a missing intermediate child are reported by the S7 binding as `TumblerAddressException`. A valid tumbler whose final key has no datum or child returns `()`. `enfilade-remove` returns `#t` when a datum was removed and `#f` when the exact final datum was not present. Malformed addresses and allocation failures are errors. REFERENCE IMPLEMENTATION ------------------------- The immediate behavioral reference for the tree is: https://raw.githubusercontent.com/enkiv2/ds-lib/refs/heads/master/Enfilade.py The corresponding core operations are its `putValue`, `getByTumbler`, `get`, and `getRange` behavior. The C implementation deliberately keeps `range` as the existing numeric-key range operation. Its ordered merge visits a key shared by a datum and child once; the Python reference can visit that key twice because it combines the two key lists before sorting. WHAT THIS IS ------------ This is a small reference enfilade: a useful, inspectable building block for other structures that need a tumbler-addressed index. It is not the complete Udanax Green backend. The larger literature adds specialized enfilades and machinery for WIDs, DISPs, rearrangement, versioning, spans, virtual copies, persistent structures, and the Green storage model. Those are layers to build on top of this primitive, not requirements for using this one. For a ZigZag cell catalogue, the natural simple pattern is: cell ID (tumbler) | v enfilade | v cell pointer New cells can therefore be catalogued under newly allocated tumbler IDs without making the catalogue itself understand the cell's internal representation. FILES ----- inc/ public C and S7 headers src/ C implementation, bundled S7, and REPL sources test/ C regression tests and S7 smoke test scheme/ Scheme reference implementation and S7 support scripts doc/ usage guide and groff manual pages configure compiler and shared-library capability checks Makefile build, check, install, uninstall, and clean targets TESTS ----- Run all regression checks with: make check This builds and runs the pure C tests and the bundled S7 smoke test. HISTORICAL REFERENCES --------------------- * Xanadu Hypertext Documents, especially "Enfilade Theory" and "Tumblers and Humbers": https://sentido-labs.com/en/library/201904240732/Xanadu%20Hypertext%20Documents.html * Udanax Green / Xanalogical Structure overview: https://www.mprove.de/visionreality/media/xuDation.html * Ted Nelson and Udanax developer archive: https://www.xanadu.com.au/mail/udanax/ * Jeff Rush's `xanalogica.tumbler`, a Python implementation explicitly derived from Udanax Green's C source: https://pypi.org/project/xanalogica.tumbler/