Commit Graph
28 Commits
Author SHA1 Message Date
rajamesandClaude Opus 5.5 0d91608f39 fix(v4.0.0): EMIT ran past the output buffer; a message's first word was read before there was room for the rest
The two root causes behind what the reviews of the wait found, both in
the code as it stands.  Each with a check that failed first.

EMIT stored its character and only then looked whether the buffer was
exactly full.  A flush that ended in a fault -- the stack too deep to
begin a message -- left the buffer full, and the fault's own message was
then put past its end, with (OUT^) never again at the end to be flushed.
The 28 cells after the buffer happen to be unused, so nothing showed;
the wait had put the ports there, and the node blocked writing to one.
EMIT now sends a buffer it finds full before it stores.

AWAIT, and (GATE) when a neighbour is writing, read a message's first
word and then used stack that was not known to be there.  With 27 or 28
values on the stack, or 28 or 29 calls deep, a line typed during AWAIT
was taken and never answered.  (ROOM) tries six cells and four return
entries first: if they are not there the text ends 'Stack overflow' with
nothing read.  (GATE) tries the three cells its writing needs before a
first word goes; it was safe before only by the order things were done.

v4/tools/depthsweep.py runs every message path of the hosted product at
every depth of both stacks: each line typed must be answered and the
node must come back.  hosted-check runs it.

make -C v4 test and sanitize at both widths, hosted-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:46:05 -04:00
rajamesandClaude Opus 5.5 6015c87157 revert(v4.0.0): step 6d, the wait, is backed out -- two reviews found it unsound
Ruled 2026-10-08.  The engine, the nucleus and their tests are as they
were at dfabfa46, before step 6d; the code of the wait and its tests are
in history at 075385ab..2cf37aab.  The limit of MESH.md 7b.7 stands: a
node that is stuck can hold up its neighbours until Hera kills it.

MESH.md 7c is kept as the record of the design as approved, and 7c.6 says
what was built, what the two reviews found, the cause the findings share
-- a node's message machinery runs on the stacks its text is using, and a
fault abandons whatever was in progress -- and what a second attempt
must settle before anything is built.

Kept from it, in hosted-check: a line that leaves 29 values on the stack,
and WORDS with 26 values on it.  Both hung the products at some commit of
the wait.

make -C v4 test and sanitize at both widths, hosted-check, lint; three
bare-metal boots typing both.  POST 538 of 538, word_count=317,
dict_hash=0xc0769523a47b7dc3 on all six: the hash of step 6c's code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 07:31:40 -04:00
rajamesandClaude Opus 5.5 2cf37aab4f fix(v4.0.0): step 6d after its review -- no error in the middle of the messages waiting, no offer left standing
Ruled 2026-10-08: A, B, C, D and the guard for a half message.  Each with
a scene in test_host_mesh.c that failed first.

A. The words of a message after its first are written from the passes
   over the messages waiting by (!W): an offer and a wait for that offer
   only (a new engine address).  A reader removed in mid-message makes no
   error; the rest is let go and counted.  Before, the error left the
   messages waiting in pieces and the node going round for ever.
B. A store to the wait withdraws every offer; (GATE1) and (PAY-SET) do it
   first.  Before, text begun after a node went round without sleeping
   could go to the port of an older offer: text for one node was done by
   another.
C. What a finished text printed is put with the messages waiting, as how
   it ended is.  With no room either is let go and counted; neither is
   begun as a message from (FINISH) any more, where a stuck node on the
   way held the whole node.
D. Only what a sender is owed from an earlier text -- its output, how it
   ended -- holds that sender's next text back; a GONE does not; and what
   cannot be noted holds nothing.  A node's own messages keep the order
   they were made in.
-  A message half taken in when its writer is removed is let go
   ((MQ-MEND)), not kept as if whole.
-  Smaller: a refused line no longer ends a waiting text 'Interrupted';
   a node's own queued messages may be passed on by 16 nodes, not 15; no
   value is left on the stack when a port goes in mid-refusal.

