feat(v4.0.0): the prompt -- QUIT, ABORT, ABORT" and ."

Layer 5 of the compiler capsule.  The host node is now started at QUIT and
left running: it prompts, reads a line from its console with QUERY,
interprets it, says " ok" or " ERROR", and goes round again.

- capsule/quit.v4: QUIT and ABORT (FORTH-79), ABORT" and (ABORT") as v3 has
  them, ." and (.") (FORTH-79).  Text is compiled as a counted string after
  the call; the run-time word returns to the cell after it.
- INTERPRET prints v3's messages before abandoning a line:
  UNKNOWN WORD: 'xxx' and xxx: compile-only.
- core.v4: CR SPACE COUNT TYPE, as DECOMPOSITION.md gives them.
- input.v4: WORD split so that (PARSE) can take text without skipping
  leading delimiters (." " is an empty string); EXPECT keeps its place in
  memory, so six values may wait on the stack while a line is typed.
- tests/test_host_quit.c: the node is fed characters and its output read;
  16 sessions are transcripts of the v3 binary.

QUIT stops the line from inside any word and says nothing; v3's goes on
with the line and cannot be compiled.  v3's compiled ABORT" crashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-04 11:49:44 -04:00
co-authored by Claude Opus 5.5
parent 806f876ee0
commit b5d644d498
8 changed files with 555 additions and 29 deletions
+15 -9
View File
@@ -570,9 +570,9 @@ width and print a flood of spaces.
| `SCAN` `SKIP` | CAP | `( baddr u c -- baddr' u' )`: `SCAN` stops at the first byte equal to the low byte of `c` (or at the end, with `u' = 0`); `SKIP` stops at the first byte not equal to it. See below. A negative count reads as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Each leaves its caller 5 data cells and 5 return entries. Clobber `A`. |
| `(S)` | CAP | Variable, 4 cells: `COMPARE`'s two lengths; `SEARCH`'s string 2 and the whole of string 1. |
| `BL` | IN | `32` — executed on the golden model (2026-10-04). |
| `EXPECT` `QUERY` | CC | Built on `KEY` (DEV); source in `v4/capsule/input.v4`. FORTH-79 (ruled 2026-10-04). `EXPECT ( baddr n -- )` stores characters from `baddr` upward until a new-line (taken, not stored) or until `n` have been received, then a zero; no action for `n <= 0`; a line longer than `n` leaves the rest to be read next. It also sets `SPAN`, which is not FORTH-79 but is v3's. `QUERY` is `TIB 80 EXPECT 0 >IN !`. v3's `EXPECT` was C's `fgets` and took `n-1` characters, and its `QUERY` took 1024. Executed on the golden model's host node (2026-10-04) against C for every size and line length. `EXPECT` leaves its caller 6 data cells and 4 return entries. |
| `EXPECT` `QUERY` | CC | Built on `KEY` (DEV); source in `v4/capsule/input.v4`. FORTH-79 (ruled 2026-10-04). `EXPECT ( baddr n -- )` stores characters from `baddr` upward until a new-line (taken, not stored) or until `n` have been received, then a zero; no action for `n <= 0`; a line longer than `n` leaves the rest to be read next. It also sets `SPAN`, which is not FORTH-79 but is v3's. `QUERY` is `TIB 80 EXPECT 0 >IN !`. v3's `EXPECT` was C's `fgets` and took `n-1` characters, and its `QUERY` took 1024. Executed on the golden model's host node (2026-10-04) against C for every size and line length. `EXPECT` is what the prompt reads every line with, on top of whatever the user has left on the stack, so it keeps where it is in memory: it leaves its caller 7 data cells and 7 return entries. A line of exactly 80 characters fills `QUERY`'s count before its new-line arrives, so the new-line is read as an empty line after it; a longer line is read as 80 characters and then the rest. |
| `SPAN` `TIB` `>IN` `SOURCE` | CC | Interpreter state on the host node. `TIB` is the buffer's byte address and `>IN` and `SPAN` their variables' word addresses, all in line; `SOURCE ( -- baddr u )` is `TIB` and `SPAN @`, a negative `SPAN` read as 0. Executed on the golden model's host node (2026-10-04). |
| `WORD` `ENCLOSE` | CC | Parser; source in `v4/capsule/input.v4`. `WORD ( c -- baddr )` is FORTH-79 (ruled 2026-10-04): characters are taken from `TIB` until the delimiter `c` or the end of the text, leading delimiters ignored, and stored as a counted string; the delimiter met (`c`, or a zero if the text ran out) is stored after them, uncounted; `>IN` is left just past it; with nothing left the count is 0. The count is a byte, so a word over 255 characters is cut to 255; the buffer is 257 bytes. v3's `WORD` skipped every delimiter after the word, always stored a zero, and cut at 62. `ENCLOSE ( baddr c -- baddr n1 n2 n3 )`, not a FORTH-79 word, works on a zero-terminated string as v3's. Executed on the golden model's host node (2026-10-04) against C, and `ENCLOSE` against values recorded from the v3 binary. `WORD` keeps what it works on in memory and has at most three cells of its own on the data stack: it leaves its caller 7 data cells and 6 return entries. |
| `WORD` `ENCLOSE` | CC | Parser; source in `v4/capsule/input.v4`. `WORD ( c -- baddr )` is FORTH-79 (ruled 2026-10-04): characters are taken from `TIB` until the delimiter `c` or the end of the text, leading delimiters ignored, and stored as a counted string; the delimiter met (`c`, or a zero if the text ran out) is stored after them, uncounted; `>IN` is left just past it; with nothing left the count is 0. The count is a byte, so a word over 255 characters is cut to 255; the buffer is 257 bytes. v3's `WORD` skipped every delimiter after the word, always stored a zero, and cut at 62. `ENCLOSE ( baddr c -- baddr n1 n2 n3 )`, not a FORTH-79 word, works on a zero-terminated string as v3's. Executed on the golden model's host node (2026-10-04) against C, and `ENCLOSE` against values recorded from the v3 binary. `WORD` keeps what it works on in memory and has at most three cells of its own on the data stack: it leaves its caller 7 data cells and 6 return entries. `(PARSE) ( c -- baddr )` is `WORD` without the skipping of leading delimiters: the text starts at `>IN` and may be empty. It is what `."` and `ABORT"` use, so that `." "` is an empty string. |
| `NUMBER` `CONVERT` | CC | FORTH-79, not v3 (ruled 2026-10-04); source in `v4/capsule/input.v4`. `CONVERT ( d1 baddr1 -- d2 baddr2 )` takes the characters from `baddr1 + 1` on as digits in the current `BASE`, in either case, accumulating each into the double after multiplying it by `BASE`, and stops at the first that is not one; the double is `( lo hi )`. v3's was base 10 only, started at `baddr1`, and took the double low cell on top. `NUMBER ( baddr -- d )` is the counted string as a signed double in the current `BASE`, with an optional leading minus; anything else gives 0 and sets `NODE-ERROR`. v3's returned `( n flag )` in base 10. Executed on the golden model's host node (2026-10-04) against C in eight bases, including a digit that carries out of the low cell. `NUMBER` leaves its caller 3 data cells and 2 return entries; the interpreter does not use it (it has a single-cell conversion of its own that needs far less of the stack). |
| `S"` `(s")` `[']` | CC | Compiler words. |
| `LITERAL` `[LITERAL]` (placeholders) | RET | The working `LITERAL` is in §5.17. |
@@ -653,7 +653,7 @@ width and print a flood of spaces.
| `CR` | CAP | `10 EMIT`, as `10 jump EMIT` — executed on the golden model (2026-10-03). Character 10, as v3; the console turns it into a new line. |
| `SPACE` | CAP | `BL EMIT`, as `32 jump EMIT` — executed on the golden model (2026-10-03). |
| `SPACES` | CAP | `( n -- )`: `-if L drop ; L: if DONE SPACE -1 + jump L DONE: drop ;` — `n` spaces, none for `n <= 0`, as v3. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 7 return entries. Clobbers `B`. |
| `."` `(do-string)` | CC | Compiler words. |
| `."` `(.")` | CC | FORTH-79; source in `v4/capsule/quit.v4`. `." text"` takes the text up to the next `"` (none at all is allowed; with no closing `"` it is the rest of the line). Interpreting, it prints it. Compiling, it lays a call to `(.")` and after it the text as a counted string — count byte, characters, four to a cell, the last cell filled with zeros. `(.")` finds the string by the return address its call left and returns to the cell after it: `pop 4* dup C@ L: if DONE push 1 + dup C@ EMIT pop -1 + jump L DONE: drop 4/ 1 + push ;`. It takes the return address off before it calls anything, so a word that prints text goes no deeper than one that calls any other word. v3's `(do-string)` has no counterpart. Executed on the golden model's host node (2026-10-04), every length from 0 to 12 and 76, including transcripts of the v3 binary. |
### 5.11 Blocks and mass storage
@@ -685,7 +685,7 @@ The storage service belongs to the Artemis role, now a device node.
| `'` | CC | `( -- addr )`, FORTH-79: the parameter field address of the next word in the input stream; not found is an error (0, and `NODE-ERROR` set). v3's `'` returned what `FIND` returns. For a word that is code the two are the same address; for a data word the parameter field is the cell after its code, which is what FORTH-79's `n ' NAME !` needs. It is immediate: inside a definition the address is compiled as a literal (FORTH-79), where v3's `'` parsed when the definition ran. Executed on the golden model's host node (2026-10-04). |
| `>LINK` `LFA` `LINK>` `>NAME` `NFA` `NAME>` `CFA` `PFA` `>BODY` `TRAVERSE` | CC | The fields of an entry, all reached from its xt, as in v3 where the xt was the entry: `>LINK`/`LFA` the link's address, `LINK>` the older entry's xt, `>NAME`/`NFA` the byte address of the counted name, `NAME>` back to the xt, `CFA` the xt itself, `PFA`/`>BODY` the parameter field, `TRAVERSE` from a name's count byte to past its last character (for `n > 0`; otherwise unchanged, as v3). Executed on the golden model's host node (2026-10-04). |
| `SMUDGE` `HIDDEN` | CC | `SMUDGE` toggles the hidden flag of the newest entry; `HIDDEN` sets it. v3 refuses both outside compilation; that check comes with the compiler words. Executed on the golden model's host node (2026-10-04). |
| `INTERPRET` | CC | FORTH-79; source in `v4/capsule/compile.v4`. Takes words from the input stream until it is exhausted: a word in the dictionary is executed, or, when compiling and not immediate, compiled; anything else must be a number in the current `BASE` (a single cell, with an optional leading minus), left on the stack or compiled as a literal. What is neither, or a compile-only word met while interpreting, abandons the line: compiling stops, the definition under way stays hidden, the rest of the input is skipped and `NODE-ERROR` is set (the message and the stack reset come with `QUIT`). Executed on the golden model's host node (2026-10-04). |
| `INTERPRET` | CC | FORTH-79; source in `v4/capsule/compile.v4`. Takes words from the input stream until it is exhausted: a word in the dictionary is executed, or, when compiling and not immediate, compiled; anything else must be a number in the current `BASE` (a single cell, with an optional leading minus), left on the stack or compiled as a literal. What is neither, or a compile-only word met while interpreting, abandons the line: compiling stops, the definition under way stays hidden, the rest of the input is skipped and `NODE-ERROR` is set, after v3's message: `UNKNOWN WORD: 'xxx'` or `xxx: compile-only`. `INTERPRET` then returns to `QUIT`, which prints ` ERROR`. Executed on the golden model's host node (2026-10-04). |
An entry, as `v4/capsule/dict.v4` builds it. A word's execution address (xt) is the address of its
code, and everything else is found from it:
@@ -715,7 +715,10 @@ it, `xt + 1`; for any other word the parameter field is the code itself.
| `(` `\` | CC | `(` is `41 WORD drop`; `\` is `SPAN @ >IN !`. In `v4/capsule/input.v4` under the names `PAREN` and `BACKSLASH`. Executed on the golden model's host node (2026-10-04). |
| `EXECUTE` | CAP | `push ;` (tail-jumps to the xt; the xt returns to `EXECUTE`'s caller). Executed on the golden model's host node (2026-10-04). |
| `NOP` | OP | `nop` |
| `QUIT` `ABORT` `ABORT"` `(ABORT")` `COLD` `WARM` | CC | |
| `QUIT` | CC | FORTH-79; source in `v4/capsule/quit.v4`. Clears the return stack, sets execution mode, returns control to the terminal; no message. It is the prompt: after a new line it prints `ok> `, reads a line with `QUERY`, clears `NODE-ERROR`, runs `INTERPRET`, prints ` ok` — or, if `NODE-ERROR` is set, ends any definition that was open and prints ` ERROR` — and goes round again, for as long as the node runs. The text is v3's. The line is not sent back; the terminal shows what is typed. The return stack is circular (D-2) and has no bottom to reset to: `QUIT` clears it by jumping into the loop and never returning, from however deep. The data stack is left alone. A definition open when `QUIT` runs is ended and stays hidden. Executed on the golden model's host node (2026-10-04): the node is started at `QUIT`, fed characters and its output read, including 16 sessions recorded from the v3 binary. v3's `QUIT` goes on with the rest of the line and cannot be compiled into a definition. |
| `ABORT` | CC | FORTH-79; source in `v4/capsule/quit.v4`. As `QUIT`, and the line it stops ends with ` ok`, as v3. The data stack is circular (D-2): there is nothing to clear, and what was on it is still there. Executed on the golden model's host node (2026-10-04), from six calls down and from inside two loops, forty times over. |
| `ABORT"` `(ABORT")` | CC | Not FORTH-79 (it is FORTH-83's); kept because v3 has it. `ABORT" text"` `( flag -- )`: if the flag is not zero, print the text, start a new line and `ABORT`. Compiled as `."` is, with `(ABORT")` as the run-time word; it also works at the prompt. Executed on the golden model's host node (2026-10-04). v3's crashes (SIGSEGV) when a word compiled with it runs, and at the prompt prints the text and carries on with the line. |
| `COLD` `WARM` | CC | |
| `BYE` `REBOOT` | HERA | |
| `SAVE-SYSTEM` | HERA | Snapshot becomes a capsule-image request. |
| `WORDS` `VLIST` `SEE` | CC | |
@@ -752,13 +755,16 @@ built into the capsule:
- Every word of the capsule keeps what it works on in memory and has at most three cells of its own on
the data stack. Measured on the golden model: a line may have **6 values on the stack** while the
interpreter reads its next word, and defining and running a word leaves 4 cells under it untouched.
interpreter reads its next word, 6 may wait there while the next line is typed, and defining and
running a word leaves 4 cells under it untouched.
- The capsule's words call each other as little as they can: byte access shifts without a loop,
opcodes are shifted into the word being built instead of placed by a counted shift, and the long
jobs (laying a loop's end, making a data word) are single words reached by a jump. Measured on the
golden model: a word run from the interpreter has **6 return entries** to itself, every kind of
line leaves at least 3 spare while it is compiled, and a defining word (`CREATE … DOES>`) works
from the prompt, from a word, and from a word that calls that. A `DO` loop takes two return
golden model: the prompt has one return entry under each line, its call of `INTERPRET`, so words
run from the prompt may call each other **8 deep**; a line that runs a word which calls nothing
leaves 5 of the nine entries untouched; every kind of line leaves at least 3 spare while it is
compiled, and a defining word (`CREATE … DOES>`) works from the prompt, from a word, and from a
word that calls that. A `DO` loop takes two return
entries. `*` is therefore not `UM* drop` on the host node but N steps of `+*` with
only the count on the return stack; it gives the same low cell (what `+*` loses at the top of `T`
takes N shifts to reach the bit that moves into `A`, and the loop ends first).