feat(v4.0.0): the host node's stacks are 32 deep (D-17)

With the stacks counted and guarded (D-16) their size is a parameter of
the node.  The host node, which runs the interpreter and the compiler
under the user's programme, gets 32 values and 32 return entries; a mesh
node keeps the F18's 10 and 9.  Nothing else about the mechanism or any
word's definition changes.

- stack.h: V4_DATA_RING and V4_RET_RING are build parameters; the
  Makefile sets them for the host-node tests.
- tests/test_host_quit.c no longer assumes a size: it fills the stacks to
  whatever they are, and takes every exit with the return stack full.
- Measured on the host node now: 28 values on a line, 29 waiting between
  lines, words 31 deep from the prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-04 18:29:10 -04:00
co-authored by Claude Opus 5.5
parent 0da7e32a0b
commit ebffa6082d
8 changed files with 133 additions and 58 deletions
+15 -12
View File
@@ -177,7 +177,8 @@ definition below depends on one, it says so.
| **D-13** | Pictured-output hold buffer: size and errors (ruled 2026-10-03). | **63 characters, as v3; on error set `NODE-ERROR` and drop the character.** The buffer holds 63 characters at either cell width. `HOLD` of a value outside 0–255, or into a full buffer, stores nothing and sets `NODE-ERROR` (§7), which is v3's behaviour (it set its error flag and dropped the character). A full double in base 2 therefore does not fit, as in v3. |
| **D-14** | An address outside the node's memory (ruled 2026-10-04). | **Guarded: an address fault.** Every address a running programme uses is checked before it is used — `P` when an instruction word or an `@p` literal is fetched or `!p` stores, `A` for `@ @+ ! !+`, `B` for `@b !b`. Outside `0 … memory size − 1` the opcode does nothing (no fetch, no store, `A` and `B` untouched), the rest of its instruction word is not executed, both stacks are emptied (D-16), and `P` becomes the node's **fault handler** for that kind of fault. The handler does not return to the programme. On the host node the handler is `(FAULT)` (`v4/capsule/quit.v4`): it prints `Address out of range`, ends any definition that was open, prints ` ERROR` and returns to the prompt, from however deep the fault was. A node with no handler stops. v3 printed ` ERROR` alone. What a mesh node's handler does — it has no console — comes with the mesh (step 2). Executed on the golden model (2026-10-04): `tests/test_exec.c` for every memory opcode and for `P`, `tests/test_host_quit.c` from the prompt. |
| **D-15** | Division by zero in `/` `MOD` `/MOD` `*/` `*/MOD` `M/MOD` (ruled 2026-10-04). | **Guarded: the word reports it and the line ends.** Each of these words tests its divisor before anything else. If it is zero the word prints v3's message, its own name and `: Division by zero`, takes its operands off the stack as v3 does, ends any definition that was open, prints ` ERROR` and returns to the prompt, from however deep; its caller is not returned to. `M/MOD` says so too, where v3 printed ` ERROR` alone. Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04), including transcripts of the v3 binary. `Q./` keeps D-11 (saturate and flag). `UM/MOD` and `SM/REM` are internal and unguarded: their callers have checked. What a mesh node, with no console, does on a zero divisor comes with the mesh (step 2); §4's definitions, which the mesh-node tests execute, still leave it unspecified. |
| **D-16** | Stack overflow and underflow; `DEPTH`, `PICK`, `ROLL` (ruled 2026-10-04; revises D-2). | **Guarded: a stack fault.** Each stack counts what it holds. Before every opcode the node checks that the stacks hold what the opcode takes — including a `T` or `S` it only reads — and have room for what it leaves. If not, the opcode does nothing and the node faults exactly as for a bad address (D-14), to that kind's handler: `Stack overflow`, `Stack underflow`, `Return stack overflow`, `Return stack underflow`, then ` ERROR` and the prompt. **Every fault, D-14's included, empties both stacks**: the handler does not return, and what a word stopped part-way has left on the data stack is of no use to its caller. (v3 keeps what the failing word had not taken, and names the word: `DROP: Stack underflow`.) The fault handler is a table of five words, one per kind, each a jump (`(FAULTS)` in `v4/capsule/quit.v4`). Two registers (§7) are all a programme sees of the stacks: `DSTACK-DEPTH` and `RSTACK-DEPTH` read as the depth, and a store to one empties that stack. There is still no stack pointer and no address for a stack cell, so `SP@` and `SP!` stay retired; `.S` waits for number output to reach the capsule. The sizes are unchanged, 10 and 9: one more value, or one more level of call, is now an error message where it used to be silent corruption. Executed on the golden model (2026-10-04): `tests/test_exec.c` for every opcode at every depth of both stacks, `tests/test_host_quit.c` from the prompt. |
| **D-16** | Stack overflow and underflow; `DEPTH`, `PICK`, `ROLL` (ruled 2026-10-04; revises D-2). | **Guarded: a stack fault.** Each stack counts what it holds. Before every opcode the node checks that the stacks hold what the opcode takes — including a `T` or `S` it only reads — and have room for what it leaves. If not, the opcode does nothing and the node faults exactly as for a bad address (D-14), to that kind's handler: `Stack overflow`, `Stack underflow`, `Return stack overflow`, `Return stack underflow`, then ` ERROR` and the prompt. **Every fault, D-14's included, empties both stacks**: the handler does not return, and what a word stopped part-way has left on the data stack is of no use to its caller. (v3 keeps what the failing word had not taken, and names the word: `DROP: Stack underflow`.) The fault handler is a table of five words, one per kind, each a jump (`(FAULTS)` in `v4/capsule/quit.v4`). Two registers (§7) are all a programme sees of the stacks: `DSTACK-DEPTH` and `RSTACK-DEPTH` read as the depth, and a store to one empties that stack. There is still no stack pointer and no address for a stack cell, so `SP@` and `SP!` stay retired; `.S` waits for number output to reach the capsule. The sizes were unchanged by this ruling, 10 and 9 (D-17 then deepens the host node's): one more value, or one more level of call, is now an error message where it used to be silent corruption. Executed on the golden model (2026-10-04): `tests/test_exec.c` for every opcode at every depth of both stacks, `tests/test_host_quit.c` from the prompt. |
| **D-17** | Stack sizes on the host node (2026-10-04, following D-16). | **32 values and 32 return entries on the host node; a mesh node keeps the F18's 10 and 9.** Once the stacks are counted (D-16) their size is a parameter of the node, like its memory, and the host node is the one that runs the interpreter and the compiler underneath the user's programme: at 10 and 9 the prompt left a programme about six values, and `/` could be used only four words deep. The mechanism is the same at both sizes — top registers over a ring — and so is every word's definition. v3's stacks are deeper still. In the golden model the sizes are `V4_DATA_RING` and `V4_RET_RING` (`stack.h`), set for the host-node tests in `v4/Makefile`. |
**Consequences of D-2 that every definition must respect.** The data stack holds 10 items and the
return stack 9, and every `call`, `FOR`, `DO` loop frame and `push` uses return-stack slots. Nesting
@@ -314,7 +315,7 @@ Section numbers match the v3 primitive reference.
| `ROT` | CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 4 return entries; in line, with `SWAP` in line, it calls nothing. |
| `-ROT` | CAP | `SWAP push SWAP pop`, `SWAP` in line (`ROT ROT` in one pass). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 5 return entries. |
| `DEPTH` | CAP | FORTH-79: how many values were on the stack before `DEPTH`. `DSTACK-DEPTH b! @b` (D-16). Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04), including a transcript of the v3 binary. |
| `PICK` | CAP | FORTH-79 (D-7): `( n -- x )`, a copy of the n-th value counting from one; `1 PICK` is `DUP`, `2 PICK` is `OVER`. The stack has no addresses, so the `n - 1` values above the one wanted are set aside in memory and put back. `n < 1` prints `PICK: Invalid index`; `n` greater than the depth is a stack underflow fault. It works with the stack full (nine values and `n`). v3's `PICK` counts from zero. Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04). |
| `PICK` | CAP | FORTH-79 (D-7): `( n -- x )`, a copy of the n-th value counting from one; `1 PICK` is `DUP`, `2 PICK` is `OVER`. The stack has no addresses, so the `n - 1` values above the one wanted are set aside in memory and put back. `n < 1` prints `PICK: Invalid index`; `n` greater than the depth is a stack underflow fault. It works with the stack full (every cell but one holding a value, and `n`). v3's `PICK` counts from zero. Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04). |
| `ROLL` | CAP | FORTH-79 (D-7): `( n -- )`, the n-th value counting from one is taken out and put on top; `3 ROLL` is `ROT`, `1 ROLL` does nothing. Built as `PICK` is, with the same errors. v3's `ROLL` counts from zero. Source in `v4/capsule/forth.v4`; executed on the golden model's host node (2026-10-04). |
### 5.2 Return stack
@@ -383,7 +384,7 @@ Section numbers match the v3 primitive reference.
| `+` | OP | `+` |
| `-` | CAP | `NEGATE +`, `NEGATE` in line — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
| `*` | CAP | `UM* drop` — executed on the golden model (2026-10-03) against C's wrapping product. Leaves its caller 6 data cells and 5 return entries. |
| `/` | CAP | `/MOD NIP`, as `push S>D pop SM/REM push drop pop` (`/MOD`'s body and `NIP` in line, so it runs no deeper than `/MOD`). Truncates toward zero, as v3. Executed on the golden model (2026-10-03) against C wherever the quotient fits a cell. Leaves its caller 5 data cells and 2 return entries. Division by zero is guarded on the host node (D-15). **On the host node** (`v4/capsule/forth.v4`) `SM/REM` has `UM/MOD` written into it and keeps its two signs in memory, not on the return stack, so `/`, `MOD` and `/MOD` work from four words deep at the prompt, and `*/` and `*/MOD`, with `M*` written in, from two and three; the answers are the same, checked against C on every pair of the edge values. |
| `/` | CAP | `/MOD NIP`, as `push S>D pop SM/REM push drop pop` (`/MOD`'s body and `NIP` in line, so it runs no deeper than `/MOD`). Truncates toward zero, as v3. Executed on the golden model (2026-10-03) against C wherever the quotient fits a cell. Leaves its caller 5 data cells and 2 return entries. Division by zero is guarded on the host node (D-15). **On the host node** (`v4/capsule/forth.v4`) `SM/REM` has `UM/MOD` written into it and keeps its two signs in memory, not on the return stack, so that at nine return entries `/`, `MOD` and `/MOD` worked from four words deep at the prompt, and `*/` and `*/MOD`, with `M*` written in, from two and three (with D-17's 32 entries there is room to spare); the answers are the same, checked against C on every pair of the edge values. |
| `MOD` `/MOD` `*/` `*/MOD` (§4 versions) | RET | Shadowed duplicates. Only the §6 versions survive. |
| `1+` `1-` `2+` `2-` | IN | `1 +`, `-1 +`, `2 +`, `-2 +` — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
| `2*` | OP | `2*` |
@@ -753,21 +754,23 @@ call would bury what it works on. A **compile-only** word may not be executed by
`v4/capsule/forth.v4` holds the first words of the vocabulary flagged this way (`DUP`, `+`, `>R`, `I`,
`LEAVE`, `@`, the in-line variables and so on).
**The short stacks (D-2, D-16) and the compiler.** The interpreter and compiler run on the same ten-cell and
nine-entry stacks as the user's words, with the user's values beneath them. Two consequences are
built into the capsule:
**The stacks and the compiler.** The interpreter and compiler run on the same stacks as the user's
words, with the user's values beneath them. The capsule was written for stacks of ten cells and nine
entries (D-2), and is as sparing as that needed; the host node's are now 32 and 32 (D-17). The two
habits built into the capsule stay, and the measurements below are for the host node as it is now:
- 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, 6 may wait there while the next line is typed, and defining and
running a word leaves 4 cells under it untouched.
the data stack. Measured on the golden model: a line may have **28 values on the stack** while the
interpreter reads its next word, 29 may wait there while the next line is typed and interpreted
(the prompt and the interpreter take no more than four cells), and defining and running a word
leaves 26 cells under it untouched. (At ten cells these were 6, 6 and 4.)
- 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: 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
run from the prompt may call each other **31 deep** (8 deep at nine entries), and one more is
reported as a return stack overflow; a line that runs a word which calls nothing uses four
entries; 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`
+8 -2
View File
@@ -40,8 +40,14 @@ BINDIR := $(HERE)/build
# dictionary and the text being compiled (JUSTIFICATION.md section 9), which
# do not fit 1024 words. Tests named test_host_*.c are therefore built with a
# host node of HOST_WORDS words; every other test keeps the mesh-node size.
HOST_WORDS := 16384
node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS))
#
# The host node's stacks are deeper too (DECOMPOSITION.md D-17): 32 values and
# 32 return entries, where a mesh node has the F18's 10 and 9. The stack is
# its top registers and a ring; the ring sizes are what is set here.
HOST_WORDS := 16384
HOST_DATA_RING := 30
HOST_RET_RING := 31
node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING))
# The capsule sources: definitions as text (include/v4/text.h), which tests
# assemble onto a node. The directory is passed to the tests so that they
+3 -2
View File
@@ -17,8 +17,9 @@
\ compile-only it may not be executed by the interpreter.
\ An immediate word is executed even when compiling.
\
\ THE STACKS ARE SHORT (D-2): ten cells of data, nine return entries, and one
\ more of either is a fault (D-16). The interpreter and compiler run on the same
\ THE STACKS ARE SHORT: written for ten cells of data and nine return entries
\ (D-2; the host node now has 32 of each, D-17), and one more of either is a
\ fault (D-16). The interpreter and compiler run on the same
\ stacks as the user's words, with the user's values beneath them, so:
\ - every word here keeps what it works on in (C), and has at most three
\ cells of its own on the data stack at any moment;
+2 -2
View File
@@ -23,8 +23,8 @@ header 2DROP inline : 2DROP drop drop ;
\ DEPTH reads the stack register. PICK and ROLL reach under the top of a
\ stack that has no addresses: the values above the one wanted are set aside
\ in (S), and put back. (F)+3 how many are still to set aside, (F)+4 where
\ the next goes, (F)+5 the value wanted, (F)+6 how many are set aside. With nine values and n on the
\ stack it is full, so until a value has been set aside nothing here pushes
\ the next goes, (F)+5 the value wanted, (F)+6 how many are set aside. With n on top of a stack that is
\ otherwise one short of full there is no room at all, so until a value has been set aside nothing here pushes
\ anything while n is still there, and never more than one cell after.
\ FORTH-79: how many values were on the stack before DEPTH
+9 -2
View File
@@ -38,12 +38,19 @@
#include "v4/cell.h"
#include "v4/guard.h"
/* D-2: data stack is T, S + 8 circular = 10 deep. */
/* D-2: data stack is T, S + 8 circular = 10 deep. That is a mesh node. The
* ring size is a build parameter, like node memory (node.h): D-17 gives the
* host node, which runs the interpreter and the compiler under the user's
* programme, deeper stacks. Nothing else about the mechanism changes. */
#ifndef V4_DATA_RING
#define V4_DATA_RING 8
#endif
#define V4_DATA_DEPTH (V4_DATA_RING + 2)
/* D-2: return stack is R + 8 circular = 9 deep. */
/* D-2: return stack is R + 8 circular = 9 deep; a build parameter likewise. */
#ifndef V4_RET_RING
#define V4_RET_RING 8
#endif
#define V4_RET_DEPTH (V4_RET_RING + 1)
/* 5% safety boundary at each end of each ring, per guard.h. The bands sit
+1 -1
View File
@@ -38,7 +38,7 @@
#define CGVARS (TOP - 48) /* (CG): codegen.v4, 14 cells */
#define CVARS (TOP - 62) /* (C): compile.v4, 10 cells */
#define FVARS (TOP - 105) /* (F): forth.v4, 7 cells */
#define SVARS (TOP - 180) /* (S): where PICK and ROLL set stack values aside, 10 cells */
#define SVARS (TOP - 540 - (v4_cell)V4_DATA_DEPTH) /* (S): where PICK and ROLL set stack values aside, one cell for each cell of the stack */
#define QVARS (TOP - 52) /* (Q): quit.v4, 2 cells */
/* buffers (word addresses; a byte address is four times this) */
+4 -4
View File
@@ -395,15 +395,15 @@ int main(void)
/* ---- how many values a line may have on the stack ---- */
{
char line[96];
char line[4 * V4_DATA_DEPTH + 16];
unsigned k, most = 0;
new_session();
for (k = 1; k <= 9; k++) {
for (k = 1; k < V4_DATA_DEPTH; k++) {
size_t at = 0;
unsigned m;
for (m = 0; m < k; m++) at += (size_t)snprintf(line + at, sizeof line - at, "%u ", m + 1);
for (m = 0; m < k; m++) at += (size_t)snprintf(line + at, sizeof line - at, "1 ");
for (m = 1; m < k; m++) at += (size_t)snprintf(line + at, sizeof line - at, "+ ");
if (one(line) == (v4_cell)(k * (k + 1) / 2)) most = k; else break;
if (one(line) == (v4_cell)k) most = k; else break;
}
printf(" a line may have %u values on the stack while the interpreter reads its next word\n", most);
CHECK(most >= 6, "the interpreter leaves most of the stack to the user");
+91 -33
View File
@@ -22,8 +22,9 @@
* - QUERY takes 80 characters (FORTH-79); v3's line is 255.
* - an address outside memory says so (D-14); v3 gives ERROR alone.
* - M/MOD by zero says so (D-15); v3 gives ERROR alone.
* - the stacks hold ten values and nine return entries, and one more is
* an error (D-16); v3's are far deeper. A stack fault names the stack,
* - the host node's stacks hold 32 values and 32 return entries (D-17),
* and one more is an error (D-16). This file is written for whatever
* size it is built with. A stack fault names the stack,
* not the word: "Stack underflow" where v3 says "DROP: Stack underflow".
* - any fault empties the data stack; v3 keeps what the failing word had
* not taken.
@@ -171,13 +172,12 @@ static const transcript script[] = {
{ "65 EMIT DROP 66 EMIT\n67 EMIT\n", "AStack underflow\n ERROR\nok> C ok\nok> ", 0 },
{ "+\n1 +\nDUP\nOVER\n1 OVER\n", "Stack underflow\n ERROR\nok> Stack underflow\n ERROR\nok> Stack underflow\n ERROR\nok> "
"Stack underflow\n ERROR\nok> Stack underflow\n ERROR\nok> ", 0 },
{ "1 2 3 4 5 6 7 8 9 10 11\nDEPTH 48 + EMIT\n", "Stack overflow\n ERROR\nok> 0 ok\nok> ", 0 },
{ ": OV 1 2 3 4 5 6 7 8 65 EMIT 9 10 11 66 EMIT ; OV\nDEPTH 48 + EMIT\n", "AStack overflow\n ERROR\nok> 0 ok\nok> ", 0 },
{ ": OV 65 EMIT 1000 0 DO I LOOP 66 EMIT ; 7 8 9 OV\nDEPTH 48 + EMIT\n", "AStack overflow\n ERROR\nok> 0 ok\nok> ", 0 },
{ "1 2 3 -1 @\nDEPTH 48 + EMIT\n", "Address out of range\n ERROR\nok> 0 ok\nok> ", 0 },
{ ": RU R> DROP R> DROP ; 65 EMIT RU 66 EMIT\n67 EMIT\n", "AReturn stack underflow\n ERROR\nok> C ok\nok> ", 0 },
{ ": N0 65 EMIT ; : N1 N0 ; : N2 N1 ; : N3 N2 ; : N4 N3 ; : N5 N4 ; : N6 N5 ;\n: N7 N6 ; N6 N7 66 EMIT\n67 EMIT\n",
" ok\nok> AReturn stack overflow\n ERROR\nok> C ok\nok> ", 0 },
{ ": DEEP 1 >R 2 >R 3 >R 4 >R 5 >R 6 >R 65 EMIT 7 >R 8 >R 66 EMIT ; DEEP\n", "AReturn stack overflow\n ERROR\nok> ", 0 },
/* a word that runs itself, through EXECUTE, until the return stack is full */
{ "VARIABLE V : RR V @ EXECUTE ; ' RR V ! 65 EMIT RR 66 EMIT\n67 EMIT\n", "AReturn stack overflow\n ERROR\nok> C ok\nok> ", 0 },
{ ": DEEP 65 EMIT BEGIN 1 >R AGAIN ; DEEP\n", "AReturn stack overflow\n ERROR\nok> ", 0 },
{ "65 66 67 1 PICK EMIT EMIT EMIT EMIT\n", "CCBA ok\nok> ", 0 },
{ "65 66 67 2 PICK EMIT EMIT EMIT EMIT\n", "BCBA ok\nok> ", 0 },
{ "65 66 67 3 PICK EMIT EMIT EMIT EMIT\n", "ACBA ok\nok> ", 0 },
@@ -188,18 +188,11 @@ static const transcript script[] = {
{ "65 66 -3 ROLL\n65 66 0 ROLL\n", "ROLL: Invalid index\n ERROR\nok> ROLL: Invalid index\n ERROR\nok> ", 0 },
{ "65 66 3 PICK\nDEPTH 48 + EMIT\n", "Stack underflow\n ERROR\nok> 0 ok\nok> ", 0 },
{ "65 66 5 ROLL\n1 PICK\n", "Stack underflow\n ERROR\nok> Stack underflow\n ERROR\nok> ", 0 },
/* nine values and n: the stack is full when PICK and ROLL start */
{ ": P9 65 2 3 4 5 6 7 8 9 9 PICK >R 2DROP 2DROP 2DROP 2DROP R> EMIT EMIT ; P9\n", "AA ok\nok> ", 0 },
{ ": R9 65 2 3 4 5 6 7 8 9 9 ROLL EMIT 2DROP 2DROP 2DROP DROP 48 + EMIT ; R9\n", "A2 ok\nok> ", 0 },
{ "65 66 67 68 69 5 ROLL EMIT EMIT EMIT EMIT EMIT\n", "AEDCB ok\nok> ", 0 },
{ "65 66 67 68 69 4 ROLL EMIT EMIT EMIT EMIT EMIT\n", "BEDCA ok\nok> ", 0 },
{ ": P5 65 66 67 68 69 5 PICK EMIT 3 PICK EMIT EMIT EMIT EMIT EMIT EMIT ; P5\n", "ACEDCBA ok\nok> ", 0 },
{ ": T1 QUIT ; : T2 1 2 3 4 T1 ; T2\nDEPTH 48 + EMIT\n", "\nok> 4 ok\nok> ", 0 },
{ ": T3 1 2 3 4 5 6 7 8 9 ABORT ; T3\nDEPTH 48 + EMIT\n", " ok\nok> 0 ok\nok> ", 0 },
/* with the return stack full or nearly: the exit empties it before it prints */
{ ": B0 1 ABORT\" d\" ; : B1 B0 ; : B2 B1 ; : B3 B2 ; : B4 B3 ; : B5 B4 ; : B6 B5 ;\nB6\n", " ok\nok> d\n ok\nok> ", 0 },
{ ": I0 5 0 PICK ; : I1 I0 ; : I2 I1 ; : I3 I2 ; : I4 I3 ; : I5 I4 ; : I6 I5 ; I6\n", "PICK: Invalid index\n ERROR\nok> ", 0 },
{ ": J0 5 0 / ; : J1 J0 ; : J2 J1 ; : J3 J2 ; : J4 J3 ; : J5 J4 ; : J6 J5 ; J6\n", "/: Division by zero\n ERROR\nok> ", 0 },
{ "1 0 0 M/MOD\n", "M/MOD: Division by zero\n ERROR\nok> ", 0 },
{ ": Z0 0 / ; : Z1 65 EMIT 9 Z0 66 EMIT ; Z1\n: Z2 1 IF [ 5 0 MOD ]\nZ2\n",
"A/: Division by zero\n ERROR\nok> MOD: Division by zero\n ERROR\nok> UNKNOWN WORD: 'Z2'\n ERROR\nok> ", 0 },
@@ -378,24 +371,89 @@ int main(void)
CHECK(is(say("DEPTH 48 + EMIT\n"), "0 ok\nok> "), "ABORT has emptied the data stack");
/* ---- how deep words may call each other from the prompt ----
* N7 is eight words deep; with the prompt's call of INTERPRET that is all
* nine return entries. A ninth word is a fault (D-16). */
* The prompt's call of INTERPRET is one return entry, so a chain of words
* one shorter than the return stack runs, and the next is a fault. */
{
unsigned deepest = 0;
for (i = 0; i <= 8; i++) {
char line[64], want[16];
boot();
if (!is(say(": N0 1+ ; : N1 N0 1+ ; : N2 N1 1+ ; : N3 N2 1+ ; : N4 N3 1+ ;\n"), " ok\nok> ")) break;
if (!is(say(": N5 N4 1+ ; : N6 N5 1+ ; : N7 N6 1+ ; : N8 N7 1+ ;\n"), " ok\nok> ")) break;
snprintf(line, sizeof line, "47 N%u EMIT 33 EMIT\n", i);
snprintf(want, sizeof want, "%c! ok\nok> ", (char)('0' + i));
char line[64], want[16];
boot();
CHECK(is(say(": N0 1+ ;\n"), " ok\nok> "), "a word that calls nothing");
for (i = 1; i <= V4_RET_DEPTH; i++) {
snprintf(line, sizeof line, ": N%u N%u 1+ ;\n", i, i - 1u);
CHECK(is(say(line), " ok\nok> "), "and one that calls it, %u deep", i + 1u);
}
for (i = 0; i <= V4_RET_DEPTH; i++) {
snprintf(line, sizeof line, "33 N%u EMIT 33 EMIT\n", i);
snprintf(want, sizeof want, "%c! ok\nok> ", (char)(34 + i));
if (strcmp(say(line), want) != 0) break;
deepest = i + 1;
}
printf(" from the prompt, words may call each other %u deep\n", deepest);
CHECK(deepest == 8, "a word run from the prompt has all the return stack but the prompt's one entry");
CHECK(is(out, "Return stack overflow\n ERROR\nok> "), "and a ninth is reported");
CHECK(deepest == V4_RET_DEPTH - 1u, "a word run from the prompt has all the return stack but the prompt's one entry");
CHECK(is(out, "Return stack overflow\n ERROR\nok> "), "and one more is reported");
}
/* ---- the exits, taken with the return stack full ----
* RR runs itself through EXECUTE; each level holds one return entry, and
* EXECUTE one more while it starts the next. With C at its largest and
* one >R besides, there is room for exactly the action's own call. An
* exit that did not empty the return stack before it printed would
* overflow it. */
{
static const struct { const char *action, *want; } e[] = {
{ "1 ABORT\" x\"", "x\n ok\nok> " },
{ "5 0 PICK", "PICK: Invalid index\n ERROR\nok> " },
{ "5 0 ROLL", "ROLL: Invalid index\n ERROR\nok> " },
{ "5 0 /", "/: Division by zero\n ERROR\nok> " },
{ "5 5 0 */MOD", "*/MOD: Division by zero\n ERROR\nok> " },
{ "ABORT", " ok\nok> " },
{ "QUIT", "\nok> " },
};
char line[96];
for (i = 0; i < sizeof e / sizeof e[0]; i++) {
boot_bare();
CHECK(is(say("VARIABLE V VARIABLE C\n"), " ok\nok> "), "two variables");
snprintf(line, sizeof line, ": RR C @ 1- DUP C ! IF V @ EXECUTE EXIT THEN 1 >R %s ;\n", e[i].action);
CHECK(is(say(line), " ok\nok> "), "a word that runs itself C times and then: %s", e[i].action);
snprintf(line, sizeof line, "' RR V ! %u C ! RR\n", (unsigned)(V4_RET_DEPTH - 3u));
CHECK(is(say(line), e[i].want), "%s with the return stack full", e[i].action);
CHECK(n.faults == 0, "%s: and it was not a fault", e[i].action);
CHECK(is(say("65 EMIT\n"), "A ok\nok> "), "and the next line runs");
/* one level more and the action's own call does not fit */
snprintf(line, sizeof line, "%u C ! RR\n", (unsigned)(V4_RET_DEPTH - 2u));
CHECK(is(say(line), "Return stack overflow\n ERROR\nok> "), "%s one level deeper is a return stack overflow", e[i].action);
}
}
/* ---- the data stack, full ----
* P1 P2 P4 P8 P16 push that many values and call nothing while they do, so
* a word can fill the stack to any depth in a short line. */
{
char line[128], want[32], fill[64];
unsigned c, bit;
#define FILL(count) do { size_t at_ = 0; fill[0] = 0; \
for (c = (count), bit = 16; bit; bit >>= 1) if (c >= bit) { at_ += (size_t)snprintf(fill + at_, sizeof fill - at_, "P%u ", bit); c -= bit; } \
for (; c >= 16; c -= 16) at_ += (size_t)snprintf(fill + at_, sizeof fill - at_, "P16 "); } while (0)
boot_bare();
CHECK(is(say(": P1 7 ; : P2 7 7 ; : P4 7 7 7 7 ; : P8 7 7 7 7 7 7 7 7 ;\n"), " ok\nok> ")
&& is(say(": P16 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 ;\n"), " ok\nok> "), "words that push 1, 2, 4, 8 and 16 values");
/* 65, then DEPTH-2 more, then n = DEPTH-1: the stack is full when PICK and ROLL start */
FILL(V4_DATA_DEPTH - 2u);
snprintf(line, sizeof line, ": PF 65 %s%u PICK >R DROP R> EMIT ABORT ; PF\n", fill, (unsigned)(V4_DATA_DEPTH - 1u));
CHECK(is(say(line), "A ok\nok> ") && n.faults == 0, "PICK of the deepest value of a full stack");
snprintf(line, sizeof line, ": RF 65 %s%u ROLL EMIT DROP DEPTH 33 + EMIT ABORT ; RF\n", fill, (unsigned)(V4_DATA_DEPTH - 1u));
snprintf(want, sizeof want, "A%c ok\nok> ", (char)(33 + V4_DATA_DEPTH - 3u));
CHECK(is(say(line), want) && n.faults == 0, "ROLL of the deepest value of a full stack");
/* exactly full is not a fault; one more is */
FILL(V4_DATA_DEPTH);
snprintf(line, sizeof line, ": F1 %s2DROP ABORT ; F1\n", fill);
CHECK(is(say(line), " ok\nok> ") && n.faults == 0, "a stack exactly full is not a fault");
snprintf(line, sizeof line, ": F2 %s7 ; F2\n", fill);
CHECK(is(say(line), "Stack overflow\n ERROR\nok> ") && n.fault_kind == V4_FAULT_DATA_OVER, "one value more is");
CHECK(is(say("DEPTH 48 + EMIT\n"), "0 ok\nok> "), "and the stack is empty afterwards");
#undef FILL
}
/* and with text printed at the bottom of the chain */
boot();
CHECK(is(say(": P0 .\" deep\" ; : P1 P0 ; : P2 P1 ; : P3 P2 ; : P4 P3 ; P4\n"), "deep ok\nok> "), ".\" five calls down");
@@ -403,22 +461,22 @@ int main(void)
/* ---- how many values may wait on the stack from one line to the next ---- */
{
unsigned k, most = 0;
for (k = 1; k <= 9; k++) {
char line[96], want[16];
for (k = 1; k < V4_DATA_DEPTH; k++) {
char line[4 * V4_DATA_DEPTH + 16], want[16];
size_t at = 0;
unsigned m;
boot();
for (m = 0; m < k; m++) at += (size_t)snprintf(line + at, sizeof line - at, "%u ", m + 1);
boot_bare();
for (m = 0; m < k; m++) at += (size_t)snprintf(line + at, sizeof line - at, "1 ");
snprintf(line + at, sizeof line - at, "\n");
if (strcmp(say(line), " ok\nok> ") != 0) break;
for (at = 0, m = 1; m < k; m++) at += (size_t)snprintf(line + at, sizeof line - at, "+ ");
snprintf(line + at, sizeof line - at, "32 + EMIT\n");
snprintf(want, sizeof want, "%c ok\nok> ", (char)(32 + k * (k + 1) / 2));
if (strcmp(say(line), want) != 0 || !canary_under_expect()) break;
snprintf(want, sizeof want, "%c ok\nok> ", (char)(32 + k));
if (strcmp(say(line), want) != 0) break;
most = k;
}
printf(" %u values may wait on the stack while the next line is typed\n", most);
CHECK(most >= 6, "reading a line leaves most of the stack to the user");
printf(" %u values may wait on the stack while the next line is typed and interpreted\n", most);
CHECK(most + 4u >= V4_DATA_DEPTH, "the prompt and the interpreter take no more than four cells of the stack");
}
/* ---- how much of the data stack a line has ---- */