make -C v4 test and sanitize at both widths, hosted-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 06:34:15 -04:00
rajamesandClaude Opus 5.5 ec817a8264 fix(v4.0.0): a waiting node goes on passing on and paying; KILL is not held by a stuck node
Found in the five-node test, each with a check that failed first:
- A node with answers it could not give to two stuck senders began no
  text at all.  It now holds back only text from a sender whose own
  answer is still with it.
- KILL waited to tell a stuck node, and a typed line that broke it left
  the nodes after it untold.  GONE is put with the messages waiting, as
  how text ended is, and goes when it can.
- So that 'tell, then wait' still works, AWAIT does what (IDLE) does but
  for beginning text: it looks through the messages waiting, offers what
  is to be passed on and what is owed, and sleeps in the wait.  A waiting
  node no longer keeps back what passes through it.
- A node in the wait with an offer on a port that turns out to have
  nothing on it is woken and told so (which port: 2 * V4_PORTS + k); no
  error is raised on it.  (GATE) makes it error 18; (IDLE) and AWAIT let
  the message go.  Before, removing a node raised 'No one on that port'
  on the unrelated text of a neighbour that had something queued for it.

make -C v4 test and sanitize at both widths, hosted-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 01:00:33 -04:00
rajamesandClaude Opus 5.5 1c720ab2c7 fix(v4.0.0): what a node passes on waits with it, offered -- a relay is not held by a stuck node
Found after the last commit: a message being passed on toward a node that
was stuck held the node passing it on in the wait, taking in what came
but serving none of it; text typed for a stuck node left Hera deaf.  And
a node that had done text for a sender that then stuck waited on it to
give its answer.

A node with nothing to do now deals first with what is for itself; then
offers at once the first message for each port and each refusal owed,
and sleeps in the wait.  What is taken is written and done with; the
rest stays with the messages waiting, in order.  How text ended is put
with the messages waiting, not written from (FINISH).  The stack is kept
as shallow between lines as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:51:30 -04:00
rajamesandClaude Opus 5.5 482cc75faf feat(v4.0.0): every message begins through the wait -- a stuck node holds up no one
(GATE) offers a message's first word and sleeps until it is taken, taking
in whatever is written to the node meanwhile; (PAY) offers every refusal
owed at once, one to a port; a node that owes one sleeps in the wait.
Writing without looking, looking again and again, and the lower-number
rule are gone.  Text from the console for a node whose own text waits to
begin a message ends that text Interrupted.  The lone node's hosts serve
an offer as a write.  MESH.md 7c; plan step 6d task 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:38:07 -04:00
rajamesandClaude Opus 5.5 59727f762a fix(v4.0.0): step 6c mended after its review -- what a stuck or gone node can and cannot hold up
The review of step 6c found it did not keep its rule.  Ruled: fix them
all; 16 hops.

- Paying a NACK no longer waits on a lower-numbered neighbour; a node
  that owes one does not sleep, and goes on taking in what it is sent.
- AWAIT looks first through the messages already waiting.
- A node waiting to write to a removed neighbour gets error 18: the
  word that says who is reading also says which ports have anything on
  them.
- A console line lets Hera go when she is blocked on the first word of
  a message (v4_fabric_interrupt_error, a device's `pending`).
- A refused answer is told to the node that waits, not the one that
  answered; one owed to the node itself goes to its own queue.
- A message is passed on by at most 16 nodes, then refused.
- A GONE is believed only when it says it is from the node's centre.
- Tests: the check that tested nothing is replaced; no room for a
  waiting sender; the limit that remains is tested as a limit.
- MESH.md 7b's rule reworded to what holds, 7b.7 says where it falls
  short; README likewise.

make -C v4 test, sanitize, hosted-check; three bare-metal boots.  POST
538 of 538, word_count=317, dict_hash=0xc0769523a47b7dc3 on all six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:18:06 -04:00
rajamesandClaude Opus 5.5 9efe8aad3a feat(v4.0.0): a refused message is told to its sender -- NACK and GONE
A node that has no room for a message, or no way to pass it on, lets it
go and counts it as before, and now notes that it owes its sender a NACK
(type 4: the node the message was for).  It sends what it owes when it
next has nothing else to do.  36 cells of the messages waiting are kept
for NACK and GONE, and nothing is owed for either.

A node sent a NACK counts it in (REFUSED); one waiting for an answer from
that node (AWAIT) stops, with error 19, Message refused.  A GONE (type 5)
from a node's centre makes it forget the way to the node named (NO-ROUTE,
new), and ends a wait on it with error 20, Node gone.  From anyone else
it is ignored.  GONE ( n node -- ) sends one.

