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

Description
Model-T Enfilade in C & (s7) Scheme
Readme AGPL-3.0 980 KiB
Languages
C 96.2%
Scheme 3.7%