diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index ce804b7f..7ff47011 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -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` diff --git a/v4/Makefile b/v4/Makefile index 62890c69..697b2bba 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -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 diff --git a/v4/capsule/compile.v4 b/v4/capsule/compile.v4 index c8656521..1d08236c 100644 --- a/v4/capsule/compile.v4 +++ b/v4/capsule/compile.v4 @@ -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; diff --git a/v4/capsule/forth.v4 b/v4/capsule/forth.v4 index f9d65395..34cb6665 100644 --- a/v4/capsule/forth.v4 +++ b/v4/capsule/forth.v4 @@ -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 diff --git a/v4/include/v4/stack.h b/v4/include/v4/stack.h index c1bf3305..542b1994 100644 --- a/v4/include/v4/stack.h +++ b/v4/include/v4/stack.h @@ -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 diff --git a/v4/tests/host_map.h b/v4/tests/host_map.h index 029896ce..198def0b 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -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) */ diff --git a/v4/tests/test_host_compile.c b/v4/tests/test_host_compile.c index 3280e2f9..46fd9361 100644 --- a/v4/tests/test_host_compile.c +++ b/v4/tests/test_host_compile.c @@ -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"); diff --git a/v4/tests/test_host_quit.c b/v4/tests/test_host_quit.c index da4675af..6f2de5f1 100644 --- a/v4/tests/test_host_quit.c +++ b/v4/tests/test_host_quit.c @@ -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 ---- */