The Machines · Runtime stack

seedrecio

A reckoner seed: recio, over a resource held by name — machines/seeds/seedrecio.shoddy

the seedrecio machine's icon

Summary

seedrecio is the first seed that opens something. (A seed is the file that bridges one machine's words into the calculator.) Every other seed in the directory takes values off the stack and puts values back. This one opens a file, keeps it open across lines, and shuts it when told: RECOPEN RECCOUNT RECFIELDS RECGET RECALL RECPUT RECAPPEND.

That was impossible until reckoner grew a table of named resources. A handle — the number the runtime uses to name an open file — must never reach the stack. UNDO restores the stack, and the handle would go with it. A name cannot be dropped, overwritten or undone away. So RECOPEN binds one, and nothing ever pushes the handle.

What a session holds is therefore a string. "sales" RECCOUNT asks the file bound under "sales" how many records it has. CLOSE and BOUND are reckoner's own words and work here without this seed saying anything about them. There is deliberately no RECCLOSE: one way to shut a resource is the whole reason the table is uniform.

Why It's Useful

The layout problem, and the shape of the answer. recio asks for a record size and a reader/writer pair over the caller's own Type:

Def rdstudent(f As Number) As Student
    Student(GetStr(f, 12), GetNum(f))

A host program writes those and compiles them in. A session cannot. There is no Type at a prompt, and no way to write a Def there that the compiler has already seen. So this seed supplies one generic reader and one generic writer. It drives them from a schema the session types — a list of field specs describing each field's name and kind:

{ { "id" "number" } { "name" "str" 12 } { "paid" "bool" } }

Three kinds, because three are what the runtime transfers. A number is eight bytes. A boolean — a True or False — is one byte. A string is a fixed zero-padded field whose width the spec carries. The record size is the sum, computed once at RECOPEN and kept with the binding. So no later word can disagree with the file about it.

A record is a plain list, in schema order — { 3 "GRACE" True } — and not a dict. A dict — a list of name-and-value pairs — would be truer to the schema and worse to type. A session that has to build { ("id" 3) ("name" "GRACE") } to write one row will not write many. The names live in the schema, and RECFIELDS answers them. A session that wants a dict has seeddict's own words to make one.

Every read is checked before it happens. GetNum, GetBool and GetStr all abort past the end of the file, and a bridged word must not be able to abort the session. So no read happens until the record number has been checked against the count. A file whose length is not a whole number of records is refused at RECOPEN, not at the first read. That mismatch is a fact about the file and the schema together. RECOPEN is the moment they first meet — the earliest honest place to say it.

A row is checked before any of it is written. A record half-written is a record corrupted. And PutStr aborts on a string longer than its field rather than cutting it short. So the whole row is validated against the schema first: field count, kind, and string width, with each refusal naming which field. The transfer only starts once nothing can refuse.

RECPUT may extend by exactly one. Seek can go anywhere, so writing record 900 of a three-record file would leave a hole of undefined bytes. That hole reads back as garbage rather than as an error. One past the end is an append — it adds a record at the end. Anything further is refused, and the refusal names the furthest write allowed.

User's Guide

> "sales.dat" { { "id" "number" } { "who" "str" 12 } { "paid" "bool" } } "db" RECOPEN
ok: opened db
[ empty ]

> "db" { 1 "GRACE" True } RECAPPEND
x: 1

> DROP "db" { 2 "ADA" False } RECAPPEND DROP
[ empty ]

> "db" RECCOUNT
x: 2

> "db" 1 RECGET
x: { 1 "GRACE" True }

> "db" RECALL
x: { { 1 "GRACE" True } { 2 "ADA" False } }

> BOUND
x: { ( "db" "recio" ) }

> "db" CLOSE
ok: closed db
[ empty ]

The file survives UNDO. Wind the stack all the way back and "db" RECCOUNT still answers — and still counts the records that were written. UNDO restores a stack; it does not un-write a file.

Word Reference

WordDescription
RECOPEN ( path schema name -- )Open a binary record file and keep it open under that name. The schema is a list of { name kind } specs, kind being "number" or "bool", or "str" with a width. Makes the file if it is not there. CLOSE shuts it; BOUND lists what is open.
RECCOUNT ( name -- n )How many records the file bound under that name holds.
RECFIELDS ( name -- list )The field names of that file's schema, in the order a record lists them.
RECGET ( name k -- rec )Record k, as a list of values in schema order. Records number from 1.
RECALL ( name -- list )Every record in file order, each one a list of values in schema order.
RECPUT ( name k rec -- )Write rec as record k, replacing what was there. k may be one past the end, which adds a record; anything beyond that is refused.
RECAPPEND ( name rec -- k )Add rec to the end and push the record number it became.

Who Uses It

UserHow
halifaxThe calculator's record-file words — the first resource a halifax session can open, write to and close.
seedisamThe schema language and its reader, writer and row checks — so an indexed table and a flat record file agree about what a field spec means.
sparkySparky folds it too, so a model calling eval reaches the same words halifax puts at a prompt.

A mill claims this seed by folding RckSeedRecio over its reckoner state, which is all halifax does.

The Machines It Uses

MachineWhy
cuttleThe Cell type a record's values cross the bridge as.
recioThe domain this seed bridges: RecSeek and the record offset arithmetic, driven by a schema instead of a compiled reader and writer.
reckonerRckReg and the argument readers, and the named-resource table — RckBind, RckBoundTo, RckResOf — that makes an open file reachable after UNDO.
seqList plumbing under the schema reader and the row checks.