test_host_mesh.c, three nodes: text for a node nobody has a way to comes
back to the console, and to a node two away, as a NACK; a waiting sender's
line ends Message refused; a node that is waiting keeps what it has room
for, owes eight refusals, counts the rest, and sends them when its wait is
ended by a GONE from its centre.  70 checks, both widths, sanitizers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:58:27 -04:00
rajamesandClaude Opus 5.5 bc6deab3f5 fix(v4.0.0): a node blocked at a port with no one on it is given an error, not left there
A node writing to a node that was stuck, and was then killed, stayed
blocked for ever: killing a stuck node could cost its neighbours.  And on
the hosted and bare-metal products a write to an empty port, 5 7 PORT!,
ended the program with "the node stopped".

Now a node that can take an error, blocked writing to or reading from one
port that nothing is wired to -- nothing ever was, or what was there has
been killed or the wire cut -- has error 18, No one on that port, raised
on it and goes on.  A bare node waits as on the fabric; so does a node
whose neighbour is asleep, and one reading "any port".

test_host_unit.c: Hera kills node 14 while node 12 is blocked writing to
it.  It failed first: 12 stayed blocked.  test_fabric.c: a bare node still
waits.  hosted-check types 5 7 PORT! and goes on; it failed first too.

make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, dict_hash 0x5f0a949a6fc8ef2b on all six:
logs/20261007-121743, -122008, -122337.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:25:35 -04:00
rajamesandClaude Opus 5.5 18adf59091 fix(v4.0.0): POST leaves nothing, as v3 really does; and what the review of step 6b found
Captain Bob was told that v3 leaves the words POST's cases define in the
dictionary, and ruled that v4 should.  That was false: v3's run_test_suite
puts the dictionary back after each word's cases (test_common.c:333,
:365).  Shown that, he ruled that POST leaves nothing.  The boot now
seals the system, runs POST, and has the node do COLD, whose printing is
not shown; PARITY:V4_SYSTEM is the system as sealed.

A case the node does not come back from ends POST there, named, with how
many were not run: it would have stalled the boot for hours, where the
capsule had ended it.  The runner's test of it now uses a word that
really never ends.

