- Each entry's flags cell also holds v3's four fields: denied, pinned, mode and a 16-bit TTL. - compile.v4: INTERPRET checks every word it is about to execute or compile -- recheck at TTL 0, else count down; a denied word is refused with v3's line, the stack emptied and the line ended. - capsule/acl.v4: ACL-MODE@ ACL-MODE! ACL-TTL@ ACL-TTL! ACL-ALLOW@ ACL-ALLOW! ACL-PINNED? ACL-PIN ACL-INHERIT ACL-INIT-PRIMITIVES ACL-HEAT@ ACL-WORD-ID, and ACL-HOOK. - capsule/ACL.fth: v3's ACL.4th as FORTH source the node compiles; loading it switches access control on. - v3 checks every execution, inside definitions too. v4's code is native, so it checks a word when it is compiled as well as when interpreted; a call compiled while the word was allowed is not checked again. DECOMPOSITION.md 5.20 says so. - ACL-HEAT@ is 0 until heat is readable (D-6). - tests/test_host_quit.c: seven transcripts of the v3 binary; the policy file loaded and exercised; COLD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1099 lines
118 KiB
Markdown
1099 lines
118 KiB
Markdown
# StarForth v4.0.0 — Primitive Decomposition
|
||
|
||
This document assigns every C primitive in StarForth v3 (`admin/LithosAnanake` at `6302dcb`) a fate in
|
||
StarForth v4.0.0. v4 is built on a 32-instruction core derived from Chuck Moore's F18 (the GA144 node).
|
||
Everything that is not one of those 32 instructions becomes capsule code, a service message to another
|
||
node, a memory-mapped register, or is retired.
|
||
|
||
The rationale is in `JUSTIFICATION.md`. This document is the specification.
|
||
|
||
**Status.** Every colon definition below is written against the ISA in §1 and has been traced by hand,
|
||
but none has been executed. Each one becomes a POST test target: it is correct when the hosted v4 golden
|
||
model produces the same results as the v3 C primitive it replaces.
|
||
|
||
---
|
||
|
||
## Contents
|
||
|
||
0. [Fates](#0-fates)
|
||
1. [The v4 core ISA](#1-the-v4-core-isa)
|
||
2. [Compiler conventions](#2-compiler-conventions)
|
||
3. [Decisions](#3-decisions)
|
||
4. [Foundation layer (new words)](#4-foundation-layer-new-words)
|
||
5. [Fate of every v3 primitive](#5-fate-of-every-v3-primitive)
|
||
6. [Messaging between nodes](#6-messaging-between-nodes)
|
||
7. [Memory-mapped registers](#7-memory-mapped-registers)
|
||
|
||
---
|
||
|
||
## 0. Fates
|
||
|
||
| Code | Fate | Meaning |
|
||
| --- | --- | --- |
|
||
| **OP** | Instruction | One of the 32 core opcodes. |
|
||
| **IN** | Inline macro | A short opcode sequence the compiler places in-line. Never called. Required for anything that touches the return stack, since a call would bury the return address. |
|
||
| **CAP** | Core capsule | A colon definition in the core capsule, built only from OP, IN, and earlier CAP words. |
|
||
| **CC** | Compiler capsule | Part of the compiler/interpreter capsule, which runs on the host node only. Mesh nodes receive compiled code. |
|
||
| **DEV** | Device service | A message to a device node that owns real hardware (console, storage, display, keyboard, log). |
|
||
| **HERA** | Hera service | A message to the Hera node, which owns lifecycle, identity, capsules, ACLs, and Stadium admission. |
|
||
| **MM** | Memory-mapped | An `@` or `!` against a hardware register (heat counters, anti-clock, governor, stack pointer). |
|
||
| **RET** | Retired | A diagnostic of v3's software machine that has no equivalent in v4. |
|
||
|
||
---
|
||
|
||
## 1. The v4 core ISA
|
||
|
||
### 1.1 Machine model
|
||
|
||
| Item | v4 node |
|
||
| --- | --- |
|
||
| Cell | 32 bits (mesh node). The host node's width is a build parameter; see D-5. |
|
||
| Addressing | Word-addressed (D-1). |
|
||
| Registers | `T` (top of data stack), `S` (second), `R` (top of return stack), `P` (program counter), `A` and `B` (address registers). |
|
||
| Stacks | F18 hardware stacks, not addressable by code: data stack `T`, `S` + 8 (10 deep), return stack `R` + 8 (9 deep). Each counts what it holds, and a push onto a full stack or a pop from an empty one is a **fault** (D-16, which replaces D-2's silent wrap-around). |
|
||
| Instruction word | Six 5-bit slots, plus 2 spare bits. |
|
||
|
||
### 1.2 Instruction word layout
|
||
|
||
```
|
||
31 27 26 22 21 17 16 12 11 7 6 2 1 0
|
||
+--------+--------+--------+--------+--------+--------+----+
|
||
| slot 0 | slot 1 | slot 2 | slot 3 | slot 4 | slot 5 | xx |
|
||
+--------+--------+--------+--------+--------+--------+----+
|
||
```
|
||
|
||
Slots execute left to right. A branch (`jump`, `call`, `next`, `if`, `-if`) takes its target from all
|
||
bits to the right of its slot, and that target replaces the same low bits of `P` (page-relative, as in
|
||
the F18). Branches are therefore legal in slots 0–3 only:
|
||
|
||
| Branch in slot | Address bits | Reach |
|
||
| --- | --- | --- |
|
||
| 0 | 27 | whole address space |
|
||
| 1 | 22 | 4M words |
|
||
| 2 | 17 | 128K words |
|
||
| 3 | 12 | 4K words (node-local) |
|
||
|
||
### 1.3 The 32 opcodes
|
||
|
||
Opcode numbering follows the F18. Two names differ from Moore's: F18 `-` is renamed `inv` and F18 `or`
|
||
(which is exclusive-or) is renamed `xor`, so instruction names never collide with FORTH-79 word names.
|
||
|
||
| Op | Name | Effect |
|
||
| --- | --- | --- |
|
||
| 00 | `;` | Return: `P ← R`, pop `R`. |
|
||
| 01 | `ex` | Swap `P` and `R` (co-routine / execute). |
|
||
| 02 | `jump a` | `P ← a`. |
|
||
| 03 | `call a` | Push `P` to `R`, `P ← a`. |
|
||
| 04 | `unext` | If `R ≠ 0`: decrement `R`, restart the current instruction word at slot 0. Else pop `R`. |
|
||
| 05 | `next a` | If `R ≠ 0`: decrement `R`, `P ← a`. Else pop `R`. |
|
||
| 06 | `if a` | If `T = 0`: `P ← a`. **Does not pop `T`.** |
|
||
| 07 | `-if a` | If `T ≥ 0` (sign bit clear): `P ← a`. **Does not pop `T`.** |
|
||
| 08 | `@p` | Push the word at `P`, `P ← P+1` (literal). |
|
||
| 09 | `@+` | Push the word at `A`, `A ← A+1`. |
|
||
| 0A | `@b` | Push the word at `B`. |
|
||
| 0B | `@` | Push the word at `A`. |
|
||
| 0C | `!p` | Store `T` at `P`, pop, `P ← P+1`. |
|
||
| 0D | `!+` | Store `T` at `A`, pop, `A ← A+1`. |
|
||
| 0E | `!b` | Store `T` at `B`, pop. |
|
||
| 0F | `!` | Store `T` at `A`, pop. |
|
||
| 10 | `+*` | Multiply step: if bit 0 of `A` is set, `T ← T+S`; then shift `T:A` right one bit. See D-3. |
|
||
| 11 | `2*` | `T ← T << 1`. |
|
||
| 12 | `2/` | `T ← T >> 1`, arithmetic. |
|
||
| 13 | `inv` | `T ← ~T`. |
|
||
| 14 | `+` | `T ← S + T`, pop. |
|
||
| 15 | `and` | `T ← S & T`, pop. |
|
||
| 16 | `xor` | `T ← S ^ T`, pop. |
|
||
| 17 | `drop` | Pop `T`. |
|
||
| 18 | `dup` | Push a copy of `T`. |
|
||
| 19 | `pop` | Pop `R` onto the data stack. |
|
||
| 1A | `over` | Push a copy of `S`. |
|
||
| 1B | `a` | Push `A`. |
|
||
| 1C | `nop` | Nothing. |
|
||
| 1D | `push` | Pop `T` onto the return stack. |
|
||
| 1E | `b!` | `B ← T`, pop. |
|
||
| 1F | `a!` | `A ← T`, pop. |
|
||
|
||
Note that `@` and `!` address through `A`, not through `T`. The FORTH words `@` and `!` therefore become
|
||
`a! @` and `a! !`.
|
||
|
||
### 1.4 Side effects that are not instructions
|
||
|
||
Execution itself drives the physics. Every retired instruction increments that opcode's heat counter and
|
||
advances the anti-clock; every `call` increments the heat counter of its target. None of this costs an
|
||
instruction. Capsule code reads it through the memory-mapped registers in §7.
|
||
|
||
---
|
||
|
||
## 2. Compiler conventions
|
||
|
||
**Literals.** A number in source compiles to `@p` followed by the value in the next word.
|
||
|
||
**Capsule `IF`.** Native `if` and `-if` leave the flag on the stack. The capsule-level `IF` consumes it,
|
||
so the compiler drops it on both paths:
|
||
|
||
```
|
||
IF body THEN → if L1 drop body jump L2
|
||
L1: drop
|
||
L2:
|
||
|
||
IF body1 ELSE body2 THEN → if L1 drop body1 jump L2
|
||
L1: drop body2
|
||
L2:
|
||
```
|
||
|
||
`BEGIN ... WHILE ... REPEAT` and `BEGIN ... UNTIL` expand the same way.
|
||
|
||
**`FOR ... NEXT`.** `n FOR ... NEXT` compiles `push` then the body then `next`. The body runs `n+1`
|
||
times, as on the F18. `FOR ... UNEXT` is the same but the body must fit in one instruction word.
|
||
|
||
**Register conventions.** `A` and `B` are caller-saved. A word that uses them says so. Words in this
|
||
document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! D2/ M* FILL ERASE MOVE COUNT CMOVE CMOVE> BLANK -TRAILING COMPARE SEARCH SCAN SKIP DECIMAL HEX OCTAL UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS +LOOP J HOLD SIGN # #S . .R U. U.R D. D.R ? DUMP Q.PRINT TYPE SEND RECV`. Words that clobber `B`:
|
||
`COMPARE SEARCH Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS <# HOLD SIGN # #S #> . .R U. U.R D. D.R ? DUMP Q.PRINT EMIT CR SPACE SPACES TYPE KEY ?TERMINAL SEND RECV`.
|
||
|
||
**Return-stack words** (`>R R> R@ 2>R 2R> 2R@ I J UNLOOP` and the loop runtimes) are always IN.
|
||
|
||
---
|
||
|
||
## 3. Decisions
|
||
|
||
Ruled by Captain Bob, 2026-10-01, except D-4 and D-6, which are deferred to development step 2 (the
|
||
hosted mesh, `JUSTIFICATION.md` §10) because only the mesh and the RTL depend on them. Where a
|
||
definition below depends on one, it says so.
|
||
|
||
| ID | Decision | Ruling |
|
||
| --- | --- | --- |
|
||
| **D-1** | Word addressing (pure Moore) or byte addressing. | **Word addressing.** `C@`/`C!` are CAP; `CELLS` is a no-op. |
|
||
| **D-2** | Stack depth, and whether the stacks are visible. | **F18 circular stacks, hidden.** Data stack 10 deep (`T`, `S` + 8 circular), return stack 9 deep (`R` + 8 circular), exactly as the F18. No stack pointer is visible to code, so `DEPTH`, `PICK`, `ROLL`, `.S`, `SP@` and `SP!` are **retired everywhere**, host node included. **Revised by D-16 (2026-10-04):** the sizes stand and the stacks are still not addressable, but they are no longer circular in effect — they are counted and guarded — and `DEPTH`, `PICK` and `ROLL` are back. |
|
||
| **D-3** | Exact `+*` semantics at 32 bits: whether the add carries out of `T` into the shift. | **Plain F18 semantics.** The carry out of `T` is not kept. `UM*` in §4 is written for this and is exact over the full range (revised and proven on the golden model, 2026-10-02). |
|
||
| **D-4** | Node memory map, including port and register addresses. | **Deferred** to step 2. Symbolic names only for now (§6, §7). |
|
||
| **D-5** | Host node cell width. | **Match the host CPU:** 32 on a Zynq-7000 (Cortex-A9), 64 on an aarch64 host. The compiler capsule is written width-independent. |
|
||
| **D-6** | Which heat structures exist in hardware: per-opcode counters only, or also per-call-target and word-to-word transition counters. | **Deferred** to step 2. The golden model implements per-opcode and per-call-target heat in the meantime. |
|
||
| **D-7** | `ROLL` semantics. | **FORTH-79** (2026-10-04, with D-16): `n ROLL` takes out the n-th value, counting from one and not counting `n`, and puts it on top; `3 ROLL` is `ROT`, `1 ROLL` does nothing; `n < 1` is an error. `PICK` counts the same way: `1 PICK` is `DUP`. v3's count from zero. |
|
||
| **D-8** | Q48.16 signedness. | **Signed.** v3's unsigned comparisons and `Q.FROM-INT` clamping are retired. |
|
||
| **D-9** | Instruction word width on a 64-bit-cell host (ruled 2026-10-02). | **32 bits at every cell width.** Six 5-bit slots plus 2 spare bits, as in §1.2. On a 64-bit host the instruction word is the low 32 bits of the cell and the high half is ignored, so compiled code is identical at both widths. Only data and `@p` literals are a full cell wide. |
|
||
| **D-10** | Q48.16 width on a 64-bit-cell node (ruled 2026-10-02). | **Two cells at every cell width.** A Q value is a signed double on 32- and 64-bit nodes alike, so every Q word is the same double word at both widths. At 32-bit cells this is bit-for-bit v3's 64-bit Q. At 64-bit cells the low cell is v3's value, and where v3 wraps on Q48.16 overflow (a sum past Q max, `ABS` or `NEG` of Q min) the high cell carries the true result instead. |
|
||
| **D-11** | `Q./` semantics: division by zero, rounding, overflow (ruled 2026-10-02). | **Saturate and flag; round toward zero; saturate on overflow.** The quotient of `a * 2^16 / b` is rounded toward zero, like `SM/REM`. When it does not fit a Q value it is clamped to Q max or Q min by its sign. Division by zero returns Q max or Q min by the sign of the dividend (`0 / 0` gives 0) and sets the node's `NODE-ERROR` register (§7), which `VM-ERROR?` reads. v3 returned 0 on division by zero and saturated whenever the dividend was 2^48 or more, even when the quotient would have fit. |
|
||
| **D-12** | Q approximations outside their domain (ruled 2026-10-02). | **Return 0 and set `NODE-ERROR`.** `Q.SQRT` of a negative value and `Q.LOG` of zero or a negative value return 0 and set `NODE-ERROR` (§7), as `Q./` does on division by zero (D-11). v3 returned 0 for `ln(0)` without a flag and read negative arguments as large unsigned values. |
|
||
| **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 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`. |
|
||
| **D-18** | Every other error (ruled 2026-10-04: "guard all errors"). | **A word that finds an error raises it, and the line ends there.** The word takes its own arguments off the stack and stores an error code in `NODE-ERROR` (§7). On a node with a prompt that store is a trap, a sixth kind of fault beside D-14's and D-16's: nothing after it executes, the return stack is emptied, and the prompt prints the code's message, ends any definition that was open, prints ` ERROR` and waits for the next line. Unlike the other faults it leaves the data stack as the word left it. The codes: 1 `Negative count` (`CMOVE`, `TYPE`), 2 `Not a number` (`NUMBER`), 3 `Number too long` (`HOLD` into a full buffer), 4 `Not a character` (`HOLD`), 5 `Dictionary full` (`,` `C,` `ALLOT`, any defining word), 6 `Name missing` (a defining word with nothing after it), 7 `Control structure mismatch`, 8 `Control structures too deep`, 9 `Shift count out of range`, 10 `Protected word`, 11 `Division by zero` (`Q./`), 12 `Argument out of range`, 13 `Block out of range`, 14 `No block is being loaded`, 15 `Deferred word not set`, 16 `Not a word`; −1 means the word printed its own message (`UNKNOWN WORD: 'xxx'`, `xxx: compile-only`). Before this ruling these set the flag and the line ran on to its end. v3 stops at once too; its messages name the word (`LOOP: missing DO`) where v4's name the fault. With the trap not attached `NODE-ERROR` is plain memory and the word returns, which is how the words are tested below the prompt. D-11, D-12 and D-13 said "set `NODE-ERROR`"; on a node with a prompt that now means this. Executed on the golden model (2026-10-04): `tests/test_exec.c`, `tests/test_host_quit.c`. |
|
||
| **D-19** | Block storage on the golden model (2026-10-05). | **Four memory-mapped registers on the host node, until the mesh carries the storage service.** `BLOCK-NUMBER`, `BLOCK-ADDRESS` (the word address of 256 cells), `BLOCK-COMMAND` (a store of 1 reads the block into those cells, 2 writes it from them) and `BLOCK-STATUS` (0 when the command worked, −1 for a block the device has not got or cells not all in memory). A block is 1024 bytes: 256 cells, four bytes to a cell, the first byte lowest, at either cell width. This is the console's arrangement (§7) applied to storage; like it, it is the model's stand-in and not the mesh protocol, which §5.11 still leaves to the device node. |
|
||
|
||
**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
|
||
is therefore shallow, and exceeding either depth is a stack fault (D-16; before 2026-10-04 it
|
||
corrupted silently). Every CAP
|
||
definition in §4 and §5 must be re-checked against these limits before it is accepted as a POST
|
||
target; the hand traces so far assumed unbounded stacks.
|
||
|
||
---
|
||
|
||
## 4. Foundation layer (new words)
|
||
|
||
These words do not exist as primitives in v3 but everything else is built on them. They are listed in
|
||
dependency order.
|
||
|
||
```forth
|
||
\ ---- helpers ------------------------------------------------------------
|
||
: NIP ( a b -- b ) push drop pop ;
|
||
: SWAP ( a b -- b a ) over push push drop pop pop ;
|
||
: OR ( a b -- a|b ) over inv and xor ;
|
||
: NEGATE ( n -- -n ) inv 1 + ;
|
||
: ROT ( a b c -- b c a ) push SWAP pop SWAP ;
|
||
|
||
\ ---- sign and zero tests (raw branches, labels shown) ------------------
|
||
: 0< ( n -- flag ) -if L1 drop -1 ; L1: drop 0 ;
|
||
: 0= ( n -- flag ) if L1 drop 0 ; L1: drop -1 ;
|
||
|
||
\ ---- unsigned compare ---------------------------------------------------
|
||
: U< ( u1 u2 -- flag ) 2DUP xor 0< IF NIP 0< ELSE - 0< THEN ;
|
||
|
||
\ ---- multiply (D-3) -----------------------------------------------------
|
||
\ Plain F18 +* loses the carry out of T, and its shift keeps T's sign bit, so
|
||
\ the loop is exact only while the multiplicand in S and the running T both
|
||
\ lie in [-2^(n-2), 2^(n-2)). UM* therefore multiplies by s = u1 2/, which
|
||
\ always does, starting T at t0 = u2 2/ when u1 is odd, and gets
|
||
\ hi:lo = t0 + s*u2. Then u1*u2 = 2*(hi:lo) + c_lo + c_hi*2^n, where
|
||
\ c_lo = u1 & u2 & 1
|
||
\ c_hi = (u1<0 ? u2 : 0) + (u1 odd and u2<0 ? 1 : 0)
|
||
\ restore the bits the two halvings dropped and the unsigned reading of
|
||
\ both top bits. Exact over the full range; proven on the golden model.
|
||
: UM* ( u1 u2 -- ulo uhi )
|
||
over 1 and inv 1 + over 2/ and \ u1 u2 t0 t0 = u2 2/ if u1 odd
|
||
over a! 31 push \ A: u2 R: loop count (FOR's push, early)
|
||
push over 2/ pop \ u1 u2 s t0 s = u1 2/
|
||
L: +* unext \ u1 u2 s hi A: lo
|
||
push drop pop \ u1 u2 hi
|
||
2* a -if L0 drop 1 + jump L1 L0: drop L1: push \ R: 2hi + top bit of lo
|
||
over -if L2 drop dup jump L3 L2: drop 0 L3: push \ R: + (u1<0 ? u2 : 0)
|
||
dup -if L4 drop over 1 and jump L5 L4: drop 0 L5: \ u2<0 ? u1&1 : 0
|
||
pop + pop + push \ u1 u2 R: hi'
|
||
and 1 and a 2* + pop ; \ lo' hi'
|
||
\ u1 and u2 wait on the data stack under the loop (+* touches only T, S and
|
||
\ A), so the corrections are made after it, with -if in place of 0< calls,
|
||
\ and the return stack holds at most two temporaries. The loop count is
|
||
\ pushed before s is made, so the data stack never holds more than four.
|
||
\ Leaves its caller 6 data cells under its arguments and 6 return entries
|
||
\ (revised 2026-10-02; the first full-range version parked its three
|
||
\ corrections on the return stack and left 4).
|
||
|
||
\ ---- divide: 32-step restoring division, divisor held in A --------------
|
||
\ Each step shifts hi:lo left one bit and, when the shifted-out bit of hi was
|
||
\ set or hi' >= d, subtracts d from hi' and sets the new low bit of lo. The
|
||
\ unsigned compare is done by sign tests in line (as U< does), and x - d is
|
||
\ `inv a + inv`, so the loop body makes no calls: it needs one return-stack
|
||
\ entry beyond its own count.
|
||
: UM/MOD ( ulo uhi ud -- urem uquot )
|
||
a! 31 FOR
|
||
-if L0 \ hi's top bit set:
|
||
2* over -if L1 drop 1 + jump L2 \ hi' = 2hi + top bit of lo
|
||
L1: drop L2: push 2* pop \ lo' = 2lo
|
||
jump SUB \ true hi' >= 2^n > d
|
||
L0:
|
||
2* over -if L3 drop 1 + jump L4
|
||
L3: drop L4: push 2* pop
|
||
dup a xor -if L5 \ top bits of hi' and d differ:
|
||
drop -if NOSUB jump SUB \ hi' >= d iff hi' has it
|
||
L5: drop dup inv a + inv -if L6 \ same: hi' >= d iff hi'-d >= 0
|
||
drop jump NOSUB
|
||
L6: push drop pop jump SETBIT
|
||
SUB: inv a + inv \ hi' - d
|
||
SETBIT: push 1 + pop \ lo' is even: + 1 sets bit 0
|
||
NOSUB:
|
||
NEXT
|
||
over push push drop pop pop ; \ SWAP in line
|
||
\ Executed on the golden model (2026-10-02): exact, q*d + r = uhi:ulo with
|
||
\ r < d, for every uhi < ud at 32- and 64-bit cells. Outside that range
|
||
\ (uhi >= ud, including ud = 0) the result is unspecified, as in FORTH-79; it
|
||
\ always terminates after 32 steps. Stack use (D-2): up to 6 data cells may
|
||
\ lie under the three arguments and up to 6 return-stack entries under its
|
||
\ return address. For UM* the same limits are 6 and 6. This replaces a
|
||
\ first version built on U<, 0<, SWAP and OR calls, which was also exact but
|
||
\ left only 3 and 3 (too few for SM/REM inside /MOD) and stepped about five
|
||
\ times as many instruction words.
|
||
|
||
\ ---- signed division, truncating toward zero (v3 semantics) -------------
|
||
\ The quotient takes the sign of d xor n, the remainder the sign of d. Sign
|
||
\ tests are native -if (as in 0<) and NEGATE is in line, so the only calls
|
||
\ are DNEGATE and UM/MOD, both call-free inside.
|
||
: SM/REM ( d n -- rem quot )
|
||
over over xor push \ R: quotient sign (top bit)
|
||
over push \ R: + remainder sign (top bit of d)
|
||
-if L0 inv 1 + L0: push \ R: + |n|
|
||
-if L1 DNEGATE L1: \ |d|
|
||
pop UM/MOD \ urem uquot
|
||
pop -if L2 drop push inv 1 + pop jump L3 L2: drop L3:
|
||
pop -if L4 drop inv 1 + ; L4: drop ;
|
||
\ Executed on the golden model (2026-10-02): exact whenever the truncated
|
||
\ quotient fits a signed cell, at 32- and 64-bit cells. Stack use (D-2): up
|
||
\ to 5 data cells under the three arguments and 3 return-stack entries under
|
||
\ its return address; /MOD, one call further out, leaves 2. The first
|
||
\ version, built on ABS, DABS, 0< and NEGATE calls, overflowed the return
|
||
\ stack inside DABS -> DNEGATE -> D+ -> U> and never returned correctly for
|
||
\ a negative dividend.
|
||
```
|
||
|
||
`2DUP`, `-`, and `DNEGATE` are defined in §5; the compiler resolves forward references within the
|
||
core capsule.
|
||
|
||
---
|
||
|
||
## 5. Fate of every v3 primitive
|
||
|
||
Section numbers match the v3 primitive reference.
|
||
|
||
### 5.1 Stack
|
||
|
||
| Word | Fate | v4 definition / notes |
|
||
| --- | --- | --- |
|
||
| `DROP` | OP | `drop` |
|
||
| `DUP` | OP | `dup` |
|
||
| `OVER` | OP | `over` |
|
||
| `SWAP` | CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 6 return entries. |
|
||
| `?DUP` | CAP | `if Z dup ; Z: ;` (`dup IF dup THEN` without the extra copy). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `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 (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
|
||
|
||
| Word | Fate | v4 definition |
|
||
| --- | --- | --- |
|
||
| `>R` | IN | `push` — executed on the golden model (2026-10-03) with `R@` and `R>`, including a transcript of the v3 binary. |
|
||
| `R>` | IN | `pop` — executed on the golden model (2026-10-03), see `>R`. |
|
||
| `R@` | IN | `pop dup push` — executed on the golden model (2026-10-03), see `>R`. |
|
||
|
||
### 5.3 Memory
|
||
|
||
| Word | Fate | v4 definition / notes |
|
||
| --- | --- | --- |
|
||
| `@` | IN | `a! @` — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `!` | IN | `a! !` — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `+!` | CAP | `a! @ + !` — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `-!` | CAP | `a! NEGATE @ + !`, `NEGATE` in line — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `2@` | CAP | `a! @+ @` (low cell at `addr`, high at `addr+1`, as in v3) — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `2!` | CAP | `a! SWAP !+ !`, `SWAP` in line — executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 6 return entries. |
|
||
| `C@` | CAP | See below (D-1). Call-free. Executed on the golden model (2026-10-03). Leaves its caller 8 data cells and 7 return entries. |
|
||
| `C!` | CAP | See below (D-1). Call-free. Executed on the golden model (2026-10-03). |
|
||
| `FILL` | CAP | `( baddr u c -- )`: the low byte of `c`. See below. Nothing for `u = 0`; for `u < 0` nothing is written and `NODE-ERROR` is set (v3 read a negative count as a huge unsigned one). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 5 data cells and 5 return entries. Clobbers `A` and, on error, `B`. |
|
||
| `MOVE` | CAP | `( addr1 addr2 n -- )` — FORTH-79 (ruled 2026-10-04): `n` cells from `addr1` to `addr2`, the cell at `addr1` first; nothing for `n <= 0`. The addresses are word addresses (D-1). `-if OK drop drop drop ; OK: if DONE push over a! @ over a! ! 1 + push 1 + pop pop -1 + jump OK DONE: drop drop drop ;` v3's `MOVE` moved bytes, in whichever direction did not overwrite what it had yet to read; that is `CMOVE` and `CMOVE>`. Executed on the golden model (2026-10-04) against C, overlapping both ways. Leaves its caller 6 data cells and 6 return entries. Clobbers `A`. |
|
||
| `ERASE` | CAP | `0 FILL`, as `0 jump FILL`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `CELLS` | IN | Empty (word-addressed, D-1); v3 answers 24 for `3 CELLS`. Executed on the golden model (2026-10-03). |
|
||
|
||
```forth
|
||
\ byte access on a word-addressed node, little-endian: four bytes to a cell at
|
||
\ either cell width (so compiled code is the same, D-9), byte address = 4 * word
|
||
\ address + byte index. `@` and `!` inside are the opcodes, addressing through A.
|
||
\ C@ is call-free, like C!: one case per byte position, the cell shifted down
|
||
\ with a 2/ loop and masked. It replaces a version built on LSHIFT, RSHIFT and
|
||
\ SWAP calls, which was correct but left its caller 4 return-stack entries.
|
||
: C@ ( baddr -- c )
|
||
dup 2/ 2/ a! 3 and \ k A: word address
|
||
if K0 -1 + if K1 -1 + if K2
|
||
drop @ 23 FOR 2/ UNEXT 255 and ; \ byte 3
|
||
K2: drop @ 15 FOR 2/ UNEXT 255 and ; \ byte 2
|
||
K1: drop @ 7 FOR 2/ UNEXT 255 and ; \ byte 1
|
||
K0: drop @ 255 and ; \ byte 0
|
||
|
||
\ C! is call-free: one case per byte position. The byte is shifted up with a
|
||
\ 2* loop, that byte of the cell cleared with a constant mask, and the two added.
|
||
\ It replaces a version built on LSHIFT, RSHIFT, SWAP and OR calls, which was
|
||
\ correct but left its caller 4 return-stack entries; this leaves 7.
|
||
: C! ( c baddr -- )
|
||
dup 2/ 2/ a! 3 and push 255 and pop \ c' k A: word address
|
||
if K0 -1 + if K1 -1 + if K2
|
||
drop 23 FOR 2* UNEXT @ 4278190080 inv and + ! ; \ byte 3
|
||
K2: drop 15 FOR 2* UNEXT @ -16711681 and + ! ; \ byte 2
|
||
K1: drop 7 FOR 2* UNEXT @ -65281 and + ! ; \ byte 1
|
||
K0: drop @ -256 and + ! ; \ byte 0
|
||
|
||
: FILL ( baddr u c -- )
|
||
-ROT \ c baddr u
|
||
-if OK drop drop drop NODE-ERROR b! -1 !b ; \ u < 0
|
||
OK: if DONE push over over C! 1 + pop -1 + jump OK
|
||
DONE: drop drop drop ;
|
||
```
|
||
|
||
### 5.4 Arithmetic
|
||
|
||
| Word | Fate | v4 definition / notes |
|
||
| --- | --- | --- |
|
||
| `+` | 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 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*` |
|
||
| `2/` | OP | `2/` |
|
||
| `ABS` | CAP | `dup 0< IF NEGATE THEN` — executed on the golden model (2026-10-02). |
|
||
| `NEGATE` | CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `MIN` | CAP | `over over < if B drop drop ; B: drop push drop pop ;` — the smaller, signed. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 6 return entries. |
|
||
| `MAX` | CAP | `over over < if A drop push drop pop ; A: drop drop ;` — the larger, signed. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 6 return entries. |
|
||
|
||
### 5.5 Logic and comparison
|
||
|
||
| Word | Fate | v4 definition / notes |
|
||
| --- | --- | --- |
|
||
| `AND` | OP | `and` |
|
||
| `XOR` | OP | `xor` |
|
||
| `OR` | CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `INVERT` | OP | `inv` |
|
||
| `NOT` | CAP | `0=` (FORTH-79 logical not, as in v3), as `jump 0=`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `LSHIFT` | CAP | `BEGIN dup WHILE 1- SWAP 2* SWAP REPEAT drop` — executed on the golden model (2026-10-03). |
|
||
| `RSHIFT` | CAP | `BEGIN dup WHILE 1- SWAP 2/ MSB inv and SWAP REPEAT drop` (clears the sign bit each step; `MSB` is the cell-width top-bit constant) — executed on the golden model (2026-10-03). |
|
||
| `0=` `0<` | CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `0<>` | CAP | `if Z drop -1 ; Z: ;` (`0= 0=` without calls). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `0>` | CAP | `-if NN drop 0 ; NN: if Z drop -1 ; Z: ;` — two native sign tests in place of `dup 0< SWAP 0= OR 0=`; correct for the most negative number. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `=` | CAP | `xor 0=` — executed on the golden model (2026-10-02). |
|
||
| `<>` | CAP | `xor 0<>`, as `xor jump 0<>`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `<` | CAP | `2DUP xor 0< IF drop 0< ELSE - 0< THEN` (overflow-safe) — executed on the golden model (2026-10-02). |
|
||
| `>` | CAP | `SWAP <`, as `SWAP jump <` with `SWAP` in line. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. |
|
||
| `<=` | CAP | `> 0=`, as `SWAP < jump 0=`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. |
|
||
| `>=` | CAP | `< 0=`, as `< jump 0=`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. |
|
||
| `U<` | CAP | §4. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 7 return entries. |
|
||
| `U>` | CAP | `SWAP U<` — executed on the golden model (2026-10-02). |
|
||
| `WITHIN` | CAP | `( n low high -- flag )`: `push over pop < push < inv pop and` — `low <= n < high`, both comparisons signed, which is what v3 computes. It replaces `over - push - pop U<`, the circular form, which answers differently when `low > high` (`5 10 0 WITHIN` is false in v3 and here, true in that form). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. |
|
||
| `TRUE` | IN | `-1` — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
| `FALSE` | IN | `0` — executed on the golden model (2026-10-03), including a transcript of the v3 binary. |
|
||
|
||
### 5.6 Mixed-precision arithmetic
|
||
|
||
A double is two 32-bit cells on a mesh node.
|
||
|
||
| Word | Fate | v4 definition |
|
||
| --- | --- | --- |
|
||
| `M+` | CAP | `S>D D+` — executed on the golden model (2026-10-02). |
|
||
| `M-` | CAP | `( d n -- d )`: `S>D DNEGATE jump D+`, `S>D` by a sign test in line. `n` is widened before it is negated, so the most negative `n` is subtracted correctly; `NEGATE M+` would add it. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary (v3 took the double with its low cell on top; the standard order is kept here, as for `M+`). Leaves its caller 5 data cells and 5 return entries. |
|
||
| `M*` | CAP | `( n1 n2 -- d )`: `over over xor push -if A inv 1 + A: push -if B inv 1 + B: pop UM* pop -if P drop jump DNEGATE P: drop ;` — the unsigned product of the magnitudes, negated when the signs differ; the sign tests are native. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary. Leaves its caller 6 data cells and 4 return entries. Clobbers `A`. |
|
||
| `M/MOD` | CAP | `( d n -- rem quot )`: `jump SM/REM`. Truncating, the remainder with the dividend's sign, as v3. v3 took the double with its low cell on top (`dhigh dlow n`), the reverse of what its own `M*` leaves; the standard order is kept here, so `M* ... M/MOD` composes. Executed on the golden model (2026-10-03), including results recorded from the v3 binary with the double's cells exchanged. Leaves its caller 5 data cells and 3 return entries. |
|
||
| `MOD` | CAP | `/MOD drop`, with `/MOD`'s body in line: `push S>D pop SM/REM drop`. The remainder has the dividend's sign, as v3. Executed on the golden model (2026-10-03) against C and results recorded from the v3 binary. Leaves its caller 5 data cells and 2 return entries. Division by zero is guarded on the host node (D-15). |
|
||
| `/MOD` | CAP | `push S>D pop SM/REM` — executed on the golden model (2026-10-02). |
|
||
| `*/` | CAP | `*/MOD NIP`, as `push M* pop SM/REM push drop pop`. Executed on the golden model (2026-10-03). Leaves its caller 5 data cells and 2 return entries. |
|
||
| `*/MOD` | CAP | `( n1 n2 n3 -- rem quot )`: `push M* pop jump SM/REM` — the product is a full double, so the answer is exact whenever the quotient fits a cell. v3 multiplied in one cell at 64-bit cells and so was right only while `n1 * n2` fitted one; the two agree there. Executed on the golden model (2026-10-03) against the identity `quot * n3 + rem = n1 * n2` on every combination of the edge values, and results recorded from the v3 binary. Leaves its caller 5 data cells and 2 return entries. |
|
||
|
||
### 5.7 Double-cell numbers
|
||
|
||
| Word | Fate | v4 definition |
|
||
| --- | --- | --- |
|
||
| `S>D` | CAP | `dup 0<` — executed on the golden model (2026-10-02). |
|
||
| `D+` | CAP | `push over push push drop pop over over xor -if L1 drop + -if C1 jump C0 L1: drop over -if L2 drop + jump C1 L2: drop + C0: pop pop + ; C1: pop pop + 1 + ;` — call-free. The carry out of the low cells is found by sign tests: if their top bits differ, there is a carry exactly when the sum's top bit is clear; if they match, exactly when both are set. Executed on the golden model (2026-10-02); leaves its caller 6 data cells under its arguments and 5 return entries. Replaces `push SWAP push over + 2DUP U> ROT drop NEGATE pop pop + +`, which was exact but left 1 return entry. |
|
||
| `DNEGATE` | CAP | `inv over if L1 drop push inv 1 + pop ; L1: drop 1 + ;` — call-free: `~d + 1`, carrying into the high cell exactly when the low cell is 0. Executed on the golden model (2026-10-02). Replaces `inv SWAP inv SWAP 1 0 D+`, which left a caller no return-stack room, so `DABS` could not run at all. |
|
||
| `D-` | CAP | `DNEGATE D+` — executed on the golden model (2026-10-02). |
|
||
| `DABS` | CAP | `dup 0< IF DNEGATE THEN` — executed on the golden model (2026-10-02). |
|
||
| `D0=` | CAP | `OR 0=` — executed on the golden model (2026-10-02). |
|
||
| `D0<` | CAP | `NIP 0<`, as `push drop pop jump 0<`. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. |
|
||
| `D=` | CAP | `D- D0=` — executed on the golden model (2026-10-02). |
|
||
| `D<` | CAP | `ROT 2DUP = IF 2DROP U< ELSE SWAP < NIP NIP THEN` — executed on the golden model (2026-10-02). |
|
||
| `(D<)` | CAP | `( d1 d2 -- d1 d2 flag )`, call-free; internal to `DMAX` and `DMIN`. Copies of the high cells go on top; if they differ the flag comes from them, else from the low cells unsigned. The value tested always has its top bit set exactly when `d1 < d2`: `ah` (high signs differ), `ah - bh` (agree), `bl` (low top bits differ), `al - bl` (agree); `x - y` with `y` on top is `push inv pop + inv`. `dup push push over pop over over xor if TIE drop over over xor -if HS drop drop jump S1 HS: drop push inv pop + inv S1: -if N1 drop -1 jump D1 N1: drop 0 D1: pop SWAP ; TIE: drop drop drop dup push push over pop over over xor -if LS drop NIP jump S2 LS: drop push inv pop + inv S2: -if N2 drop -1 jump D2 N2: drop 0 D2: pop pop ROT ;` with `SWAP`, `NIP` and `ROT` in line. Executed on the golden model (2026-10-02). |
|
||
| `DMAX` | CAP | `(D<) if L drop push push drop drop pop pop ; L: drop drop drop ;` Executed on the golden model (2026-10-02). Replaces `2OVER 2OVER D< IF 2SWAP THEN 2DROP`, which needs 8 data cells plus `D<`'s 2: the whole 10-deep data stack, so it failed whenever the caller held anything at all (D-2). |
|
||
| `DMIN` | CAP | `(D<) if L drop drop drop ; L: drop push push drop drop pop pop ;` Executed on the golden model (2026-10-02); replaces `2OVER 2OVER D< 0= IF 2SWAP THEN 2DROP` for the same reason as `DMAX`. |
|
||
| `D2*` | CAP | `2* over -if P drop 1 + jump J P: drop J: push 2* pop` — the low cell's top bit enters the high cell; a native sign test in place of `over 0< NEGATE OR`. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. |
|
||
| `D2/` | CAP | `push a! 0 pop +* push drop a pop` — one `+*` with `S = 0` is an exact arithmetic right shift of `T:A`, as in `Q.FROM-INT`. It replaces `dup 1 and push 2/ SWAP 1 RSHIFT pop IF MSB OR THEN SWAP`. Executed on the golden model (2026-10-03), including results recorded from the v3 binary. Clobbers `A`. |
|
||
| `2DROP` | IN | `drop drop` — executed on the golden model (2026-10-03). |
|
||
| `2DUP` | IN | `over over` — executed on the golden model (2026-10-03). |
|
||
| `2SWAP` | CAP | `ROT push ROT pop`, with `ROT` and `SWAP` in line so it makes no calls. Executed on the golden model (2026-10-02). Called, `ROT` and `SWAP` left its caller 2 return entries; in line, 4. |
|
||
| `2OVER` | CAP | `push push 2DUP pop pop 2SWAP` — executed on the golden model (2026-10-02). |
|
||
| `2ROT` | CAP | `push push 2SWAP pop pop jump 2SWAP`. Executed on the golden model (2026-10-03), including a result recorded from the v3 binary. With six cells of its own it leaves its caller 3 data cells and 1 return entry. |
|
||
| `2>R` | IN | `SWAP push push` — executed on the golden model (2026-10-03) with `2R@` and `2R>`; the high cell is on top of the return stack, as in v3. |
|
||
| `2R>` | IN | `pop pop SWAP` — see `2>R`. |
|
||
| `2R@` | IN | `pop pop 2DUP push push SWAP` — see `2>R`. |
|
||
|
||
### 5.8 Number formatting and output
|
||
|
||
All output reaches the console through `EMIT` (DEV).
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `<#` `#` `#S` `HOLD` `SIGN` `#>` | CAP | Pictured output over `UM/MOD` and a 64-byte hold buffer, filled backwards from its end `HEND` through the pointer `HLD`. Standard stack effects (v3 took its double low cell on top, and its tolerant `#>` popped `ud` only if present; neither is kept). Otherwise v3's behaviour: digits `0`–`9` then `A`–`Z`; `BASE` outside 2–36 reads as 10; 63 characters; `HOLD` of a value outside 0–255 or into a full buffer stores nothing and sets `NODE-ERROR` (D-13). Definitions below. Executed on the golden model (2026-10-03) against a C reference in bases 2, 3, 8, 10, 16, 36 and four invalid ones. `<# #S #>` leaves its caller 4 data cells and 3 return entries; the signed picture `dup push ABS 0 <# #S pop SIGN #>` leaves 2 return entries. `#`, `#S`, `HOLD` and `SIGN` clobber `A` and `B`. |
|
||
| `HLD` | CAP | Variable: byte address of the first held character. `HLD ( -- addr )` is the variable's word address, a literal. Not a v3 word. Executed on the golden model (2026-10-03): after `<#` and each `HOLD` or `#`, `HLD @` is the address `#>` returns. |
|
||
| `.` `.R` `U.` `U.R` `D.` `D.R` | CAP | Built on pictured output and `TYPE`; definitions below. `.`, `U.` and `D.` print the number in the current base, then one space. The `.R` words right-justify it in `width` columns, print a wider number whole, pad nothing for `width <= 0`, and print no space after it: that is FORTH-79's reference word (ruled 2026-10-04); v3 printed a space after these too. Where v4 parts from v3: a double is `( lo hi )`, where v3's `D.` took the low cell on top; `D.` prints the whole double, where v3 printed `DOUBLE-OVERFLOW` unless it fitted one signed cell (they agree whenever it does); printing honours the `BASE` variable, where v3 printed in a host copy that only `DECIMAL`, `HEX` and `OCTAL` set, so `n BASE !` changed v3's input base but not its output; and the number is built in the 63-character hold buffer, so a longer one — a 64-bit cell in base 2, a large double in a small base — loses its leading characters and sets `NODE-ERROR` (D-13), where v3 printed from a private 80-character buffer. Executed on the golden model (2026-10-03) against a C reference in the ten bases of the pictured-output test and eleven field widths, and against five transcripts of the v3 binary (a sixth, the extreme cells, at 64-bit cells). Each leaves its caller 4 data cells; `U.` and `U.R` leave 3 return entries, the signed words 2, their sign waiting on the return stack. All clobber `A` and `B`. |
|
||
| `(W)` | CAP | Variable, 2 cells: the field width of the `.R` words, and whether a blank follows the number. Like `BASE` and `HLD`, it lives in node memory, so the width is on neither stack while the picture runs. |
|
||
| `.S` | CC | As v3: `<depth> `, then every value on the stack, the deepest first, each followed by a blank, then a new line; the stack is left as it was. Built on `DEPTH`, `PICK` and `.` (D-16). It works on the stack it is printing, so it needs six cells of it free: on the host node's 32 it prints a stack of up to 26 values, and a fuller one is a stack overflow. Source in `v4/capsule/numout.v4`; executed on the golden model's host node (2026-10-04), including transcripts of the v3 binary. |
|
||
| `?` | CAP | `( addr -- )`: `a! @ jump .` — the cell at word address `addr` (D-1), printed as `.` prints it. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 2 return entries. Clobbers `A` and `B`. |
|
||
| `DUMP` | CAP | `( baddr u -- )`: `u` bytes from byte address `baddr`, sixteen to a line, in v3's format: the address in hex, `": "`, each byte as two hex digits and a space (three spaces where the line runs out), `" \|"`, the bytes as characters with `.` for anything outside 32–126, `"\|"` and a new line. Always hex, whatever `BASE` is, and `BASE` is put back. The address is two hex digits per byte of a cell — 8 at 32-bit cells, 16 at 64, which is v3's width. Nothing for `u = 0`; for `u < 0` nothing is printed and `NODE-ERROR` is set, as `TYPE` does, where v3 raised its error flag. Addresses are not range-checked (open, `node.h`). Definition below. Executed on the golden model (2026-10-03) against a C reference at six alignments and every length 0–50, and at 64-bit cells against a transcript of the v3 binary, byte for byte. Leaves its caller 4 data cells and 4 return entries. Clobbers `A` and `B`. |
|
||
| `(DP)` | CAP | Variable, 4 cells: `DUMP`'s saved `BASE`, address, count and column. |
|
||
| `BASE` | CAP | Variable. `BASE ( -- addr )` is its word address (D-1), a literal. Executed on the golden model (2026-10-03). Storing to it changes what is printed, which it did not in v3 (see `.`). |
|
||
| `DECIMAL` `HEX` `OCTAL` | CAP | `10 BASE !`, `16 BASE !`, `8 BASE !`, with `!` in line as `a! !`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Each leaves its caller 8 data cells and 8 return entries. Clobber `A`. |
|
||
|
||
```forth
|
||
\ pictured output. HEND is the byte address just past the hold buffer.
|
||
: (BASE) ( -- b ) BASE b! @b dup -2 + -if L1 drop drop 10 ;
|
||
L1: drop dup -37 + -if L2 drop ; L2: drop drop 10 ;
|
||
: <# ( -- ) HEND HLD b! !b ;
|
||
: HOLD ( c -- ) dup -256 and if OKC drop jump ERR
|
||
OKC: drop HLD b! @b -(HEND-62) + -if ROOM drop
|
||
ERR: drop NODE-ERROR b! -1 !b ;
|
||
ROOM: drop HLD b! @b -1 + dup !b jump C!
|
||
: SIGN ( n -- ) -if L1 drop 45 jump HOLD L1: drop ;
|
||
: # ( ud1 -- ud2 )
|
||
0 (BASE) UM/MOD -ROT \ qhi lo rem1
|
||
(BASE) UM/MOD -ROT \ qlo qhi rem
|
||
dup -10 + -if L1 drop 48 + jump L2 L1: drop 55 + L2: jump HOLD
|
||
: #S ( ud -- 0 0 ) L: # over over OR if L1 drop jump L L1: drop ;
|
||
: #> ( ud -- baddr u ) drop drop HLD b! @b HEND over push inv pop + inv ;
|
||
```
|
||
|
||
`#` divides the double by the base in two `UM/MOD` steps, the high cell first and its remainder
|
||
leading the low cell; the last remainder is the digit. `HOLD`, `SIGN` and `#` end with a jump to the
|
||
next word rather than a call, so `C!` returns straight to their caller and two return-stack entries
|
||
are saved. `#` keeps the high quotient under the second division on the data stack, not on the
|
||
return stack. `-ROT` is in line (`SWAP push SWAP pop`).
|
||
|
||
```forth
|
||
\ number output. ABS is in line: -if A inv 1 + A:
|
||
: .R ( n width -- )
|
||
0 (W) 1 + b! !b \ no blank after it
|
||
BODY: (W) b! !b dup push ABS 0 <# #S pop SIGN #> \ baddr u
|
||
TAIL: (W) b! @b -if POS drop jump OUT \ width < 0
|
||
POS: over - SPACES \ width - u spaces
|
||
OUT: TYPE (W) 1 + b! @b if DONE drop jump SPACE
|
||
DONE: drop ;
|
||
: . ( n -- ) -1 (W) 1 + b! !b 0 jump BODY \ a blank after it
|
||
: U.R ( u width -- ) 0 (W) 1 + b! !b UBODY: (W) b! !b 0 <# #S #> jump TAIL
|
||
: U. ( u -- ) -1 (W) 1 + b! !b 0 jump UBODY
|
||
: D.R ( d width -- ) 0 (W) 1 + b! !b
|
||
DBODY: (W) b! !b dup push -if A DNEGATE A: <# #S pop SIGN #> jump TAIL
|
||
: D. ( d -- ) -1 (W) 1 + b! !b 0 jump DBODY
|
||
```
|
||
|
||
```forth
|
||
\ DUMP. DIGITS is two per byte of a cell.
|
||
: (DB) ( -- c ) (DP) 1 + b! @b (DP) 3 + b! @b + jump C@ \ the byte in this column
|
||
: DUMP ( baddr u -- )
|
||
-if OK drop drop NODE-ERROR b! -1 !b ; \ u < 0
|
||
OK: (DP) 2 + b! !b (DP) 1 + b! !b \ count, address
|
||
BASE b! @b (DP) b! !b 16 BASE b! !b \ hex
|
||
LINE: (DP) 2 + b! @b if DONE drop
|
||
(DP) 1 + b! @b 0 <# DIGITS (DP) 3 + b! !b \ the address
|
||
AD: # (DP) 3 + b! @b -1 + dup !b if ADX drop jump AD
|
||
ADX: drop #> TYPE 58 EMIT SPACE
|
||
0 (DP) 3 + b! !b \ the bytes in hex
|
||
HX: (DP) 2 + b! @b (DP) 3 + b! @b inv + -if HAVE \ count - column - 1
|
||
drop SPACE SPACE SPACE jump HN
|
||
HAVE: drop (DB) 0 <# # # #> TYPE SPACE
|
||
HN: (DP) 3 + b! @b 1 + dup !b -16 + -if HXX drop jump HX
|
||
HXX: drop SPACE 124 EMIT
|
||
0 (DP) 3 + b! !b \ the bytes as characters
|
||
CH: (DP) 2 + b! @b (DP) 3 + b! @b inv + -if HAVC jump CHX
|
||
HAVC: drop (DB)
|
||
dup -32 + -if GE drop drop 46 jump EM
|
||
GE: drop dup -127 + -if BIG drop jump EM
|
||
BIG: drop drop 46
|
||
EM: EMIT
|
||
(DP) 3 + b! @b 1 + dup !b -16 + -if CHX drop jump CH
|
||
CHX: drop 124 EMIT CR
|
||
(DP) 1 + b! @b 16 + !b \ next line
|
||
(DP) 2 + b! @b -16 + -if MORE jump DONE
|
||
MORE: !b jump LINE
|
||
DONE: drop (DP) b! @b BASE b! !b ;
|
||
```
|
||
|
||
Everything `DUMP` keeps between words — the address, the count, the column and the saved `BASE` — is
|
||
in the variable `(DP)`, so the stacks carry only what each picture and `TYPE` need, and no loop count
|
||
sits on the return stack across a call.
|
||
|
||
**In the capsule.** `v4/capsule/numout.v4` holds these words for the host node — `<# # #S HOLD SIGN #>`, `. .R U. U.R D. D.R`, `?`, `SPACES`, `DECIMAL HEX OCTAL` — each the definition above, with the shared body and tail of the number words as words of their own (`(.BODY)`, `(U.BODY)`, `(D.BODY)`, `(.TAIL)`), since a jump cannot land inside another word. Executed from the prompt on the golden model's host node (2026-10-04), including transcripts of the v3 binary (`tests/test_host_quit.c`). At 64-bit cells a 64-digit binary number is one digit more than the hold buffer's 63 (D-13): 63 digits are printed and the line ends with ` ERROR`.
|
||
|
||
There is one picture for signed singles, one for unsigned and one for doubles; each plain word is
|
||
its `.R` word with a width of 0 and the blank flag set, entered by a jump past where `.R` clears
|
||
that flag, and all six share one tail, which prints the blank only when the flag is set. The width is tested for sign before `width - u`, which could wrap for a very negative
|
||
width and print a flood of spaces.
|
||
|
||
### 5.9 Strings, parsing, and input
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `COUNT` | CAP | `( baddr -- baddr+1 c )`: `dup C@ push 1 + pop`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 6 return entries. Clobbers `A`. |
|
||
| `CMOVE` | CAP | `( src dst u -- )`, low byte first. See below. Nothing for `u = 0`; for `u < 0` nothing is written and `NODE-ERROR` is set, where v3 raised its error flag. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 5 data cells and 5 return entries. |
|
||
| `CMOVE>` | CAP | `( src dst u -- )`, high byte first. See below. Errors as `CMOVE`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. |
|
||
| `BLANK` | CAP | `32 FILL`, as `32 jump FILL`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. v3's counted-string auto-detection is not kept. |
|
||
| `-TRAILING` | CAP | `( baddr u -- baddr u' )`. See below. A negative count reads as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 6 data cells and 6 return entries. |
|
||
| `COMPARE` | CAP | `( a1 u1 a2 u2 -- n )`: `-1`, `0` or `1` by the first differing byte, unsigned, else the shorter string is less. See below. Negative counts read as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. Clobbers `A` and `B`. |
|
||
| `SEARCH` | CAP | `( a1 u1 a2 u2 -- a3 u3 flag )`: the rest of string 1 from the first place string 2 occurs and `-1`, or string 1 and `0`; an empty string 2 is found at the start. See below; the inner comparison is in line, not a call to `COMPARE`. Same notes. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 3 data cells and 5 return entries. Clobbers `A` and `B`. |
|
||
| `SCAN` `SKIP` | CAP | `( baddr u c -- baddr' u' )`: `SCAN` stops at the first byte equal to the low byte of `c` (or at the end, with `u' = 0`); `SKIP` stops at the first byte not equal to it. See below. A negative count reads as 0, as v3. v3's counted-string auto-detection is not kept. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Each leaves its caller 5 data cells and 5 return entries. Clobber `A`. |
|
||
| `(S)` | CAP | Variable, 4 cells: `COMPARE`'s two lengths; `SEARCH`'s string 2 and the whole of string 1. |
|
||
| `BL` | IN | `32` — executed on the golden model (2026-10-04). |
|
||
| `EXPECT` `QUERY` | CC | Built on `KEY` (DEV); source in `v4/capsule/input.v4`. FORTH-79 (ruled 2026-10-04). `EXPECT ( baddr n -- )` stores characters from `baddr` upward until a new-line (taken, not stored) or until `n` have been received, then a zero; no action for `n <= 0`; a line longer than `n` leaves the rest to be read next. It also sets `SPAN`, which is not FORTH-79 but is v3's. `QUERY` is `TIB 80 EXPECT 0 >IN !`. v3's `EXPECT` was C's `fgets` and took `n-1` characters, and its `QUERY` took 1024. Executed on the golden model's host node (2026-10-04) against C for every size and line length. `EXPECT` is what the prompt reads every line with, on top of whatever the user has left on the stack, so it keeps where it is in memory: it leaves its caller 7 data cells and 7 return entries. A line of exactly 80 characters fills `QUERY`'s count before its new-line arrives, so the new-line is read as an empty line after it; a longer line is read as 80 characters and then the rest. |
|
||
| `SPAN` `TIB` `>IN` `SOURCE` | CC | Interpreter state on the host node. `TIB` is the buffer's byte address and `>IN` and `SPAN` their variables' word addresses, all in line; `SOURCE ( -- baddr u )` is `TIB` and `SPAN @`, a negative `SPAN` read as 0. Executed on the golden model's host node (2026-10-04). |
|
||
| `WORD` `ENCLOSE` | CC | Parser; source in `v4/capsule/input.v4`. `WORD ( c -- baddr )` is FORTH-79 (ruled 2026-10-04): characters are taken from `TIB` until the delimiter `c` or the end of the text, leading delimiters ignored, and stored as a counted string; the delimiter met (`c`, or a zero if the text ran out) is stored after them, uncounted; `>IN` is left just past it; with nothing left the count is 0. The count is a byte, so a word over 255 characters is cut to 255; the buffer is 257 bytes. v3's `WORD` skipped every delimiter after the word, always stored a zero, and cut at 62. `ENCLOSE ( baddr c -- baddr n1 n2 n3 )`, not a FORTH-79 word, works on a zero-terminated string as v3's. Executed on the golden model's host node (2026-10-04) against C, and `ENCLOSE` against values recorded from the v3 binary. `WORD` keeps what it works on in memory and has at most three cells of its own on the data stack: it leaves its caller 7 data cells and 6 return entries. `(PARSE) ( c -- baddr )` is `WORD` without the skipping of leading delimiters: the text starts at `>IN` and may be empty. It is what `."` and `ABORT"` use, so that `." "` is an empty string. |
|
||
| `NUMBER` `CONVERT` | CC | FORTH-79, not v3 (ruled 2026-10-04); source in `v4/capsule/input.v4`. `CONVERT ( d1 baddr1 -- d2 baddr2 )` takes the characters from `baddr1 + 1` on as digits in the current `BASE`, in either case, accumulating each into the double after multiplying it by `BASE`, and stops at the first that is not one; the double is `( lo hi )`. v3's was base 10 only, started at `baddr1`, and took the double low cell on top. `NUMBER ( baddr -- d )` is the counted string as a signed double in the current `BASE`, with an optional leading minus; anything else gives 0 and sets `NODE-ERROR`. v3's returned `( n flag )` in base 10. Executed on the golden model's host node (2026-10-04) against C in eight bases, including a digit that carries out of the low cell. `NUMBER` leaves its caller 3 data cells and 2 return entries; the interpreter does not use it (it has a single-cell conversion of its own that needs far less of the stack). |
|
||
| `S"` `(S")` `[']` | CC | Source in `v4/capsule/quit.v4` and `compile.v4`. `S" text"` `( -- baddr u )`, as v3: in a definition the text is compiled after a call to `(S")`, as `."` compiles it, and `(S")` leaves its address and length and returns past it. At the prompt the text is copied to `PAD` and stays there until `PAD` is used again. `['] xxx` compiles `xxx`'s address as a literal. v3 also has `[LITERAL]`, another name for `LITERAL`. Executed on the golden model's host node (2026-10-04), including transcripts of the v3 binary. |
|
||
| `LITERAL` `[LITERAL]` (placeholders) | RET | The working `LITERAL` is in §5.17. |
|
||
|
||
```forth
|
||
\ SWAP and the subtractions ( - ) are in line.
|
||
: CMOVE ( src dst u -- )
|
||
-if OK drop drop drop NODE-ERROR b! -1 !b ; \ u < 0
|
||
OK: if DONE push \ src dst R: u
|
||
over C@ over C! 1 + push 1 + pop pop -1 + jump OK
|
||
DONE: drop drop drop ;
|
||
|
||
: CMOVE> ( src dst u -- )
|
||
-if OK drop drop drop NODE-ERROR b! -1 !b ; \ u < 0
|
||
OK: if DONE -1 + push \ src dst R: i = u - 1
|
||
over pop dup push + C@ \ src dst c
|
||
over pop dup push + C! \ src dst
|
||
pop jump OK
|
||
DONE: drop drop drop ;
|
||
|
||
: -TRAILING ( baddr u -- baddr u' )
|
||
-if L drop 0 ; \ u < 0
|
||
L: if DONE over over + -1 + C@ -32 + if SP drop ; \ not a space
|
||
SP: drop -1 + jump L
|
||
DONE: ;
|
||
|
||
: COMPARE ( a1 u1 a2 u2 -- n )
|
||
-if A drop 0 A: (S) 1 + b! !b \ a1 u1 a2
|
||
SWAP -if B drop 0 B: dup (S) b! !b \ a1 a2 u1
|
||
(S) 1 + b! @b \ a1 a2 u1 u2
|
||
over over - -if GE drop drop jump M \ the smaller
|
||
GE: drop push drop pop
|
||
M: \ a1 a2 m
|
||
L: if EQ push \ a1 a2 R: m
|
||
over C@ over C@ - if SAME \ c1 - c2
|
||
-if GT drop drop drop pop drop -1 ;
|
||
GT: drop drop drop pop drop 1 ;
|
||
SAME: drop 1 + push 1 + pop pop -1 + jump L
|
||
EQ: drop drop drop (S) b! @b (S) 1 + b! @b - \ u1 - u2
|
||
-if NN drop -1 ;
|
||
NN: if ZZ drop 1 ;
|
||
ZZ: ;
|
||
|
||
: SEARCH ( a1 u1 a2 u2 -- a3 u3 flag )
|
||
-if A drop 0 A: (S) 1 + b! !b (S) b! !b \ a1 u1 (S): a2 u2
|
||
-if B drop 0 B: dup (S) 3 + b! !b over (S) 2 + b! !b \ a u (S)+2: a1 u1
|
||
L: dup (S) 1 + b! @b - -if TRY \ u - u2
|
||
drop drop drop (S) 2 + b! @b (S) 3 + b! @b 0 ; \ not found
|
||
TRY: drop over (S) b! @b (S) 1 + b! @b \ a u p q k
|
||
I: if MATCH push \ a u p q R: k
|
||
over C@ over C@ xor if SAME
|
||
drop drop drop pop drop push 1 + pop -1 + jump L \ next place
|
||
SAME: drop 1 + push 1 + pop pop -1 + jump I
|
||
MATCH: drop drop drop -1 ;
|
||
|
||
: SCAN ( baddr u c -- baddr' u' )
|
||
255 and push -if L drop 0 \ baddr u R: c
|
||
L: if DONE over C@ pop dup push xor if FOUND
|
||
drop push 1 + pop -1 + jump L
|
||
FOUND: drop
|
||
DONE: pop drop ;
|
||
|
||
: SKIP ( baddr u c -- baddr' u' )
|
||
255 and push -if L drop 0 \ baddr u R: c
|
||
L: if DONE over C@ pop dup push xor if SAME drop jump DONE
|
||
SAME: drop push 1 + pop -1 + jump L
|
||
DONE: pop drop ;
|
||
```
|
||
|
||
### 5.10 Terminal I/O
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `EMIT` | DEV | Console service: one-character message. Until the mesh exists it is a store to the `CONSOLE-TX` register (§7): `CONSOLE-TX b! !b`. As in v3, the low byte of the cell is the character. Executed on the golden model (2026-10-03). Clobbers `B`. |
|
||
| `KEY` | DEV | Console service: blocking receive. Until the mesh exists it reads the `CONSOLE-STATUS` and `CONSOLE-RX` registers (§7): `L: CONSOLE-STATUS b! @b if WAIT drop CONSOLE-RX b! @b ; WAIT: drop jump L` — it asks whether a character is pending until one is, then takes it. The character is 0–255. v3's `KEY` returned −1 at the end of its input; here there is no end of input, and `KEY` waits. Executed on the golden model (2026-10-03), including a transcript of the v3 binary, and shown still waiting after 5000 instruction words with nothing pending. Leaves its caller 9 data cells and 8 return entries. Clobbers `B`. |
|
||
| `?TERMINAL` | DEV | Console service: non-blocking status. Until the mesh exists: `CONSOLE-STATUS b! @b` — −1 when a character is pending, 0 when not, as v3; it takes nothing. Executed on the golden model (2026-10-03). Clobbers `B`. |
|
||
| `TYPE` | CAP | `( baddr u -- )`. Loop of `C@ EMIT`, or one string message to the console node: `-if OK drop drop NODE-ERROR b! -1 !b ; OK: if DONE over C@ EMIT push 1 + pop -1 + jump OK DONE: drop drop ;` — nothing for `u = 0`; for `u < 0` nothing is printed and `NODE-ERROR` is set, where v3 printed nothing and raised its error flag. An address outside the node's memory is an address fault (D-14). Executed on the golden model (2026-10-03). Leaves its caller 6 data cells and 6 return entries. Clobbers `A` and `B`. |
|
||
| `CR` | CAP | `10 EMIT`, as `10 jump EMIT` — executed on the golden model (2026-10-03). Character 10, as v3; the console turns it into a new line. |
|
||
| `SPACE` | CAP | `BL EMIT`, as `32 jump EMIT` — executed on the golden model (2026-10-03). |
|
||
| `SPACES` | CAP | `( n -- )`: `-if L drop ; L: if DONE SPACE -1 + jump L DONE: drop ;` — `n` spaces, none for `n <= 0`, as v3. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 7 data cells and 7 return entries. Clobbers `B`. |
|
||
| `."` `(.")` | CC | FORTH-79; source in `v4/capsule/quit.v4`. `." text"` takes the text up to the next `"` (none at all is allowed; with no closing `"` it is the rest of the line). Interpreting, it prints it. Compiling, it lays a call to `(.")` and after it the text as a counted string — count byte, characters, four to a cell, the last cell filled with zeros. `(.")` finds the string by the return address its call left and returns to the cell after it: `pop 4* dup C@ L: if DONE push 1 + dup C@ EMIT pop -1 + jump L DONE: drop 4/ 1 + push ;`. It takes the return address off before it calls anything, so a word that prints text goes no deeper than one that calls any other word. v3's `(do-string)` has no counterpart. Executed on the golden model's host node (2026-10-04), every length from 0 to 12 and 76, including transcripts of the v3 binary. |
|
||
|
||
### 5.11 Blocks and mass storage
|
||
|
||
The storage service belongs to the Artemis role, now a device node.
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `BLOCK` `BUFFER` `UPDATE` `SAVE-BUFFERS` `EMPTY-BUFFERS` `FLUSH` | DEV | FORTH-79 (`FLUSH` is v3's, the same as `SAVE-BUFFERS`). Source in `v4/capsule/blocks.v4`, over the device of D-19. There are **two buffers**, so two blocks can be in memory at once; a buffer goes to the block that asks, turn about, and a block that was `UPDATE`d is written before its buffer is given away. `BLOCK` and `BUFFER` give a **byte address**, as `PAD` and `TIB` are — what `C@`, `CMOVE` and `TYPE` take. A block number below 1, or one the device has not got, is D-18's code 13, `Block out of range` (v3 says ` ERROR` alone). Executed on the golden model's host node (2026-10-05), including transcripts of the v3 binary, with the bytes on the device checked after each step. |
|
||
| `LOAD` `THRU` `-->` `BLK` | CC | `LOAD` and `BLK` are FORTH-79; `THRU` and `-->` are v3's. Source in `v4/capsule/blocks.v4`. The text being interpreted is at the address in the variable `(SRC)`, `SPAN` characters long, `>IN` the place in it; `QUERY` makes that the terminal's buffer and `LOAD` a block's, all 1024 characters as one stream. `LOAD` saves `(SRC)`, `SPAN`, `>IN` and `BLK` on the return stack and puts them back, so a block may `LOAD` another and **the rest of the line `LOAD` was on is interpreted afterwards** (v3 drops it). `BLK` is the block being interpreted, 0 at the terminal; v3 has no `BLK`. When words are split at blanks a zero byte reads as a blank, so a block that was never written, or whose lines were put there one at a time, loads cleanly. A buffer can be given to another block between one word of the text and the next, so while a block is loading `WORD` first makes sure it is in a buffer (a hook `LOAD` installs). In a block `\` skips to the next 64-character line. `-->` at the terminal is code 14, `No block is being loaded`. An error while loading ends every `LOAD` under way and the terminal is the input again. Executed on the golden model's host node (2026-10-05), three blocks deep on two buffers, including transcripts of the v3 binary. |
|
||
| `LIST` | CAP | As v3: a new line, `Block n`, sixteen lines of 64 characters each after its two-digit number and a colon, and an empty line; a character that is not printable shows as a blank. Leaves `n` in `SCR`. Executed on the golden model's host node (2026-10-05) against a transcript of the v3 binary. |
|
||
| `SCR` | CAP | Variable: the block `LIST` showed last. |
|
||
| `BLK-CONFIRM-FORMAT` `RELOCATE-BLOCK` | DEV | Owner-only storage messages. |
|
||
| `BLK-ACL-ALLOW@` `BLK-ACL-ALLOW!` `BLK-ACL-TTL@` `BLK-ACL-TTL!` | HERA | ACL state is held by Hera. |
|
||
| `BLK-OWNER@` | HERA | Read-only, as in v3. |
|
||
| `BLK-ATTACH` | RET | Took a raw host pointer. Replaced by an attach message from the device node. |
|
||
|
||
### 5.12 Dictionary space
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `HERE` `ALIGN` `ALLOT` `,` `C,` `2,` `PAD` `LATEST` | CC | The dictionary lives on the host node; source in `v4/capsule/dict.v4`. The dictionary pointer `DP` is a byte address, so `C,` packs four characters to a cell; `HERE` is the next whole cell (a word address); `,` and `ALLOT` align first. An address unit is a cell (D-1), so `ALLOT` counts cells where v3's counted bytes; a negative count gives space back. `2,` stores the low cell first, as v3. Going outside the dictionary space stores nothing, moves nothing and sets `NODE-ERROR`. `PAD` is a fixed scratch area, as in v3, given as a byte address. `LATEST` is the xt of the newest entry, 0 if there is none (v3's pushes `HERE`, which looks like a bug; reported, not changed). Executed on the golden model's host node (2026-10-04). `,` calls nothing and leaves its caller 7 data cells and 8 return entries; `ALLOT` leaves 7 and 7. |
|
||
| `SP@` `SP!` | RET | A stack cell has no address (D-2, D-16). `DEPTH`, and a store to `DSTACK-DEPTH` to empty the stack, are what remain of them. |
|
||
|
||
### 5.13 Dictionary manipulation
|
||
|
||
| Word | Fate |
|
||
| --- | --- |
|
||
| `FIND` | CC | `( -- xt \| 0 )`, FORTH-79 and v3 alike: the compilation address of the next word in the input stream, or 0 if it is not in the dictionary. `32 WORD` then a search from the newest entry back, skipping hidden ones; names are case sensitive, as v3, and 31 characters are significant (FORTH-79). Executed on the golden model's host node (2026-10-04) against a list kept in C, 300 entries. Leaves its caller 7 data cells and 5 return entries. |
|
||
| `'` | CC | `( -- addr )`, FORTH-79: the parameter field address of the next word in the input stream; not found is an error (0, and `NODE-ERROR` set). v3's `'` returned what `FIND` returns. For a word that is code the two are the same address; for a data word the parameter field is the cell after its code, which is what FORTH-79's `n ' NAME !` needs. It is immediate: inside a definition the address is compiled as a literal (FORTH-79), where v3's `'` parsed when the definition ran. Executed on the golden model's host node (2026-10-04). |
|
||
| `>LINK` `LFA` `LINK>` `>NAME` `NFA` `NAME>` `CFA` `PFA` `>BODY` `TRAVERSE` | CC | The fields of an entry, all reached from its xt, as in v3 where the xt was the entry: `>LINK`/`LFA` the link's address, `LINK>` the older entry's xt, `>NAME`/`NFA` the byte address of the counted name, `NAME>` back to the xt, `CFA` the xt itself, `PFA`/`>BODY` the parameter field, `TRAVERSE` from a name's count byte to past its last character (for `n > 0`; otherwise unchanged, as v3). Executed on the golden model's host node (2026-10-04). |
|
||
| `SMUDGE` `HIDDEN` | CC | `SMUDGE` toggles the hidden flag of the newest entry; `HIDDEN` sets it. v3 refuses both outside compilation; that check comes with the compiler words. Executed on the golden model's host node (2026-10-04). |
|
||
| `INTERPRET` | CC | FORTH-79; source in `v4/capsule/compile.v4`. Takes words from the input stream until it is exhausted: a word in the dictionary is executed, or, when compiling and not immediate, compiled; anything else must be a number in the current `BASE` (a single cell, with an optional leading minus), left on the stack or compiled as a literal. What is neither, or a compile-only word met while interpreting, abandons the line: compiling stops, the definition under way stays hidden, the rest of the input is skipped and `NODE-ERROR` is set, after v3's message: `UNKNOWN WORD: 'xxx'` or `xxx: compile-only`. `INTERPRET` then returns to `QUIT`, which prints ` ERROR`. Executed on the golden model's host node (2026-10-04). |
|
||
|
||
An entry, as `v4/capsule/dict.v4` builds it. A word's execution address (xt) is the address of its
|
||
code, and everything else is found from it:
|
||
|
||
```
|
||
name the count byte, then the characters, four bytes to a cell, zero-padded to a whole cell
|
||
xt - 3 flags: 1 immediate, 2 hidden, 4 data
|
||
xt - 2 the word address of the name
|
||
xt - 1 the link: the xt of the entry before this one, 0 for the first
|
||
xt the code
|
||
```
|
||
|
||
A name holds at most 31 characters. A *data* word (one made by `CREATE`, `VARIABLE` or `CONSTANT`) is
|
||
one whose code is a single call to its run-time routine, with its parameter field in the cell after
|
||
it, `xt + 1`; for any other word the parameter field is the code itself.
|
||
|
||
### 5.14 Vocabularies
|
||
|
||
| Word | Fate |
|
||
| --- | --- |
|
||
| `VOCABULARY` `DEFINITIONS` `CONTEXT` `CURRENT` `FORTH` `ORDER` | CC | FORTH-79 (`ORDER` is v3's). Source in `v4/capsule/system.v4`, `dict.v4` and `compile.v4`. A vocabulary is two cells — its head, the xt of its newest entry, and the address of the vocabulary defined before it. `CONTEXT` and `CURRENT` each hold the address of a vocabulary's head cell; FORTH's is `(LATEST)`. A name is looked up in the `CONTEXT` vocabulary and then, if that is not FORTH, in FORTH ("new vocabularies chain to FORTH"). A new entry goes into the `CURRENT` vocabulary, and `:` makes the `CURRENT` vocabulary `CONTEXT`. `VOCABULARY xxx` makes an empty one whose name, executed, makes it `CONTEXT`; `FORTH` does the same for FORTH and is immediate; `DEFINITIONS` makes the `CONTEXT` vocabulary `CURRENT`. `WORDS` lists the `CONTEXT` vocabulary. `FORGET` takes what was defined later out of every vocabulary, and a vocabulary that goes with it gives way to FORTH. `ORDER` prints `Search order:` and `Current:` by name. v3's vocabularies do not separate anything: a word defined in one is found from every other, and a word redefined in one replaces FORTH's permanently; its `FORTH` is not immediate. Executed on the golden model's host node (2026-10-05). |
|
||
| `(FIND)` | CC | |
|
||
|
||
### 5.15 System
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `(` `\` | CC | `(` is `41 WORD drop`; `\` is `SPAN @ >IN !`. In `v4/capsule/input.v4` under the names `PAREN` and `BACKSLASH`. Executed on the golden model's host node (2026-10-04). |
|
||
| `EXECUTE` | CAP | `push ;` (tail-jumps to the xt; the xt returns to `EXECUTE`'s caller). Executed on the golden model's host node (2026-10-04). |
|
||
| `NOP` | OP | `nop` |
|
||
| `QUIT` | CC | FORTH-79; source in `v4/capsule/quit.v4`. Clears the return stack, sets execution mode, returns control to the terminal; no message. It is the prompt: after a new line it prints `ok> `, reads a line with `QUERY`, clears `NODE-ERROR`, runs `INTERPRET`, prints ` ok` — or, if `NODE-ERROR` is set, ends any definition that was open and prints ` ERROR` — and goes round again, for as long as the node runs. The text is v3's. The line is not sent back; the terminal shows what is typed. It empties the return stack first, by a store to `RSTACK-DEPTH` (D-16), so it works from however deep, with the return stack full. The data stack is left alone. A definition open when `QUIT` runs is ended and stays hidden. Executed on the golden model's host node (2026-10-04): the node is started at `QUIT`, fed characters and its output read, including 16 sessions recorded from the v3 binary. v3's `QUIT` goes on with the rest of the line and cannot be compiled into a definition. |
|
||
| `ABORT` | CC | FORTH-79; source in `v4/capsule/quit.v4`. As `QUIT`, and it empties the data stack too (a store to `DSTACK-DEPTH`, D-16); the line it stops ends with ` ok`, as v3. Executed on the golden model's host node (2026-10-04), from six calls down and from inside two loops, forty times over, including transcripts of the v3 binary. |
|
||
| `ABORT"` `(ABORT")` | CC | Not FORTH-79 (it is FORTH-83's); kept because v3 has it. `ABORT" text"` `( flag -- )`: if the flag is not zero, print the text, start a new line and `ABORT`. Compiled as `."` is, with `(ABORT")` as the run-time word; it also works at the prompt. Executed on the golden model's host node (2026-10-04). v3's crashes (SIGSEGV) when a word compiled with it runs, and at the prompt prints the text and carries on with the line. |
|
||
| `COLD` `WARM` | CC | Source in `v4/capsule/system.v4`. `WARM` prints v3's two lines and `ABORT`s: both stacks emptied, the dictionary kept. `COLD` first puts the system back as the loader left it — `DP` and the newest FORTH entry from the two cells `(BOOT)`, one vocabulary, decimal, empty block buffers, `FENCE` at the capsule's end — so everything defined since is gone. Executed on the golden model's host node (2026-10-05). |
|
||
| `BYE` `REBOOT` | HERA | |
|
||
| `SAVE-SYSTEM` | HERA | Snapshot becomes a capsule-image request. |
|
||
| `WORDS` `VLIST` | CC | Source in `v4/capsule/system.v4`. The names in the dictionary, the newest first, a blank after each, a new line whenever one has passed column 64; a definition under way is not shown. v3 prints a heading, eight names to a line and a total. Executed on the golden model's host node (2026-10-04). |
|
||
| `SEE` | CC | FORTH source, `v4/capsule/tools.fth`, compiled by the node. v4 code is native, so `SEE xxx` is a disassembler: the name, then each instruction word on a line — the opcodes by name, a literal's value after its `@p`, and after a call or a branch the name of the word it goes to, or the address if that is not a word. Text compiled by `."`, `S"` or `ABORT"` is shown in quotes after its call (the three run-time words `(.")` `(S")` `(ABORT")` have names for this, and are compile-only). Padding is not shown. It stops at the first `;`, `ex` or `jump` that no earlier branch goes past. A data word shows what it holds; an immediate word says so. v3's `SEE` lists the words of threaded code, which v4 has not got. Executed on the golden model's host node (2026-10-05), on definitions with literals, text, `IF` and loops, on the capsule's own words, and on itself. |
|
||
| `PAGE` | DEV | As v3 on a terminal: `ESC [2J ESC [H`. Source in `v4/capsule/system.v4`; executed on the golden model's host node (2026-10-05). |
|
||
| `79-STANDARD` | CC | FORTH-79: executes if a FORTH-79 system is there. It is, so nothing happens. v3's prints two lines and leaves a flag, which the standard does not have. |
|
||
|
||
### 5.16 Line editor
|
||
|
||
| Word | Fate |
|
||
| --- | --- |
|
||
| `L` `S` `SHOW` `EDIT` and the `EDITOR` vocabulary | CC | **Redone for v4** (ruled 2026-10-05: v3's `EDIT`, a small shell of its own on stdin, is not the editor). The editor is FORTH source, `v4/capsule/editor.fth`, which the node compiles itself; it is a vocabulary, `EDITOR`, whose words are used at the ordinary prompt. `n EDIT` lists screen `n` and makes `EDITOR` the `CONTEXT` vocabulary. Then: `L` list the screen, `N` `B` the next and the one before, `n T` type a line, `n P text` put the rest of the input line on line `n`, `n E` erase, `n D` delete (the lines below move up; the line is held in `PAD`), `n S` spread (a blank line at `n`), `n H` hold, `n R` replace from `PAD`, `n I` insert `PAD`'s line, `WIPE` blank the screen, `DONE` write what changed and go back to FORTH. Every change is `UPDATE`d. In FORTH itself are v3's three as they were — `L ( n -- )` a line, `S ( baddr u n -- )` a line set from a string, `SHOW` the screen — and `COPY ( from to -- )`. A line outside 0–15 is `ABORT"`ed with `Line out of range`. Executed on the golden model's host node (2026-10-05): the source compiled line by line, every command, and what reaches storage. A screen editor, with a cursor, is not written. |
|
||
|
||
### 5.17 Defining words and the compiler
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `:` `;` `EXIT` `IMMEDIATE` `STATE` `[` `]` `LITERAL` `COMPILE` `[COMPILE]` | CC | Source in `v4/capsule/compile.v4`, all to FORTH-79. `:` makes an entry and starts compiling; the word cannot be found until `;`, so a redefinition can use the old word. `;` lays the return, reveals the word and stops compiling. `LITERAL` compiles the number on the stack when compiling. `COMPILE xxx` lays code that compiles `xxx` when the word it is in runs; `[COMPILE] xxx` compiles `xxx` though it is immediate. Executed on the golden model's host node (2026-10-04) against results recorded from the v3 binary; v3's `COMPILE` does not work (`: C1 COMPILE DUP ; IMMEDIATE : U3 C1 + ;` gives `DUP: Stack underflow`), reported, not fixed. |
|
||
| `CREATE` `VARIABLE` `CONSTANT` `DOES>` | CC | FORTH-79. A data word's code is one instruction word, a call to its run-time routine, and its parameter field is the cell after it: `(DOVAR)` is `pop ;` (the call's return address is the parameter field's address) and `(DOCON)` is `pop a! @ ;`. `VARIABLE` allots one cell, set to 0. `DOES>` lays a call to `(DOES)` and then `pop`: `(DOES)`, run by the defining word, rewrites the newest entry's code into a call to the code after it and returns from the defining word; that code's `pop` fetches the parameter field address. Executed on the golden model's host node (2026-10-04) against results recorded from the v3 binary. |
|
||
| `FORGET` `FENCE` | CC | Source in `v4/capsule/system.v4`. `FORGET xxx` is FORTH-79: `xxx` and every word defined after it are removed and their space is free again. A word that is not there is an error (`UNKNOWN WORD`), and so is one that starts below the address in the variable `FENCE` — where the capsule's own words are — which is D-18's code 10, `Protected word`. v3's `FORGET DUP` succeeds and takes `FORGET` itself with it. Executed on the golden model's host node (2026-10-04), including a transcript of the v3 binary. |
|
||
| `LIT` | OP | `@p` |
|
||
| `does_rt` | RET | Internal helper; `DOES>` is implemented by the compiler capsule. |
|
||
|
||
**How a word is compiled.** v4 code is native: a colon definition is instruction words, and
|
||
compiling a word into one lays down a call to it. Two flags on an entry change that. An **in-line**
|
||
word's code is a straight run of opcodes and literals ending in `;`, and that run is copied in place
|
||
of a call; this is fate IN (§0), and everything that touches the return stack must be in line, since a
|
||
call would bury what it works on. A **compile-only** word may not be executed by the interpreter.
|
||
`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 vocabulary at the prompt.** The host node's capsule is `v4/capsule/`, loaded in this order: `core.v4` (bytes, console, strings the rest rest on), `input.v4`, `dict.v4`, `codegen.v4`, `compile.v4`, `quit.v4` (the prompt, faults and errors), `forth.v4` (stack, arithmetic, division, `DEPTH` `PICK` `ROLL`), `numout.v4` (number output, `.S`), `system.v4` (vocabularies, `WORDS`, `FORGET`, `COLD`, `DEFER`), then the two files of FORTH source the node compiles itself, `editor.fth`, `tools.fth` (`SEE`) and `ACL.fth` (the access control policy); `blocks.v4` (mass storage and `LOAD`), `log.v4` (logging), `acl.v4` (access control), `qmath.v4` (the Q48.16 words of §5.26: `Q.FROM-INT Q.TO-INT Q.1 Q.0 Q.SCALE Q.+ Q.- Q.* Q./ Q.ABS Q.NEG Q.= Q.< Q.> Q.0= Q.MAX Q.MIN Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS Q.PRINT`; `DUMP` is in `numout.v4`) and `words.v4` (the stack, comparison, shift, double, mixed and string words of §5.1–5.9: `2SWAP 2OVER 2ROT 2>R 2R> 2R@ 2@ 2! -! 0<> 0> <> <= >= U< U> ABS MAX MIN WITHIN LSHIFT RSHIFT D- DABS D0= D0< D= D2* D2/ D< DMAX DMIN M+ M- CMOVE> MOVE FILL ERASE BLANK -TRAILING COMPARE SEARCH SCAN SKIP ?TERMINAL TRUE FALSE INVERT NOP`). Each is the definition this document gives; `tests/test_host_quit.c` runs them from the prompt against transcripts of the v3 binary. From the prompt, 122 results of the Q words printed by `Q.PRINT` — which shows all sixteen bits of a fraction — are digit for digit v3's, at both cell widths. Under D-18, `Q./` by zero and `Q.SQRT` and `Q.LOG` outside their domain leave the result D-11 and D-12 give and then raise an error (codes 11 `Division by zero` and 12 `Argument out of range`); v3 returns 0 and says nothing. As v3, `LSHIFT` and `RSHIFT` with a count that is negative or as large as the cell is wide are an error (D-18, code 9, `Shift count out of range`). `M+`, like `M-` and `M/MOD`, takes its double in the standard order, low cell first; v3 takes it low cell on top.
|
||
|
||
**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 **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 **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`
|
||
takes N shifts to reach the bit that moves into `A`, and the loop ends first).
|
||
|
||
A programme that v3 runs with more than that on its stacks does not run here; that is D-2, not the
|
||
compiler.
|
||
|
||
**The code generator** (`v4/capsule/codegen.v4`) is what these words are built on. It packs opcodes,
|
||
literals and branches into instruction words at `HERE`, by the rules of §1.2 and §2: slots filled left
|
||
to right, unused slots `nop`, a literal's value in the cell after its word, `;` `ex` and any branch
|
||
closing the word. A branch goes only in a slot whose address field reaches all of the node's memory
|
||
(slots 0–2 on a 16,384-word host node, 0–3 on a 1,024-word one); otherwise it starts the next word, so
|
||
no reach check is ever needed.
|
||
|
||
| Word | Does |
|
||
| --- | --- |
|
||
| `(OP,)` | `( op -- )` any opcode that is neither a branch nor `@p` |
|
||
| `(LIT,)` | `( x -- )` `@p` and its value |
|
||
| `(LABEL)` | `( -- addr )` closes the word being built; the address of the next: a branch target |
|
||
| `(BRANCH,)` | `( target op -- )` a branch to a known address; `(JUMP,)` and `(CALL,)` `( addr -- )` are the two commonest |
|
||
| `(BRANCH>)` `(RESOLVE)` | `( op -- ref )` a branch whose address is not known yet, and `( target ref -- )` filling it in |
|
||
| `(FLUSH)` `(CG-RESET)` | append the word being built; forget it |
|
||
|
||
Executed on the golden model's host node (2026-10-04) by laying the same programmes down with the text
|
||
assembler on one node and with the code generator, running, on another, and comparing memory word for
|
||
word: every opcode, literals and branches in every slot position, and 600 random programmes. What it
|
||
lays down was then run.
|
||
|
||
### 5.18 Control flow
|
||
|
||
| Word | Fate | v4 definition / notes |
|
||
| --- | --- | --- |
|
||
| `IF` `ELSE` `THEN` `BEGIN` `UNTIL` `AGAIN` `WHILE` `REPEAT` `DO` `?DO` `LOOP` `+LOOP` | CC | Compile-time structure words, immediate and compile-only; source in `v4/capsule/compile.v4`. Expansions per §2 and below. What an opening word leaves for the word that closes it goes on a **control-flow stack in memory** (32 cells), not on the data stack, with a number saying which word left it; a closing word that finds the wrong number, or nothing, abandons the line. A loop that tests at its end (`UNTIL`) must drop the flag on both ways out, so `BEGIN` lays `jump L0 Ld: drop L0:` and `UNTIL` lays `if Ld drop`. Executed on the golden model's host node (2026-10-04): the code laid down for each is word for word what the text assembler makes of the expansion given here, and the programmes run give what the v3 binary gave. |
|
||
| `LEAVE` `I` `J` `UNLOOP` | CC | In-line words (below), compile-only. |
|
||
| `CASE` `OF` `ENDOF` `ENDCASE` | CC | Source in `v4/capsule/compile.v4`; not FORTH-79, as v3. The value chosen on stays on the stack through the clauses. Each `x OF a ENDOF` is laid as `over xor if M drop jump N M: drop drop a jump END N:`, and `ENDCASE` lays the `drop` that the part after the last `ENDOF` leaves the value for, with `END` after it. The control-flow stack holds the jumps to `END`, how many there are, and the tag 7; inside a clause, the jump to `N` and 8. They nest, and a word out of place is a `Control structure mismatch`. Executed on the golden model's host node (2026-10-04), including transcripts of the v3 binary. |
|
||
| `EXIT` | OP | `;` |
|
||
| `(BRANCH)` | OP | `jump` |
|
||
| `(0BRANCH)` | IN | `if L … drop` (§2) — executed on the golden model (2026-10-03) as `IF 1 ELSE 2 THEN`, including results recorded from the v3 binary. |
|
||
| `(DO)` | IN | `over push push drop` (R: limit index) — `SWAP push push` without the `SWAP`. The body always runs once, as v3. Executed on the golden model (2026-10-03). |
|
||
| `(?DO)` | IN | `over over xor if SKIP drop (DO) … body and loop end … jump PAST SKIP: drop drop drop PAST:` — skips the loop when index = limit, as v3. Executed on the golden model (2026-10-03). |
|
||
| `(LOOP)` | IN | See below. Adds 1 and goes round again while index < limit, signed, as v3. Executed on the golden model (2026-10-03) against C on every pair of 14 loop ends, and sequences recorded from the v3 binary. |
|
||
| `(+LOOP)` | IN | See below. `( n -- )`: adds `n` and goes round again while index < limit for `n >= 0`, while index >= limit for `n < 0`, as v3. Executed on the golden model (2026-10-03) against C on every pair of 14 loop ends with 11 steps, and sequences recorded from the v3 binary. Clobbers `A`. |
|
||
| `(LEAVE)` | IN | `pop pop drop dup push push` — FORTH-79 (ruled 2026-10-04): the limit is set equal to the index, so the loop ends at the next `LOOP` or `+LOOP`; the index is unchanged and the rest of the body still runs. v3's `LEAVE` left the loop at once (FORTH-83's behaviour) and, having set the index to the limit, left both on its return stack, so a `LEAVE` in an inner loop made the outer `LOOP` count the inner loop's leftovers and stop: `: T 3 0 DO 10 0 DO I . I 1 = IF LEAVE THEN LOOP I . LOOP ;` prints `0 1 10` in v3 and `0 1 0 0 1 1 0 1 2` here. Executed on the golden model (2026-10-04) in `LOOP` and `+LOOP`, up, down and with a step of 0. |
|
||
| `I` | IN | `pop dup push` — executed on the golden model (2026-10-03). |
|
||
| `J` | IN | `pop pop pop dup push a! push push a` — the outer index waits in `A` while the inner pair goes back, so `J` needs no return entry beyond the two loops' own (with two in-line `SWAP`s it needed one more). Executed on the golden model (2026-10-03), including a sequence recorded from the v3 binary. Clobbers `A`. |
|
||
| `UNLOOP` | IN | `pop drop pop drop` — executed on the golden model (2026-10-03) as `UNLOOP EXIT`, including a sequence recorded from the v3 binary. |
|
||
|
||
```
|
||
<less>, used by both loop ends: ( i' lim -- i' lim s ), the top bit of s set
|
||
exactly when i' < lim, signed -- i' itself when the two differ in sign, else
|
||
i' - lim (the subtraction in line):
|
||
over over xor -if SAME
|
||
drop over jump T
|
||
SAME: drop over over -
|
||
T:
|
||
|
||
(LOOP) expansion:
|
||
pop 1 + pop \ index' limit
|
||
<less> \ index' limit s
|
||
-if Lexit \ not less: done
|
||
drop push push \ R: limit index'
|
||
jump Lbody
|
||
Lexit: drop drop drop
|
||
|
||
(+LOOP) expansion: ( n -- )
|
||
dup a! pop + pop \ index' limit A: n
|
||
<less> a xor \ top bit set: go round again
|
||
-if Lexit
|
||
drop push push
|
||
jump Lbody
|
||
Lexit: drop drop drop
|
||
```
|
||
|
||
`(LOOP)` goes round again while `index' < limit`, signed, which is v3's rule. So `0 5 DO … LOOP` runs
|
||
once, as in v3; a test for `index' = limit`, which this document had first, would run it round the whole
|
||
number circle. `(+LOOP)` goes round again exactly when `s` and `n` differ in sign: `index' < limit` for
|
||
`n >= 0` and the opposite for `n < 0`. A loop costs its word two return-stack entries while it runs.
|
||
|
||
**New in v4:** `FOR`, `NEXT`, and `UNEXT` are native (§2). Counted loops that don't need an ascending
|
||
index should use them; they cost one instruction per iteration.
|
||
|
||
### 5.19 StarForth extensions
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `ENTROPY@` `ENTROPY!` | MM | Per-call-target heat table (D-6). |
|
||
| `WORD-ENTROPY` `RESET-ENTROPY` `TOP-WORDS` | CC | Reports over the heat registers. |
|
||
| `(-` `INIT` | CC | |
|
||
| `VERSION` | CC | Prints `StarForth v4.0.0 F18 32-bit` (or `64-bit`). Source in `v4/capsule/system.v4`. |
|
||
| `SEED` `RANDOM` | CAP | Deterministic PRNG (xorshift) in capsule code, reproducible from the seed. |
|
||
| `WAIT` | CAP | Loop until the `ANTICLOCK` register has advanced `n`. |
|
||
| `HEARTBEAT-TICKS@` | MM | `HEARTBEAT` register. |
|
||
| `ZUSE-AUTHENTICATE` `ZUSE-SESSION?` `ZUSE-PUBKEY@` `ZUSE-CERT-INSTALLED?` | HERA | |
|
||
|
||
### 5.20 Word-level ACL
|
||
|
||
On the host node, as v3 has it at its prompt (2026-10-05). Source in `v4/capsule/acl.v4` (the fields),
|
||
`compile.v4` (the check) and `ACL.fth` (the policy, FORTH source, which is v3's `capsules/ACL.4th`).
|
||
Executed on the golden model's host node against transcripts of the v3 binary. Between nodes, access control
|
||
is still the router's and Hera's (§6); nothing here decides that.
|
||
|
||
**The fields.** Each entry's flags cell (§5.13) also holds: bit 5 *denied*, bit 6 *pinned*, bits 8–15 the
|
||
*mode* (0 TTL, 1 STRICT), bits 16–31 the *TTL*, a count of uses left before the next recheck. A new entry has
|
||
all of them zero. v3's TTL is 32 bits; its own policy never sets more than 65535, which is the most 16 bits
|
||
hold.
|
||
|
||
**The check.** `INTERPRET` runs it on every word it is about to execute or compile: if the TTL is 0 the word
|
||
is rechecked, otherwise the TTL is counted down; then a denied word is refused with v3's line —
|
||
`WARN: ACL: denied 'xxx'` — the data stack emptied and the line ended. The recheck is the word whose address
|
||
is in the variable `ACL-HOOK`; with none there, the word is allowed and given the longest TTL. `ACL.fth` puts
|
||
its `ACL-RECHECK` there (v3 finds it by name each time).
|
||
|
||
**Where this is weaker than v3.** v3's inner interpreter checks every execution, inside definitions too. v4
|
||
code is native: a compiled call goes straight to its word, and no opcode asks permission. So v4 checks a
|
||
word when it is *compiled* as well as when it is interpreted — a denied word cannot be built into anything
|
||
new — but a call compiled while the word was allowed is not checked again when it runs. Closing that needs
|
||
the check in the node itself (the `call` opcode), which is a hardware decision and is not taken here.
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `ACL-MODE@` `ACL-MODE!` `ACL-TTL@` `ACL-TTL!` `ACL-ALLOW@` `ACL-ALLOW!` `ACL-PINNED?` `ACL-PIN` | CAP | As v3, on an xt. The `!` words do nothing to a pinned word. `ACL-TTL!` takes a negative count as 0 and one above 65535 as 65535; `ACL-MODE!` keeps the low byte. An xt of 0 is D-18's code 16, `Not a word`. |
|
||
| `ACL-INHERIT` | CAP | `( src dst -- )`, as v3: `dst` takes `src`'s mode, is unpinned, allowed, TTL 0. The one word that unpins. |
|
||
| `ACL-INIT-PRIMITIVES` | CAP | As v3: every word that is not pinned, in every vocabulary, back to TTL mode, TTL 0, allowed. |
|
||
| `ACL-HEAT@` | MM | `( xt -- heat )`. The node's heat counters are not readable from a programme yet (D-6): 0, so `ACL.fth` gives every word its base TTL of 256. |
|
||
| `ACL-WORD-ID` | CC | `( xt -- n )`: a number for the word that never changes. Here that is its address. |
|
||
| `ACL-HOOK` | CAP | Variable: the xt of the recheck word, or 0. Not in v3. |
|
||
| `ACL-STRICT` `ACL-TTL-MODE` `ACL-TTL-COMPUTE` `ACL-RECHECK` `ACL-BOOT` `ACL-ENTRY` `HERMES-CHANNEL-OPEN?` and the constants | CC | `ACL.fth`: v3's policy file, word for word but for `ACL-BOOT`, which pins only the words v4 has (`EXEC` and `BYE` are not on this node) and sets `ACL-HOOK`. Loading the file runs `ACL-BOOT`; a system that does not load it has no access control. `COLD` removes it. |
|
||
|
||
### 5.21 Physics: benchmark and diagnostics
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `BENCH-DICT-LOOKUP` `PHYSICS-CACHE-STATS` `PHYSICS-TOGGLE-CACHE` `PHYSICS-RESET-STATS` `PHYSICS-BUILD-INFO` | RET | Measure v3's software dictionary and hot-words cache, which do not exist on a node. |
|
||
| `PHYSICS-BAYESIAN-REPORT` | CAP | Host node only; kept as an analysis tool. |
|
||
| `PHYSICS-WORD-METRICS` `PHYSICS-CALC-KNOBS` `PHYSICS-BURN` `PHYSICS-SHOW-FEEDBACK` | RET | Never registered in v3. |
|
||
|
||
### 5.22 Physics: pipelining diagnostics
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `PIPELINING-*` (all six) | RET | Return as MM reports if D-6 adds transition counters. |
|
||
|
||
### 5.23 Physics: freeze, heat, and decay
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `FREEZE-WORD` `UNFREEZE-WORD` `FROZEN?` | MM | Freeze bit in the heat table. |
|
||
| `HEAT@` `HEAT!` | MM | Test-only write retained. |
|
||
| `SHOW-HEAT` `ALL-HEATS` | CC | |
|
||
| `DECAY-RATE@` | MM | Governor parameter register. |
|
||
| `FREEZE-CRITICAL` | CAP | Freezes the core set; the list is rewritten for v4 names. |
|
||
|
||
### 5.24 Dictionary heat optimisation
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `HEAT-PERCENTILES` `LOOKUP-STRATEGY@` `LOOKUP-STRATEGY!` `REORG-BUCKETS` `SHOW-HEAT-OPTIMIZATION` `COMPARE-LOOKUPS` | RET | Lookup strategy is a software-dictionary concern. The host compiler may keep a heat-ordered dictionary internally, but these words are not carried forward. |
|
||
|
||
### 5.25 Logging
|
||
|
||
A message has a level, and is printed if its level is at or below `LOG-LEVEL`. Source in
|
||
`v4/capsule/log.v4`; executed on the golden model's host node (2026-10-05) against transcripts of the v3
|
||
binary.
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `LOG-ERROR` … `LOG-DEBUG` (levels) | IN | Constants, as v3: `LOG-ERROR` 0, `LOG-WARN` 1, `LOG-INFO` 2, `LOG-TEST` 3, `LOG-DEBUG` 4. |
|
||
| `LOG-LEVEL!` `LOG-LEVEL@` | CAP | A variable, 2 (`LOG-INFO`) at switch-on and after `COLD`. As v3, `LOG-LEVEL!` takes a level below 0 as 0 and one above 4 as 4. |
|
||
| `LOG-ERROR"` … `LOG-DEBUG"` | CC | `LOG-xxx" text"` logs the text now, if interpreting, or when the word it is compiled into runs. Compiled as `."` is: the level as a literal, a call to `(LOG")`, and the text after it. |
|
||
| `LOG-*-STR` | DEV | `( baddr u -- )`: log a string at that level. On the mesh this is the log-ring service on the recorder (the ARM); until then it goes to the console. |
|
||
| `(LOG")` | CC | The run-time word of `LOG-xxx"`, compile-only. It stands for v3's five `(do-log-*)`. |
|
||
| `(LOG-APPEND-RAW)` | DEV | |
|
||
|
||
**What is printed** is v3's line without its time of day, which a node has not got: the level's colour, its
|
||
name and a colon, the colour off, the text, a new line — `ESC[32mINFO: ESC[0mtext` — with red for `ERROR`,
|
||
yellow `WARN`, green `INFO`, magenta `TEST`, blue `DEBUG`. v3's `LOG-DEBUG` level also turns on the
|
||
interpreter's own trace of every word; v4 has no such trace. A string logged with a negative length is
|
||
D-18's `Negative count` when its level is shown, and is ignored when it is not.
|
||
|
||
### 5.26 Q48.16 fixed-point math
|
||
|
||
A Q48.16 value is 64 bits, so on a 32-bit node it occupies **two cells** and every Q word is a double
|
||
word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values are signed.
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `Q.+` `Q.-` | CAP | `D+`, `D-` — executed on the golden model (2026-10-02) against v3's `q48_add`/`q48_sub` (`uint64_t` wrapping): bit-for-bit at 32-bit cells; at 64-bit cells the low cell is v3's, per D-10. |
|
||
| `Q.*` | CAP | `SWAP push over over UM* drop push push over pop -if L1 SWAP jump L2 L1: SWAP drop 0 L2: pop SWAP - push push over pop UM* pop + ROT pop dup push over -if L3 drop dup jump L4 L3: drop 0 L4: push UM* pop - D+ ROT pop UM* SWAP push 0 D+ pop push over pop SWAP Q.TO-INT push Q.TO-INT pop SWAP` (`SWAP` and `ROT` in line) — `floor(a*b / 2^16)`, signed (D-8), cut to two cells. Cells 0–2 of the product come from the unsigned cell products `a1*b1` (low cell), `a0*b1`, `a1*b0`, `a0*b0`, in that order, each input dropped after its last use and `b0` waiting on the return stack. Reading `a1` and `b1` as signed takes `(b1<0 ? a0 : 0)` and `(a1<0 ? b0 : 0)` off cell 2; each is folded in as soon as its operands are adjacent. The result is cells 0–2 shifted right 16 with `Q.TO-INT` twice. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference, and against v3's `q48_mul` for non-negative operands. Leaves its caller 3 data cells under its arguments and 3 return entries. Clobbers `A`. |
|
||
| `Q./` | CAP | `over over OR if ZERO drop push over pop SWAP over xor (Q/) 4 + b! !b -if L1 DNEGATE L1: push push -if L2 DNEGATE L2: pop pop dup if CHK drop jump DIV CHK: drop over -131072 and if CHK2 drop jump DIV CHK2: drop push push dup pop dup push N-18 FOR 2* UNEXT over over xor -if SAME drop drop -if NOOV jump OV SAME: drop push inv pop + inv -if OV NOOV: drop pop pop jump DIV OV: drop drop drop pop pop drop drop (Q/) 4 + b! @b -if OVP drop 0 MSB ; OVP: drop -1 MAXHI ; DIV: (UQ/) (Q/) 4 + b! @b -if L3 drop DNEGATE ; L3: drop ; ZERO: drop drop drop NODE-ERROR b! -1 !b over over OR if Z0 drop -if ZP drop drop 0 MSB ; ZP: drop drop -1 MAXHI ; Z0: drop ;` — D-11: `a * 2^16 / b`, rounded toward zero; saturated to Q max / Q min on overflow; on division by zero, Q max / Q min by the dividend's sign (0 for `0 / 0`) and `NODE-ERROR` set. The sign of the result waits in `(Q/)`. Overflow needs no division: the quotient reaches `2^(2N-1)` exactly when `|a| * 2^16 >= |b| * 2^(2N-1)`, possible only for `|b| < 2^17`, where it is `a1 >= b0 << (N-17)` unsigned. `MSB` and `MAXHI` are the high cells of Q min and Q max. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference (including `NODE-ERROR`), on width-native edge values, and against v3's `q48_div` for non-negative `a < 2^48`. Leaves its caller 3 data cells under its arguments and 3 return entries. Clobbers `A` and `B`. |
|
||
| `(UQ/)` | CAP | Internal to `Q./`: unsigned `floor(a * 2^16 / b)` for `b != 0` and no overflow. Restoring division, `2N+16` steps, on one shifting register — quotient (starting as `a`) above remainder — with the quotient and divisor in `(Q/)` so the stacks carry only the remainder, the quotient bit and the loop count. Each step's quotient bit enters at the next step's shift; with no overflow the bits leaving the quotient in the last 16 steps are zeros. Trial subtraction by in-line sign tests (`x - y` with `y` on top is `push inv pop + inv`), including the full double subtraction with its borrow, so the loop calls nothing but `D2*C`. Executed on the golden model (2026-10-02). Clobbers `A` and `B`. |
|
||
| `D2*C` | CAP | `( lo hi cin -- lo' hi' cout )`: the double shifted left one bit, `cin` entering at the bottom and `cout` the bit leaving the top (both 0 or 1). `a! dup -if L1 drop 1 jump L2 L1: drop 0 L2: push over -if L3 drop 1 jump L4 L3: drop 0 L4: over + + push 2* a + pop pop` — `cin` waits in `A`, and `2hi + m` is `over + +`, so it needs one return entry. Executed on the golden model (2026-10-02). Clobbers `A`. |
|
||
| `(Q/)` | CAP | Variable, 5 cells: `Q./`'s quotient register, divisor and result sign. Like `BASE` and the hold buffer, it lives in node memory. |
|
||
| `Q.ABS` `Q.NEG` | CAP | `DABS`, `DNEGATE` — executed on the golden model (2026-10-02) against v3's `q48_abs` and `0 - q`: bit-for-bit at 32-bit cells, including v3's wrap of Q min to itself; at 64-bit cells the low cell is v3's and the high cell is the true sign, per D-10. |
|
||
| `Q.COS` | CAP | v3's `q48_cos_approx`: on `xu = |(Q.REDUCE) x|`, `1 - xu^2/2! + xu^4/4! - …` to `n = 10`, run by `Q.SIN`'s loop: `(Q.REDUCE) -if A1 inv 1 + A1: 0 (QT) b! !b 0 over 0 Q.* drop (QT) 1 + b! !b 65536 (QT) 2 + b! !b 65536 (QT) 3 + b! !b 2 (QT) 4 + b! !b -1 (QT) 5 + b! !b jump TRIG` — term and sum start at 1.0, `n` at 2, and the sign cell is 0 so nothing is negated at the end. It jumps into the loop rather than calling it, so it costs no extra return entry. Executed on the golden model (2026-10-03): bit for bit v3's result on angles of every size and sign. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||
| `Q.SIN` | CAP | v3's `q48_sin_approx`: on `r = (Q.REDUCE) x` and `xu = |r|`, `xu - xu^3/3! + xu^5/5! - …` to `n = 11`, each term the last times `xu^2` over `n(n-1)`, stopping once a term is below 10 ulp; negated if `r < 0`. After the reduction every value fits one cell, so the variable `(QT)` holds single cells and the only calls are `(Q.REDUCE)`, `Q.*`, `Q./` and `UM*`. The Taylor loop is written so that `Q.COS` can jump into it. Executed on the golden model (2026-10-03): bit for bit v3's result on angles of every size and sign. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||
| `(Q.REDUCE)` | CAP | `( x -- r )`, v3's `q48_reduce_angle`: the angle reduced to one cell, `-pi <= r <= pi` with `pi = 205887`, `2pi = 411774` — the remainder of `x / 2pi` with the sign of `x`, then one step of `2pi` back into range. `x / 2pi` does not fit a cell, but only the remainder is wanted: `|x| mod 2pi` is two `UM/MOD` steps, the high cell first and its remainder then leading the low cell. `dup (QT) b! !b -if L1 DNEGATE L1: 0 411774 UM/MOD drop 411774 UM/MOD drop (QT) b! @b -if P1 drop inv 1 + jump J1 P1: drop J1: dup -205888 + -if BIG drop dup 205887 + -if OK drop 411774 + ; OK: drop ; BIG: drop -411774 + ;` Executed on the golden model (2026-10-03), bit for bit v3's. Clobbers `A` and `B`. |
|
||
| `(QT)` | CAP | Variable, 6 cells: `Q.SIN` / `Q.COS`'s sign, `x^2`, term, sum, `n` and subtract flag. |
|
||
| `Q.LOG` | CAP | v3's `q48_log_approx`: `x = 2^k * m` with `1.0 <= m < 2.0`; `ln m` by up to 6 Newton rounds on `e^y = m` from `y = m - 1.0`, stopping once `|m - e^y| < 100` ulp; result `y + k * ln 2` with `ln 2 = 45426`. `e^y` is `Q.EXP`'s Taylor loop **written in line** (flattened, ruled 2026-10-02), so `Q.LOG` calls only `Q.*`, `Q./` and `UM*` and the call chain is one level shorter than through `Q.EXP`. After the reduction `m`, `y`, the Taylor term and its sum each fit one cell at either width, so the variable `(QL)` holds single cells. `x <= 0` returns 0 and sets `NODE-ERROR` (D-12). Executed on the golden model (2026-10-03): bit for bit v3's result for every `x > 0` tested, including a sweep of the reduced `m`. v3's clamp `y = max(y - corr, 0)` is kept for fidelity but cannot fire: over all 65536 values of `m` it is never reached, and the iteration never needs more than 4 rounds. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||
| `(QL)` | CAP | Variable, 7 cells: `Q.LOG`'s `m`, `y`, `k`, rounds left, Taylor term (then `delta`), sum, and `n * 1.0`. |
|
||
| `Q.SQRT` | CAP | v3's `q48_sqrt_approx`: Newton from `x0 = q/2 + 0.25`, up to 8 rounds of `x' = (x + q/x) / 2`, returning `x` as soon as `|x' - x| < 10` ulp; `sqrt(0) = 0`, `sqrt(1.0) = 1.0`. `q < 0` returns 0 and sets `NODE-ERROR` (D-12). `q`, `x` and the rounds left live in the variable `(QR)`. The halving is a logical double shift, `push a! 0 pop +* MAXHI and push drop a pop`. Executed on the golden model (2026-10-02): bit for bit v3's result for `0 <= q < 2^48`; above that v3's `q48_div` saturates and v4 divides correctly, so they part. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||
| `(QR)` | CAP | Variable, 5 cells: `Q.SQRT`'s `q`, `x` and rounds left. |
|
||
| `Q.EXP` | CAP | v3's `q48_exp_approx`: `1 + x + x^2/2! + …` to 10 terms on `x = |q|`, stopping once a term is below 50 ulp; `1.0 Q./ e^|q|` for `q < 0`; `e^0 = 1.0`; `|q| >= 16.0` gives 0 (`q < 0`) or **Q max** (v3 returned all ones, which reads as −ulp under D-8). `x`, the term, the sum, the sign and the term index live in the variable `(QE)`, so only `Q.*` and `Q./` arguments sit on the stacks. Executed on the golden model (2026-10-02): bit for bit v3's result for `|q| < 16.0` (at 64-bit cells as the same value, high cell 0). Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||
| `(QE)` | CAP | Variable, 8 cells: `Q.EXP`'s `x`, term, sum, sign and term index. |
|
||
| `Q.FROM-INT` | CAP | `push 0 a! 0 pop 15 FOR +* UNEXT push drop a pop` — `n * 2^16` as a signed double. `+*` with `S = 0` never adds, so each step is an exact arithmetic right shift of `T:A`; starting from `T:A = n:0` (that is, `n * 2^N`) and shifting `N-16` bits leaves `n * 2^16`. The count is `N-17`: 15 at 32-bit cells, 47 at 64. Negative values are no longer clamped to 0. Executed on the golden model (2026-10-02): matches v3's `q48_from_u64` for `n >= 0` (at 64-bit cells in the low cell, where v3 wraps for `n >= 2^47`, per D-10). Clobbers `A`. |
|
||
| `Q.TO-INT` | CAP | `push a! 0 pop 15 FOR +* UNEXT drop drop a` — the same `+*` shift, 16 bits at every cell width, leaving the low cell in `A`. Rounds toward minus infinity, as v3's arithmetic shift does (−1.5 gives −2). Executed on the golden model (2026-10-02): v3's `q48_to_u64` exactly at 64-bit cells, its low cell at 32. Clobbers `A`. |
|
||
| `Q.1` `Q.0` `Q.SCALE` | IN | Double-cell constants, placed in line as two literals, low cell first: `Q.1` and `Q.SCALE` are `65536 0` (1.0), `Q.0` is `0 0`. v3's are 65536, 0 and 65536. Executed on the golden model (2026-10-03): the values match v3's, `Q.1 Q.TO-INT` is 1, `1 Q.FROM-INT` is `Q.1`, and `q Q.1 Q.*` and `q Q.0 Q.+` return `q`. |
|
||
| `Q.=` `Q.<` `Q.>` `Q.0=` `Q.MAX` `Q.MIN` | CAP | `D=`, `D<`, `2SWAP D<` (for `Q.>`), `D0=`, `DMAX`, `DMIN`. Signed (D-8); v3 compared unsigned, so v3 agrees only when both values have the same sign. Executed on the golden model (2026-10-02). `Q.>` was given here as `SWAP D<`, which swaps single cells, not Q values, and gave a wrong answer in 11702 of 20169 test cases. |
|
||
| `Q.PRINT` | CAP | `( q -- )`: the integer part, a point, the five digits `floor(frac * 100000 / 65536)`, then one space, as v3; always decimal, whatever `BASE` is, and `BASE` is put back. Signed (D-8): a negative value prints `-` and its magnitude (−1.5 prints `-1.50000`), where v3 printed it as a large unsigned number. `dup (QP) 3 + b! !b -if A DNEGATE A: (QP) 2 + b! !b dup (QP) 1 + b! !b BASE b! @b (QP) b! !b 10 BASE b! !b 65535 and 4 FOR dup 2* 2* + UNEXT 10 FOR 2/ UNEXT 0 <# # # # # # drop drop 46 HOLD (QP) 1 + b! @b (QP) 2 + b! @b dup push push a! 0 pop 15 FOR +* UNEXT drop drop a pop 15 FOR 2/ UNEXT HIMASK and #S (QP) 3 + b! @b SIGN #> (QP) b! @b BASE b! !b jump OUT` — `OUT` is the `TYPE jump SPACE` at the end of `.R`. The fraction is `frac * 3125 / 2048`, which stays inside a 32-bit cell where `frac * 100000` would not; times 3125 is times 5 five times. The integer part is `|q|` shifted right 16 as a double: its low cell by `Q.TO-INT`'s `+*` shift in line, its high cell by `2/` and `HIMASK`, which clears the top 16 bits. Executed on the golden model (2026-10-03) against a C reference, on every seventh fraction, and against a transcript of the v3 binary for non-negative values. Leaves its caller 4 data cells and 3 return entries. Clobbers `A` and `B`. |
|
||
| `(QP)` | CAP | Variable, 4 cells: `Q.PRINT`'s saved `BASE`, `|q|` and the sign. |
|
||
|
||
### 5.27 Inference engine
|
||
|
||
The runtime governor moves into hardware as the multi-level Rolling Window of Truth (see
|
||
`JUSTIFICATION.md`). These words survive on the host node as analysis tools.
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `INFER-RUN` `INFER-WINDOW@` `INFER-DECAY@` `INFER-VARIANCE@` `INFER-FIT@` `INFER-EARLY-EXIT@` | CAP | Host node only. Ported from `inference_words.c`. |
|
||
| `Q.VARIANCE` `INFER-DECAY-SLOPE` `INFER-WINDOW-WIDTH` `WINDOW-DIVERSITY` | CAP | Host node only. |
|
||
| `L8-MODE` `L8-UPDATE` `L8-APPLY` `L8-TABLE-FORCE` | MM | Jacquard selector state becomes governor registers. `L8-TABLE-FORCE` remains the DoE override. |
|
||
| `BAYES-*` (all six) | RET | Model the v3 hot-words cache and bucket search. |
|
||
|
||
### 5.28 DEFER and IS
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `DEFER` `IS` `DEFER@` | CC | As v3. `DEFER xxx` makes a data word whose run-time routine fetches the address in its parameter field and goes to it (`pop a! @ … push ;`), so the word it runs returns to `xxx`'s caller and a deferred word is no deeper than a plain one. `' yyy IS xxx` and `DEFER@ xxx` work at the prompt and in a definition. A deferred word with nothing set is D-18's code 15, `Deferred word not set`. Source in `v4/capsule/system.v4`; executed on the golden model's host node (2026-10-05), including transcripts of the v3 binary's behaviour. |
|
||
|
||
### 5.29 – 5.32 Framebuffer, keyboard, TrueType, scrollback
|
||
|
||
| Word | Fate |
|
||
| --- | --- |
|
||
| `PLOT` `FB-WIDTH` `FB-HEIGHT` | DEV |
|
||
| `KBD-SCAN` `KBD-DEBUG` `VKBD-EVENT` `VKBD-DEBUG` `KEY-EVENT` `ALT+TAB` | DEV |
|
||
| `TTF-TEXT` | DEV |
|
||
| `SCROLL-BACK` `SCROLL-FWD` | DEV |
|
||
|
||
### 5.33 Kernel REPL and DoE hooks
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `HB-ON` `HB-OFF` | MM | Recorder-enable register. The fabric pushes DoE rows into a FIFO; the ARM drains it to storage. |
|
||
| `BLK-ATTACH-ACK` `KH-BLK-ATTACH-SEND` `KH-ELEVATE-SEND` | RET | Kernel-Hermes calls. Hermes is now the fabric; these become ordinary packets (§6). |
|
||
|
||
### 5.34 Hera and child-VM words
|
||
|
||
| Word | Fate | Notes |
|
||
| --- | --- | --- |
|
||
| `BIRTH` `KILL` `START` `STOP` `USE` `EXEC` `EJECT` `CONNECT-HERMES` `CONNECT-ARTEMIS` `BYE` | HERA | A VM becomes a node (or a group of nodes). `BIRTH` loads a capsule into a node and releases it. |
|
||
| `VM-EXEC` `VM-CALL` `VM-STEP` | CAP | Built on `SEND` and `RECV` (§6). |
|
||
| `VM-HEAT` `VM-ERROR?` `VM-COUNT` `VM-CONSERVED?` `VM-PHYSICS-STATUS` | MM + HERA | Per-node heat is a register; fleet totals are Hera's. |
|
||
| `SWITCH-MARK-WORK` | RET | Nodes run concurrently; there is no context switch to mark. |
|
||
| `MAMA-VM-ID` `NAME>XT` | HERA, CC | |
|
||
| `CAPSULE-*` `WORKER-BIRTH` `UNATTENDED-BIRTH` `CONSOLE-ATTACH` | HERA | |
|
||
| `MINT` `MINT-SCRATCH` `MINT-SCRATCH-EMIT` `ZUSE-ELIGIBILITY-ADD` `ZUSE-ELIGIBLE?` `ELEVATE-PUBKEY-UNPACK` | HERA | |
|
||
| `RUNCAP-TEST` `PAIR-TEST` | HERA | Diagnostics, kept. |
|
||
| `STADIUM-ADMIT` `STADIUM-EVICT` `STADIUM-RES-PULL` `STADIUM-RES-PUSH` | HERA | Admission and reservoir are Hera's. |
|
||
| `STADIUM-HEAT@` `STADIUM-HEAT!` `STADIUM-RES@` `STADIUM-WORD-HEAT` | MM | |
|
||
|
||
### 5.35 Hosted lifecycle stubs
|
||
|
||
| Word | Fate |
|
||
| --- | --- |
|
||
| `BIRTH` `KILL` `PAUSE` `RESUME` `USE` (hosted) | RET — the hosted v4 golden model implements the real HERA messages. |
|
||
|
||
---
|
||
|
||
## 6. Messaging between nodes
|
||
|
||
Each node has four neighbour ports (`PORT-UP`, `PORT-DOWN`, `PORT-LEFT`, `PORT-RIGHT`) mapped into its
|
||
address space. A read from a port blocks until the neighbour writes; a write blocks until the neighbour
|
||
reads. This is the GA144 model. No instruction is added: a port is an address.
|
||
|
||
### 6.1 Packet format (proposal)
|
||
|
||
| Word | Contents |
|
||
| --- | --- |
|
||
| 0 | Header: destination node (8 bits), type (8), TTL/heat (8), payload length in words (8). |
|
||
| 1 | ACL tag: sender identity fingerprint, checked by the router on every packet. |
|
||
| 2 … | Payload. |
|
||
|
||
The router in each node forwards packets not addressed to it, decrements TTL, and drops a packet whose
|
||
TTL reaches zero or whose ACL tag fails. This is v3 Hermes's per-message ACL check and unconditional TTL
|
||
expiry, moved into logic. A packet type `SOS` is reserved for any node to emit.
|
||
|
||
### 6.2 Send and receive
|
||
|
||
```forth
|
||
: SEND ( addr u port -- ) b! SWAP a! BEGIN dup WHILE 1- @+ !b REPEAT drop ;
|
||
: RECV ( addr u port -- ) b! SWAP a! BEGIN dup WHILE 1- @b !+ REPEAT drop ;
|
||
```
|
||
|
||
`A` walks the buffer and `B` holds the port, so each word moved costs one fetch and one store.
|
||
|
||
---
|
||
|
||
## 7. Memory-mapped registers
|
||
|
||
Addresses are assigned in the node memory map (D-4). Names only here.
|
||
|
||
| Register | Access | Contents |
|
||
| --- | --- | --- |
|
||
| `HEAT-OP[0..31]` | R | Per-opcode retirement heat. |
|
||
| `HEAT-CALL[...]` | R/W | Per-call-target heat, with freeze bit (D-6). |
|
||
| `ANTICLOCK` | R | Virtual tick: a pure function of the execution stream. |
|
||
| `HEARTBEAT` | R | Adaptive heartbeat count. |
|
||
| `GOV-*` | R/W | Governor parameters and Jacquard selector state (`L8-*`, decay rate). |
|
||
| `REC-ENABLE` | R/W | DoE recorder on/off (`HB-ON` / `HB-OFF`). |
|
||
| `PORT-STATUS` | R | Per-port ready flags, for non-blocking polls. |
|
||
| `NODE-ERROR` | R/W | Error register (D-18): a store of a non-zero code raises an error — on a node with a prompt it is a trap that ends the line with the code's message; a store of zero clears it. Originally the arithmetic error flag: set to −1 by `Q./` on division by zero (D-11), by `Q.SQRT` / `Q.LOG` outside their domain (D-12) and by `HOLD` on a bad character or a full buffer (D-13), cleared by writing 0. `VM-ERROR?` reads it. |
|
||
| `CONSOLE-TX` | W | Console transmit: a store sends the low 8 bits of the value as one character. This is what `EMIT` writes to. On the single-node golden model it is a capture register: the character is appended to a buffer on the node and memory is not written (`v4_node_console_attach`, `v4/include/v4/node.h`), so printing words can be run and their output compared with v3's. With the mesh (step 2) it becomes the port to the console node. |
|
||
| `CONSOLE-RX` | R | Console receive: a fetch gives the next pending character, 0–255, and takes it; with none pending it gives −1 and takes nothing. This is what `KEY` reads. On the single-node golden model the characters come from a queue the test feeds (`v4_node_console_input_attach`, `v4_node_console_feed`, `v4/include/v4/node.h`). Only the data fetches `@`, `@+` and `@b` see the register; instruction words and literals at the same address are read as memory. With the mesh (step 2) it becomes the port from the console node, and a read with nothing pending will block instead. |
|
||
| `CONSOLE-STATUS` | R | Console receive status: a fetch gives −1 when a character is pending and 0 when not, and changes nothing. `?TERMINAL` reads it, and `KEY` polls it. On the mesh it is the console port's bit of `PORT-STATUS`. |
|
||
| `DSTACK-DEPTH` | R/W | A fetch gives how many values the data stack holds, the fetch's own push not counted. A store empties the data stack; the value stored is taken off first and ignored. `DEPTH` reads it; `ABORT` stores to it (D-16). |
|
||
| `RSTACK-DEPTH` | R/W | The same for the return stack. `QUIT`, `ABORT` and the error exits store to it before they call anything. |
|
||
| `BLOCK-NUMBER` `BLOCK-ADDRESS` `BLOCK-COMMAND` `BLOCK-STATUS` | R/W | The block storage device on the golden model's host node (D-19). |
|