The Machines · Runtime stack

seedisam

A reckoner seed: isam, whole-table words only — machines/seeds/seedisam.shoddy

the seedisam machine's icon

Summary

seedisam bridges isam, the machine for indexed tables. It has two entry points that register the same word names. A mill folds one or the other, never both, so a session learns one vocabulary.

This page used to say ISAMOPEN could not exist. The reason given was that a handle — the number a program holds to name an open file — must never reach the stack. A handle is a NUMBER, and UNDO restores the stack. So the number naming an open table would come back with it. That was one of two problems, and naming them apart is what let both be solved.

One place the types genuinely fight back. isam orders keys with a raw <, so the key type is fixed when the program is compiled. But the schema that says whether the key is a number or a string is data, read at run time. So both instantiations exist in the seed, held in one sum type (a value that is exactly one of a fixed set of shapes). Every word branches once on which one it holds. It reads like duplication. It is the language stating a fact.

Why It's Useful

Two entry points, not one generic one. isam.shoddy's keys must be NUMBERs or STRINGs. Shoddy has no way to ask a Cell which of the two it holds and hand back a value of the matching generic type. So a number-keyed table seeds through RckSeedIsamNum, and a string-keyed one through RckSeedIsamStr. Both register the same five words.

Records cross the bridge through a cellOf function the caller supplies, not through a new stack case. isam's record type is the caller's own Type, unknown here exactly as it is unknown to isam.shoddy itself. The usual shape to hand back is a Pair built field to field, as seedcsv's CSVPAIRS already does for a row.

User's Guide

Let db  = IsamOpen("customers.dat", "customers.idx", KeyOf)
Let st0 = RckSeedIsamNum(RckNew(), db, Fn(rec) => CutPair(Pair(CutNum(Id(rec)), CutStr(Name(rec)))))
RckEval(st0, "ISAMCOUNT")                 ' x: however many records the table holds
RckEval(st0, "100 200 ISAMRANGE")         ' x: the records with keys 100..200
RckEval(st0, "999 " & Chr(34) & "none" & Chr(34) & " ISAMGETOR")   ' x: "none" unless key 999 exists

Word Reference

The same five words for both key kinds; NUMBER-keyed via RckSeedIsamNum, STRING-keyed via RckSeedIsamStr

WordDescription
ISAMALL ( -- list )Every record in the table, key order.
ISAMCOUNT ( -- n )How many records the table holds.
ISAMHAS ( k -- flag )Whether the table has a record under key k.
ISAMRANGE ( lo hi -- list )The records with keys from lo to hi inclusive, key order.
ISAMGETOR ( k dflt -- rec )The record under key k, or dflt unchanged when there is none.

The session-opened half — RckSeedIsam

Every word takes the name the table was opened under. A record is a list of values in schema order, as for RECGET.

WordDescription
ISAMOPEN ( path schema key name -- )Open an indexed table and keep it open under that name. The schema is a list of { name kind } specs, and key names which field is the key. Makes the table if it is not there. CLOSE shuts it — one name, two files.
ISAMCOUNT ( name -- n )How many records the table holds.
ISAMFIELDS ( name -- list )The field names of that table's schema, in record order. The key is one of these.
ISAMHAS ( name k -- flag )Whether the table has a record under key k.
ISAMGETOR ( name k dflt -- rec )The record under key k, or dflt unchanged when there is none. There is no plain ISAMGET: IsamGet aborts on a missing key, and a bridged word may not end the session.
ISAMALL ( name -- list )Every record, in key order.
ISAMRANGE ( name lo hi -- list )Every record whose key lies from lo to hi, in key order.
ISAMINSERT ( name rec -- )Add rec. Refuses when that key is already there — ISAMUPDATE is the one that replaces.
ISAMUPDATE ( name rec -- )Replace the record with rec's key. Refuses when there is none.
ISAMDELETE ( name k -- )Remove the record under key k. Refuses when there is none.

Every one of those refusals is a pre-flight: a check made before the risky call. IsamInsert aborts on a duplicate key. IsamUpdate, IsamDelete and IsamGet abort on a missing one. So IsamHas is asked first each time, and that is the whole reason these words are reachable from a prompt at all. The key is checked against the schema's kind too: a string key handed to a number-keyed table would not fail, it would quietly look up record 0.

Who Uses It

UserHow
halifaxThe calculator's table words, through RckSeedIsam — a table the session opens for itself from a path, a schema and the name of its key.
sparkySparky folds it too, so a model calling eval reaches the same words halifax puts at a prompt.

A host with a table of its own seeds the other half directly. It calls RckSeedIsamNum or RckSeedIsamStr with a handle it has already opened. A mill folds one entry point or the other, never both: the word names are shared on purpose, so a session learns one vocabulary.

The Machines It Uses

MachineWhy
cuttleThe Cell type records cross the bridge as.
isamThe domain this seed bridges: IsamAll, IsamCount, IsamHas, IsamRange, IsamInsert, IsamUpdate, IsamDelete — and, in the session-opened half, IsamOpen.
reckonerRckReg, RckSeeding and the argument readers every registered word is built from — and the named-resource table, RckBind and RckResOf, that makes an open table reachable after UNDO.
seedrecioThe schema language, and its reader, writer and row checks. One definition of what { "who" "str" 12 } means, used by both record machines, so the two cannot drift apart.
seqList plumbing under the schema and key lookups.
strJoin, so a key naming no field is refused with the field names there actually are.