368 lines
6.9 KiB
Plaintext
368 lines
6.9 KiB
Plaintext
ENSIFER ENFILADE — BEGINNER'S GUIDE
|
|
===================================
|
|
|
|
This guide assumes you have never worked with an enfilade before.
|
|
|
|
The shortest description is:
|
|
|
|
A tumbler is an address.
|
|
An enfilade is the tree that stores things at those addresses.
|
|
|
|
For example:
|
|
|
|
1.2.3
|
|
|
|
is one tumbler address.
|
|
|
|
If we store:
|
|
|
|
1.2.3 -> 42
|
|
|
|
the enfilade creates this path automatically:
|
|
|
|
root
|
|
|
|
|
+-- 1
|
|
|
|
|
+-- 2
|
|
|
|
|
+-- 3 -> 42
|
|
|
|
You do not construct those intermediate nodes yourself.
|
|
|
|
1. BUILD AND RUN IT
|
|
-------------------
|
|
|
|
From the Ensifer directory, configure and build:
|
|
|
|
./configure
|
|
make
|
|
|
|
Then start the REPL:
|
|
|
|
./build/bin/logan
|
|
|
|
To load the module into a different S7 host:
|
|
|
|
(load "./build/lib/enfilade.so" (inlet 'init_func 'init_enfilade_s7))
|
|
|
|
2. CREATE ONE
|
|
-------------
|
|
|
|
(define e (make-enfilade))
|
|
|
|
`e` is now the enfilade.
|
|
|
|
3. PUT SOMETHING IN IT
|
|
----------------------
|
|
|
|
(enfilade-put-data e "1.2.3" 42)
|
|
|
|
There is now a datum at address `1.2.3`.
|
|
|
|
4. GET IT BACK
|
|
--------------
|
|
|
|
(enfilade-get-by-tumbler e "1.2.3")
|
|
|
|
Result:
|
|
|
|
(42)
|
|
|
|
The result is a Scheme list because a tumbler lookup can produce more than one
|
|
datum.
|
|
|
|
5. PUT MORE IN
|
|
--------------
|
|
|
|
(enfilade-put-data e "1.2.4" 43)
|
|
(enfilade-put-data e "1.3.1" 99)
|
|
|
|
Now:
|
|
|
|
(enfilade-get-by-tumbler e "1.2.3")
|
|
; => (42)
|
|
|
|
(enfilade-get-by-tumbler e "1.2")
|
|
; => (42 43)
|
|
|
|
The second lookup stops at key `2` inside the `1` branch and returns the data
|
|
at that key followed by the data beneath its child enfilade.
|
|
|
|
6. A KEY MAY HAVE BOTH A DATUM AND CHILDREN
|
|
--------------------------------------------
|
|
|
|
This is important.
|
|
|
|
(enfilade-put-data e "1.2" 99)
|
|
(enfilade-put-data e "1.2.5" 50)
|
|
|
|
Now key `1.2` has both:
|
|
|
|
datum = 99
|
|
child = 5 -> 50
|
|
|
|
So:
|
|
|
|
(enfilade-get-by-tumbler e "1.2")
|
|
; => (99 42 43 50)
|
|
|
|
The datum at the exact key comes first. The child is then flattened in numeric
|
|
key order.
|
|
|
|
7. REPLACE A DATUM
|
|
------------------
|
|
|
|
A final key contains one datum. Putting another datum at the same final key
|
|
replaces the old one:
|
|
|
|
(enfilade-put-data e "1.2.3" 500)
|
|
(enfilade-get-by-tumbler e "1.2.3")
|
|
; => (500)
|
|
|
|
8. REMOVE A DATUM
|
|
-----------------
|
|
|
|
(enfilade-remove e "1.2.3")
|
|
; => #t
|
|
|
|
That removes only the datum stored exactly at `1.2.3`.
|
|
|
|
It does not delete an unrelated child at that key.
|
|
|
|
Removing a nonexistent final datum returns `#f`:
|
|
|
|
(enfilade-remove e "1.2.3")
|
|
; => #f
|
|
|
|
If an intermediate child does not exist, the operation reports a
|
|
`TumblerAddressException` instead.
|
|
|
|
9. RANGE
|
|
--------
|
|
|
|
Keep this distinction in mind:
|
|
|
|
tumbler lookup = dotted address
|
|
range = numeric keys at one enfilade level
|
|
|
|
For example:
|
|
|
|
(enfilade-get-range e 1 3)
|
|
|
|
means “visit numeric keys 1, 2, and 3 at this level, inclusive, and flatten
|
|
their contents.”
|
|
|
|
A negative end means “through the end”:
|
|
|
|
(enfilade-get-range e 2 -1)
|
|
|
|
The `range` interface is intentionally kept exactly as the existing API.
|
|
|
|
10. TUMBLERS ARE MORE THAN STRINGS
|
|
-----------------------------------
|
|
|
|
The external notation is a string:
|
|
|
|
"1.2.3"
|
|
|
|
but the package now has a real C tumbler primitive underneath it.
|
|
|
|
The S7 side exposes the useful basic operations too.
|
|
|
|
Compare:
|
|
|
|
(tumbler-compare "1" "1.1")
|
|
; => -1
|
|
|
|
(tumbler-compare "1.1" "1.1.1")
|
|
; => -1
|
|
|
|
(tumbler-compare "1.2" "1.1.9")
|
|
; => 1
|
|
|
|
Add:
|
|
|
|
(tumbler-add "3" "2.16.3")
|
|
; => "5.16.3"
|
|
|
|
(tumbler-add "25.6.46.93" "0.0.3.1.21")
|
|
; => "25.6.49.1.21"
|
|
|
|
Subtract / difference:
|
|
|
|
(tumbler-subtract "1.4.3" "1.1.1")
|
|
; => "0.3.3"
|
|
|
|
These are the historical tumbler operations, not ordinary decimal-number
|
|
addition and subtraction.
|
|
|
|
11. WHY THIS MATTERS FOR ZIGZAG
|
|
-------------------------------
|
|
|
|
An enfilade does not need to understand what a datum means.
|
|
|
|
That is useful when the datum is a ZigZag cell pointer.
|
|
|
|
Conceptually:
|
|
|
|
cell ID
|
|
|
|
|
v
|
|
tumbler
|
|
|
|
|
v
|
|
enfilade
|
|
|
|
|
v
|
|
cell
|
|
|
|
The catalogue can therefore answer:
|
|
|
|
“What cell is stored at this ID?”
|
|
|
|
without embedding ZigZag cell structure into the indexing mechanism.
|
|
|
|
12. THE C VERSION
|
|
-----------------
|
|
|
|
Include:
|
|
|
|
#include "enfilade.h"
|
|
|
|
Create:
|
|
|
|
enfilade_t *e = create_enfilade();
|
|
|
|
Store a pointer:
|
|
|
|
int value = 42;
|
|
put_enfilade(e, "1.2.3", &value);
|
|
|
|
Use it while the pointed-to object is alive:
|
|
|
|
int *found = getXtumbler_enfilade(e, "1.2.3");
|
|
|
|
printf("%d\n", *found);
|
|
|
|
Destroy the index before destroying the object it points to:
|
|
|
|
destroy_enfilade(e);
|
|
|
|
The enfilade never frees `value`.
|
|
|
|
The important rule is therefore:
|
|
|
|
the enfilade owns its tree;
|
|
the caller owns its datum.
|
|
|
|
13. TUMBLER C API
|
|
-----------------
|
|
|
|
Include:
|
|
|
|
#include "tumbler.h"
|
|
|
|
Parse a tumbler string:
|
|
|
|
tumbler_t t = {0};
|
|
tumbler_result_t r = tumbler_parse("1.2.3", &t);
|
|
|
|
Use it:
|
|
|
|
int cmp = tumbler_compare(&t, &t);
|
|
|
|
Release it:
|
|
|
|
tumbler_free(&t);
|
|
|
|
Arithmetic produces another heap-owned tumbler whose storage the caller must
|
|
also release:
|
|
|
|
tumbler_t a = {0}, b = {0}, out = {0};
|
|
|
|
tumbler_parse("25.6.46.93", &a);
|
|
tumbler_parse("0.0.3.1.21", &b);
|
|
tumbler_add(&a, &b, &out);
|
|
|
|
tumbler_free(&a);
|
|
tumbler_free(&b);
|
|
tumbler_free(&out);
|
|
|
|
14. VALID TUMBLERS
|
|
------------------
|
|
|
|
The current C representation accepts one or more unsigned decimal fields:
|
|
|
|
1
|
|
1.2
|
|
1.2.3
|
|
0
|
|
1.0.3
|
|
100.0.7
|
|
|
|
Malformed examples:
|
|
|
|
.1
|
|
1.
|
|
1..2
|
|
1.a.2
|
|
-1.2
|
|
|
|
The zero field is legal. Historical Xanadu addressing uses zero fields as
|
|
delimiters in some larger address formats; this small enfilade does not assign
|
|
zero any additional structural meaning.
|
|
|
|
15. ERRORS
|
|
----------
|
|
|
|
Malformed tumbler text:
|
|
|
|
TumblerAddressException
|
|
|
|
Missing intermediate child during lookup/removal:
|
|
|
|
TumblerAddressException
|
|
|
|
Valid final key with no datum or descendants:
|
|
|
|
()
|
|
|
|
Allocation failure:
|
|
|
|
memory-error
|
|
|
|
16. WHAT YOU DO NOT NEED TO LEARN YET
|
|
--------------------------------------
|
|
|
|
You do not need CRUM, POOM, Granfilade, Spanfilade, WID, DISP, orgl, loaf, or
|
|
Green's virtual-memory machinery to use this object.
|
|
|
|
Those names describe the larger historical system and specialized data
|
|
structures built around enfilades. They are important when implementing those
|
|
higher layers, but they are not prerequisites for understanding this small
|
|
primitive.
|
|
|
|
The right mental model for this package is simply:
|
|
|
|
tumbler -> path through an enfilade -> datum(s)
|
|
|
|
17. REFERENCES
|
|
--------------
|
|
|
|
Immediate behavioral reference:
|
|
|
|
https://raw.githubusercontent.com/enkiv2/ds-lib/refs/heads/master/Enfilade.py
|
|
|
|
Historical Xanadu technical material:
|
|
|
|
https://sentido-labs.com/en/library/201904240732/Xanadu%20Hypertext%20Documents.html
|
|
|
|
Udanax Green / Xanalogical Structure:
|
|
|
|
https://www.mprove.de/visionreality/media/xuDation.html
|
|
|
|
Jeff Rush's tumbler implementation:
|
|
|
|
https://pypi.org/project/xanalogica.tumbler/
|