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
+20 -4
View File
@@ -14,6 +14,7 @@ void v4_node_reset(v4_node *n)
v4_node_console_attach(n, -1);
v4_node_console_input_attach(n, -1, -1);
v4_node_fault_attach(n, -1);
v4_node_stack_regs_attach(n, -1, -1);
}
int v4_node_addr_ok(v4_cell addr)
@@ -23,19 +24,29 @@ int v4_node_addr_ok(v4_cell addr)
return (v4_ucell)addr < (v4_ucell)V4_NODE_WORDS;
}
void v4_node_fault_attach(v4_node *n, v4_cell handler)
void v4_node_stack_regs_attach(v4_node *n, v4_cell d, v4_cell r)
{
n->fault_vector = v4_node_addr_ok(handler) ? handler : (v4_cell)-1;
n->dstack_reg = d;
n->rstack_reg = r;
}
void v4_node_fault_attach(v4_node *n, v4_cell table)
{
n->fault_vector = (v4_node_addr_ok(table) && v4_node_addr_ok(table + (v4_cell)(V4_FAULT_KINDS - 1u))) ? table : (v4_cell)-1;
n->fault_addr = 0;
n->fault_kind = 0;
n->faults = 0;
n->stopped = 0;
}
void v4_node_fault(v4_node *n, v4_cell addr)
void v4_node_fault(v4_node *n, unsigned kind, v4_cell addr)
{
n->fault_kind = kind;
n->fault_addr = addr;
n->faults++;
if (n->fault_vector >= 0) n->p = n->fault_vector;
v4_rstack_clear(&n->rs);
v4_dstack_clear(&n->ds);
if (n->fault_vector >= 0) n->p = n->fault_vector + (v4_cell)kind;
else n->stopped = 1;
}
@@ -64,6 +75,9 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
n->console_dropped++;
return;
}
/* The stack registers: a store empties the stack. -1 when not attached. */
if (addr == n->dstack_reg) { v4_dstack_clear(&n->ds); return; }
if (addr == n->rstack_reg) { v4_rstack_clear(&n->rs); return; }
if (!v4_node_addr_ok(addr)) return;
n->mem[(unsigned)addr] = value;
}
@@ -81,6 +95,8 @@ v4_cell v4_node_fetch(v4_node *n, v4_cell addr)
if (n->input_pos == n->input_len) n->input_pos = n->input_len = 0;
return c;
}
if (addr == n->dstack_reg) return (v4_cell)n->ds.depth;
if (addr == n->rstack_reg) return (v4_cell)n->rs.depth;
return v4_node_load(n, addr);
}