hosted-check also requires that RS1 and T{ are unknown after boot, and
boots a program whose POST has failing cases (tests/post_cases_fail.c):
PARITY:FAIL, POST: FAILED, no prompt, none of a case's printing shown.

Comments and documents that still named the POST capsule or its two
hooks are brought up to date; NUCLEUS.md 6.3 says what v3 does, with the
lines, and how the wrong ruling came about.

make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, word_count=314, dict_hash
0x220ab283a504a3b3 on all six: logs/20261007-112638, -112901, -113220.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:40:20 -04:00
rajamesandClaude Opus 5.5 4bdb210a99 feat(v4.0.0): POST is the kernel's -- the boot feeds the cases, and nothing of the harness is on the node
The boot runs POST with the runner (v4/system/post.c) after the capsules:
it feeds the 538 cases to the node and judges them from outside.  A
case's printing no longer reaches the console.  What the cases define
stays in the dictionary, as in v3; the system is sealed after POST and
the boot prints PARITY:V4_SYSTEM word_count=N dict_hash=..., as v3 prints
its parity after POST.

Gone: capsules/v4/post79.4th and its blocks 7000 up; the harness words;
(CATCH) and (EMIT-HOOK), with what EMIT and the prompt loop did for them.

The generator runs v3 on the lines as the kernel sends them, without the
capsule's "T| ".  One expected result follows from that: >IN.initial
prints 6, not 9.

make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and
riscv64 boot, POST 538 of 538, word_count=411, dict_hash
0x6fb1d09418b189ee on all six: logs/20261007-105118, -105335, -105651.
T{ is unknown at the prompt; RS1 prints 42 42 before and after COLD.  A
scratch build with one expectation changed names the case and ends
PARITY:FAIL, POST: FAILED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:59:02 -04:00
rajamesandClaude Opus 5.5 d5b7235464 feat(v4.0.0): every node asks the kernel for its blocks; a born node is not POSTed
A block is a kernel request, as ENGINE.md 3.3 has it: the node puts the
block's number and the address of 256 cells on its stack and writes the
request to port 0, and the kernel leaves the status there.  The requests
are -1, read, and -2, write, the same for every node.  v4/system/blocks.c
serves them from the kernel's block subsystem, which is v3's.  The four
storage registers are gone from the engine.

The device that spoke block messages (4a505a15) is withdrawn with its
test and its message types: Captain Bob ruled on 2026-10-07 that it, a
node's own drive, and nodes with no storage had left the OS as designed
(docs/v4.0.0/MESH.md 8.5).

Hera no longer sends POST to the nodes she births: POST is the kernel's,
once.  Every node has its kernel on port 0; it serves a node's blocks and,
for Hera alone, her requests for nodes and capsules.

Bare metal: the node boots and is POSTed against POST's own block RAM,
and the kernel's chain -- fast RAM, the ramdrive, the virtio disk -- is
set up after POST and before the prompt, as on the v3 path.  The disk is
read and not written: nothing in v4 yet gives the owner's word that it
may be formatted.  A hosted program has the chain's fast RAM, as hosted
v3 has with no disk.  Error 17 is Storage refused.

make -C v4 test and sanitize pass at both widths; hosted-check passes on
three ISAs; amd64, aarch64 and riscv64 boot, POST 538 of 538, with the
typed session: logs/20261007-081603, -081839, -082226.  The hashes are
the same on all six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:24:47 -04:00
rajamesandClaude Opus 5.5 348eed7fa1 feat(v4.0.0): five nodes from nothing -- Hera asks for nodes, births and joins them
MESH.md step 5, in the fabric under test; the products are still one
node each until steps 8 and 9.

manage.c: what Hera asks of whoever holds the fabric, eleven requests by
KERNEL-WORD -- NODE-ME -BORN -WIRE -UNWIRE -SLEEP -WAKE -KILL -PARITY and
CAPSULE-OPEN -CELL -LINE. Nucleus: PORT!, SEND-ON, AWAIT, (SEAL).
capsules/v4/hera.4th: BIRTH and UNIT, the unit rule in FORTH and nowhere
else. The dictionary hash moves to the engine (v4_image_dict_hash).

test_host_unit.c, 34 checks, 64-bit: Hera is born empty, takes the
nucleus through her port, FORTH-79, POST and her capsule; 10 UNIT; four
nodes are born, each takes the nucleus and FORTH-79 through its port from
Hera and passes POST 538/538; four parities, one dictionary hash; they
talk, and a message between two corners not wired goes by Hera.

All v4 tests at both widths and under ASan+UBSan. hosted-check on three
ISAs. Bare metal: logs/20261006-143934 (amd64), -144204 (aarch64),
-144601 (riscv64).

Open, recorded in MESH.md: not run at 32 bits; a node that never answers
leaves Hera waiting; the capsules are read from files in the test, not
from the baked directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:48:23 -04:00
rajamesandClaude Opus 5.5 f9c034cadd feat(v4.0.0): a node looks before it writes; two neighbours no longer stop each other
MESH.md step 4, the fault found there, as ruled (section 7a): a node
writes only to a neighbour that is reading, keeps what it takes in
meanwhile, and loses and counts what it has no room for.

Engine: two more addresses after a node's ports -- which ports have a
neighbour waiting to write to it, which to read from it. Nucleus: (GATE)
before every message; the messages waiting, a ring of 400 cells, dealt
with when the node is idle. Of two neighbours the lower number may wait
to write (ruled after it was built); NEIGHBOUR tells a node who is on
each port.

test_host_mesh.c, 44 checks: the case that stopped the nodes passes; six
messages from each node to each at once all arrive; 800 at once, the
nodes come to rest and every message arrived or was counted (287 arrived,
714 of all kinds let go). test_fabric.c: the two looks. Both widths,
ASan+UBSan.

POST: twelve cases handed HERE, a cell address on v4, to words that take
a byte address, and so wrote into or read from the nucleus's code at cell
HERE/4. Ruled: left out, marked OPEN, until HERE and the byte words are
made to agree as its own step. POST is 538 cases.

hosted-check on three ISAs, 538/538. Bare metal: logs/20261006-134817
(amd64), -135044 (aarch64), -135440 (riscv64).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:56:57 -04:00
rajamesandClaude Opus 5.5 630d03fa4e feat(v4.0.0): a node finds the way -- routes, passing on, SEND; one fault stands
MESH.md step 4. Each node has a table of destinations and the port toward
each, and a port for everything else (ROUTE, DEFAULT-ROUTE, NO-ROUTES). A
message not for this node is passed on whole; one with nowhere to go is
dropped and counted. What text prints and how it ended go back to the node
it came from by the same table. SEND sends text to another node.

test_host_mesh.c: three StarForth nodes in a row behind a console, 28
checks at both widths and under ASan+UBSan. hosted-check on three ISAs.
Bare metal: logs/20261006-115225 (amd64), -115501 (aarch64), -115849
(riscv64).

NOT DONE. Two neighbours that write to each other at once wait for ever:
a write blocks until the neighbour reads, and a node that is writing is
not reading. The last check in test_host_mesh.c shows it (KNOWN FAULT).
MESH.md section 7a sets out the ways out; none is chosen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 12:01:32 -04:00
rajamesandClaude Opus 5.5 a041b401ea feat(v4.0.0): a node is sent text as a message and sends back what it prints
MESH.md step 3.  The ports are the transport; the message is what is
transported: to, from, type, heat and TTL, ACL tag, sequence, length, then
text four characters to a word.

- quit.v4: a node with nothing to do is blocked reading "any port"; text
  for it is interpreted; (FINISH) sends what it printed and then how the
  text ended, and it waits again
- core.v4: EMIT keeps what is printed, (FLUSH-OUT) and (HDR) send it to the
  sender on the port the message came on.  EMIT still needs one free data
  cell and no more; it works on the return stack and in A and B
- message.h/.c: the same format for whatever is on a port and is not a node
- boot.c: the boot is the node's console on port 1 and its kernel on port 0
- the prompt tests are a console that speaks messages
- gone: v4_line_begin, v4_line_done, v4_line_status; writing a node's input
  buffer and setting its P from outside; any use of CONSOLE-TX

Verified: make -C v4 test (test_host_quit.c 1283 checks, the full-stack
figures unchanged) and make -C v4 sanitize pass; hosted-check passes on
three ISAs with POST 550 of 550; clean qemu with STARFORTH_V4=1 passes POST
and answers lines typed at each prompt on amd64, aarch64 and riscv64
(logs/20261006-110551, -111621, -111341).  -110837 is an aarch64 run ended
by the test wrapper's limit while still in UEFI firmware; it shows nothing
about v4.

Not done: KEY, EXPECT and QUERY still read the console's input registers;
a message not for this node is let go (step 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:19:46 -04:00
rajamesandClaude Opus 5.5 ed6b11ad88 feat(v4.0.0): (CATCH) and (EMIT-HOOK), the two nucleus hooks POST needs
(CATCH): while it is not zero, a line that ends in an error sets it to -1
and ends " ok" instead of " ERROR".  (EMIT-HOOK): the xt of a word that is
given each character EMIT would send to the console.  The prompt loop sets
(EMIT-HOOK) to 0 at the end of every line.  docs/v4.0.0/NUCLEUS.md 6.3,
amended: it said one nucleus word would do.

Verified: make -C v4 test passes at both widths; by hand at the hosted
prompt, an unknown word and a division by zero are caught, the output hook
receives every character, and an uncaught error still says ERROR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:49:43 -04:00
rajamesandClaude Opus 5.5 c1bbcaaba4 feat(v4.0.0): word-level access control at the prompt, as v3
- 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>
2026-10-05 11:58:43 -04:00
rajamesandClaude Opus 5.5 a1afb44598 feat(v4.0.0): the editor, redone in FORTH; COLD WARM PAGE VERSION DEFER
- capsule/editor.fth: the block editor as FORTH source, which the node
  compiles itself.  It is a vocabulary, EDITOR, used at the ordinary
  prompt: n EDIT, then L N B T P E D S H R I WIPE DONE.  v3's EDIT, a
  shell of its own, is not carried over (ruled 2026-10-05); v3's L S and
  SHOW are kept in FORTH as they were, with COPY.
- system.v4: COLD (the system as the loader left it), WARM, PAGE,
  VERSION, 79-STANDARD (FORTH-79: silent), and DEFER IS DEFER@ as v3.
- input.v4: where words are split at blanks a zero byte reads as a blank,
  so a block never written, or filled a line at a time, loads cleanly.
- tests/test_host_quit.c: the editor's source fed to the prompt line by
  line, every command on empty and full screens, what reaches storage;
  the system words; deferred words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:25:20 -04:00
rajamesandClaude Opus 5.5 c8dc14897c feat(v4.0.0): block storage, LOAD and LIST (D-19)
- The golden model's host node gets a block storage device: four
  memory-mapped registers (number, address, command, status), 1024-byte
  blocks as 256 cells.  tests/test_node.c.
- capsule/blocks.v4: BLOCK BUFFER UPDATE SAVE-BUFFERS EMPTY-BUFFERS LIST
  LOAD SCR BLK (FORTH-79) and v3's FLUSH THRU and -->, over two buffers.
- The text being interpreted is at the address in (SRC), which QUERY makes
  the terminal's buffer and LOAD a block's; LOAD saves and restores it, so
  blocks nest and the rest of LOAD's line runs afterwards.  WORD makes
  sure a loading block is still in a buffer before it reads.
- In a block, \ skips to the next 64-character line.
- A block number that does not exist, and --> at the terminal, are errors
  with messages (D-18).
- tests/test_host_quit.c: ten transcripts of the v3 binary; nesting three
  deep on two buffers; what reaches the device and when.

v3's LOAD drops the rest of its line, and v3 has no BLK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:32:04 -04:00
rajamesandClaude Opus 5.5 ab272cc2ad feat(v4.0.0): the Q48.16 words and DUMP at the prompt
- capsule/qmath.v4: 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 -- the definitions test_foundation.c executes on the mesh node,
  now in the host node's vocabulary.
- numout.v4: DUMP.
- 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 (D-18): "Division by
  zero", "Argument out of range".  v3 returns 0 silently.
- tests/test_host_quit.c: 122 results printed by Q.PRINT are transcripts
  of the v3 binary, the same at both cell widths; signed values, the
  errors and DUMP's layout besides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:00:27 -04:00
rajamesandClaude Opus 5.5 e7d686c7a2 feat(v4.0.0): CASE, ['], S", WORDS, FORGET and FENCE
- compile.v4: CASE OF ENDOF ENDCASE, as v3 (they nest; a word out of
  place is a control structure mismatch); ['] and [LITERAL].
- quit.v4: S" and its run-time word; at the prompt the text is copied to
  PAD.
- capsule/system.v4: WORDS and VLIST; FORGET (FORTH-79), which gives the
  space back and will not remove a word below FENCE -- the capsule's own
  words -- where v3's FORGET DUP succeeds.
- tests/test_host_quit.c: ten more transcripts of the v3 binary, and
  nesting, the fence and the listing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:49:53 -04:00
rajamesandClaude Opus 5.5 c02505db69 feat(v4.0.0): 48 more words at the prompt (words.v4)
capsule/words.v4: the stack, comparison, shift, double, mixed and string
words whose definitions DECOMPOSITION.md already gives and the mesh-node
tests execute, now in the host node's vocabulary:

  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

- A shift count that is negative or as large as the cell is an error, as
  in v3 (code 9, "Shift count out of range").
- tests/test_host_quit.c: 36 sessions from the prompt that are
  transcripts of the v3 binary, and the cases where v4 keeps the standard
  (MOVE in cells; M+ and M- with the double low cell first).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:34:13 -04:00
rajamesandClaude Opus 5.5 dc7e37ba5c feat(v4.0.0): every error is raised and ends the line (D-18)
Ruled 2026-10-04: guard all errors.  The errors that set NODE-ERROR and
let the line run on now stop it at once, with a message.

- A store of a non-zero code to NODE-ERROR is a trap, a sixth kind of
  fault: nothing after it executes, the return stack is emptied, and the
  data stack is left as the word left it.  Not attached, NODE-ERROR is
  plain memory, as the tests below the prompt use it.
- The capsule's words store a code where they stored -1, and the prompt's
  (RAISED) prints its message: Negative count, Not a number, Number too
  long, Not a character, Dictionary full, Name missing, Control structure
  mismatch, Control structures too deep.
- ' and COMPILE and [COMPILE] of a word that is not there say
  UNKNOWN WORD: 'xxx', as the interpreter does.
- tests: the trap in test_exec.c; every message from the prompt, with the
  rest of the line not run and the stack kept, in test_host_quit.c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:13:23 -04:00
rajamesandClaude Opus 5.5 b5d644d498 feat(v4.0.0): the prompt -- QUIT, ABORT, ABORT" and ."
Layer 5 of the compiler capsule.  The host node is now started at QUIT and
left running: it prompts, reads a line from its console with QUERY,
interprets it, says " ok" or " ERROR", and goes round again.

- capsule/quit.v4: QUIT and ABORT (FORTH-79), ABORT" and (ABORT") as v3 has
  them, ." and (.") (FORTH-79).  Text is compiled as a counted string after
  the call; the run-time word returns to the cell after it.
- INTERPRET prints v3's messages before abandoning a line:
  UNKNOWN WORD: 'xxx' and xxx: compile-only.
- core.v4: CR SPACE COUNT TYPE, as DECOMPOSITION.md gives them.
- input.v4: WORD split so that (PARSE) can take text without skipping
  leading delimiters (." " is an empty string); EXPECT keeps its place in
  memory, so six values may wait on the stack while a line is typed.
- tests/test_host_quit.c: the node is fed characters and its output read;
  16 sessions are transcripts of the v3 binary.

QUIT stops the line from inside any word and says nothing; v3's goes on
with the line and cannot be compiled.  v3's compiled ABORT" crashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:49:44 -04:00
rajamesandClaude Opus 5.5 806f876ee0 fix(v4.0.0): shorter chains of calls in the compiler capsule
Measured, compiling a DO loop left one return entry spare under
INTERPRET and a defining word called from inside another word
overflowed the nine-entry return stack (D-2); the prompt loop will take
one more.

C@ and C! shift without a loop.  The code generator shifts each opcode
into the word being built, so nothing is placed by a counted shift, and
the address masks come from a table.  The dictionary search, the
in-liner, the end of a loop and the making of a data word are each one
word reached by a jump.

Every kind of line now leaves at least three return entries spare while
it compiles; a word run from the interpreter has six; and CREATE ...
DOES> works from the prompt, from a word, and from a word that calls
that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:17:58 -04:00
rajamesandClaude Opus 5.5 bbd1b4047f refactor(v4.0.0): the capsule's inner words keep their state in memory
WORD, the dictionary words and the code generator run underneath
whatever the user has on the stacks, which are ten and nine deep (D-2).
Measured, they used five to seven data cells of their own, leaving a
line about three.  Each now keeps what it works on in its file's
scratch cells and has at most three cells on the data stack; , calls
nothing; and the longest chains of calls are shorter.

The public words of core.v4, input.v4 and dict.v4 get dictionary
headers.  NUMBER is split so the interpreter can have a flag instead of
NODE-ERROR.  The host-node tests share one memory map, host_map.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:41:50 -04:00
rajamesandClaude Opus 5.5 443439c3d2 feat(v4.0.0): reading a line and splitting it into words and numbers
The first layer of the compiler capsule, on the host node: TIB >IN SPAN
SOURCE BL EXPECT QUERY WORD ENCLOSE CONVERT NUMBER and the comment
words.  The definitions are text, v4/capsule/core.v4 (the core words
they rest on) and v4/capsule/input.v4, assembled by the text assembler,
which can now read a file.

EXPECT, QUERY, WORD and ENCLOSE behave as v3's.  CONVERT and NUMBER are
FORTH-79 (ruled 2026-10-04): any BASE, a double in the standard order,
and NUMBER returns a signed double or sets NODE-ERROR.

Executed at both cell widths against C and against values recorded from
the v3 binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:30:32 -04:00