Files

298 lines
8.4 KiB
Markdown
Raw Permalink Normal View History

2026-09-30 19:27:45 -04:00
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 `<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/