feat(v4.0.0): the stacks are guarded (D-16); DEPTH, PICK and ROLL

Ruled 2026-10-04, revising D-2: stack overflow and underflow are errors
that are shown and return to the prompt, not silent wrap-around.

- Each stack counts what it holds.  Before every opcode the executor
  checks that the stacks hold what it takes and have room for what it
  leaves; otherwise the opcode does nothing and the node faults, as for a
  bad address, to that kind's handler.  Every fault empties both stacks.
- The fault handler is now a table of five jumps: address, data overflow,
  data underflow, return overflow, return underflow.  The host node says
  "Stack overflow", "Stack underflow", "Return stack overflow",
  "Return stack underflow", then ERROR and the prompt.
- Two registers, DSTACK-DEPTH and RSTACK-DEPTH: a fetch reads the depth,
  a store empties the stack.  QUIT, ABORT and the error exits empty the
  return stack before they call anything; ABORT empties the data stack.
- capsule/forth.v4: DEPTH, PICK and ROLL, to FORTH-79 (counting from
  one).  PICK and ROLL set the values above the one wanted aside in
  memory, and work with the stack full.
- Division by zero now takes its operands off the stack, as v3 does.
- A colon with no room for its entry abandons the line.
- tests: every opcode at every depth of both stacks; the faults, the
  registers and the three words from the prompt.

The sizes are unchanged: ten values, nine return entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-04 17:31:18 -04:00
co-authored by Claude Opus 5.5
parent 361dcd1148
commit 0da7e32a0b
16 changed files with 542 additions and 106 deletions
+41
View File
@@ -320,6 +320,46 @@ static void test_differential_random(void)
printf(" differential: %d ops\n", OPS);
}
/* D-16: each stack counts what it holds. The count stops at the stack's size
* and at zero; push and pop go on doing what they always did there, and it is
* the executor that turns those two cases into faults (test_exec.c). */
static void test_depth_count(void)
{
v4_dstack d;
v4_rstack r;
unsigned i;
v4_dstack_reset(&d);
v4_rstack_reset(&r);
CHECK(d.depth == 0 && r.depth == 0, "reset stacks hold nothing");
for (i = 1; i <= V4_DATA_DEPTH + 3u; i++) {
v4_dstack_push(&d, (v4_cell)i);
CHECK(d.depth == (i < V4_DATA_DEPTH ? i : (unsigned)V4_DATA_DEPTH), "data depth after %u pushes is %u", i, d.depth);
}
for (i = V4_DATA_DEPTH; i-- > 0; ) {
(void)v4_dstack_pop(&d);
CHECK(d.depth == i, "data depth counts down to %u", i);
}
(void)v4_dstack_pop(&d);
CHECK(d.depth == 0, "and stays at zero");
for (i = 1; i <= V4_RET_DEPTH + 3u; i++) {
v4_rstack_push(&r, (v4_cell)i);
CHECK(r.depth == (i < V4_RET_DEPTH ? i : (unsigned)V4_RET_DEPTH), "return depth after %u pushes is %u", i, r.depth);
}
for (i = V4_RET_DEPTH; i-- > 0; ) {
(void)v4_rstack_pop(&r);
CHECK(r.depth == i, "return depth counts down to %u", i);
}
(void)v4_rstack_pop(&r);
CHECK(r.depth == 0, "and stays at zero");
v4_dstack_push(&d, 5); v4_dstack_push(&d, 6); v4_rstack_push(&r, 7);
v4_dstack_clear(&d); v4_rstack_clear(&r);
CHECK(d.depth == 0 && r.depth == 0, "clear empties a stack");
CHECK(d.t == 6 && d.s == 5 && r.r == 7, "and changes nothing else");
CHECK(v4_dstack_guards_intact(&d) && v4_rstack_guards_intact(&r), "guards intact");
}
int main(void)
{
printf("v4 stack tests: V4_CELL_BITS=%d, data depth %d, return depth %d\n",
@@ -332,6 +372,7 @@ int main(void)
test_underflow_wraps_rather_than_trapping();
test_exhaustive_sequences();
test_differential_random();
test_depth_count();
printf(" %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0;