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:
co-authored by
Claude Opus 5.5
parent
0da7e32a0b
commit
ebffa6082d
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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) */
|
||||
|
||||
@@ -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
@@ -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 ---- */
|
||||
|
||||
Reference in New Issue
Block a user