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 if you are new to the code. Read
doc/ensifer.7 for the conceptual overview,
doc/enfilade.3 for the C API, and
doc/enfilade-s7.7 for Scheme. The tumbler primitive is
documented separately in doc/tumbler.3 and
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 <enfilade> 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/