Files
LithosAnanake/docs/v4.0.0/POST79.md
T
rajamesandClaude Opus 5.5 2930349bbf feat(v4.0.0): POST passes and is part of the boot; U* and U/MOD
The boot is now nucleus, forth79.4th, POST, prompt, on both products.

- forth79.4th: U* and U/MOD, the capsule's first colon definitions.  They
  are in the FORTH-79 Required Word Set and neither v3 nor v4 had them.
- post79.4th: 550 cases, 126 of the 130 required words.  443 are v3's with
  v3's result.  The rest follow three rulings (2026-10-05): address-
  dependent cases are checked for count, not value; where v3 departs from
  FORTH-79 the standard's result is expected; words v3 has no case for get
  cases written by hand.  v4/tools/post79_rules.py holds each exception
  with its reason and docs/v4.0.0/POST79.md lists them all.
- every case starts from an empty stack, DECIMAL and FORTH DEFINITIONS
- the boot requires POST's tally line with fail=0

Verified: tests=550 pass=550 fail=0 and identical PARITY lines on hosted
amd64, aarch64 and riscv64 (make -C v4 hosted-check) and on bare metal,
clean qemu with STARFORTH_V4=1, on the same three (logs/20261005-1619xx,
-1621xx, -1625xx).  A U/MOD broken on purpose fails five cases and stops
the boot.  make -C v4 test passes.

Not shown: all words but those two are still assembled, so POST has so far
tested the assembled words.  Nothing was typed at a bare-metal prompt.
Open: PAD 42 OVER ! faults on v4 (D-1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:28:01 -04:00

20 KiB

POST for the FORTH-79 Required Word Set: where it departs from v3

Written by v4/tools/mkpost.py from v4/tools/post79_rules.py; do not edit. Design: NUCLEUS.md section 6. The capsule is capsules/v4/post79.4th.

POST runs 550 cases. 443 are v3's, with what the hosted v3 binary did as the expected result. This file lists every other case, and why.

1. Rewritten for FORTH-79 (59)

v3 departs from the standard, or the case is written for v3's machine. v4 follows the standard (standing ruling), so the expected result is the standard's and not v3's.

Case v3's line What v3 did As POST runs it Must do Why
PICK.pick_0 1 2 3 0 PICK . CR stack 1 2 3, printed 3 \n unchanged an error FORTH-79: PICK and ROLL count from 1; v3 counts from 0; 0 PICK is an error
PICK.pick_1 1 2 3 1 PICK . CR stack 1 2 3, printed 2 \n unchanged stack 1 2 3, prints 3 \n FORTH-79: PICK and ROLL count from 1; v3 counts from 0
PICK.pick_2 1 2 3 2 PICK . CR stack 1 2 3, printed 1 \n unchanged stack 1 2 3, prints 2 \n FORTH-79: PICK and ROLL count from 1; v3 counts from 0
ROLL.roll_1 1 2 3 1 ROLL . . . CR stack empty, printed 1 3 2 \n unchanged stack empty, prints 3 2 1 \n FORTH-79: PICK and ROLL count from 1; v3 counts from 0
>R.basic 42 >R R@ . R> . CR stack empty, printed 42 42 \n : RS1 42 >R R@ . R> . CR ; RS1 stack empty, prints 42 42 \n FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
>R.zero 0 >R R@ . R> . CR stack empty, printed 0 0 \n : RS2 0 >R R@ . R> . CR ; RS2 stack empty, prints 0 0 \n FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
>R.negative -123 >R R@ . R> . CR stack empty, printed -123 -123 \n : RS3 -123 >R R@ . R> . CR ; RS3 stack empty, prints -123 -123 \n FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
>R.multiple 1 2 >R >R R@ . R> . R@ . R> . CR stack empty, printed 1 1 2 2 \n : RS4 1 2 >R >R R@ . R> . R@ . R> . CR ; RS4 stack empty, prints 1 1 2 2 \n FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
R>.basic 42 >R R> . CR stack empty, printed 42 \n : RS5 42 >R R> . CR ; RS5 stack empty, prints 42 \n FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
R>.lifo_order 1 2 >R >R R> . R> . CR stack empty, printed 1 2 \n : RS6 1 2 >R >R R> . R> . CR ; RS6 stack empty, prints 1 2 \n FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
R@.basic 42 >R R@ . R> DROP CR stack empty, printed 42 \n : RS7 42 >R R@ . R> DROP CR ; RS7 stack empty, prints 42 \n FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
R@.non_destructive 99 >R R@ R@ = . R> DROP CR stack empty, printed -1 \n : RS8 99 >R R@ R@ = . R> DROP CR ; RS8 stack empty, prints -1 \n FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
,.basic 42 , HERE 8 - @ . CR stack empty, printed 42 \n 42 , HERE 1 - @ . CR stack empty, prints 42 \n v3's address unit is a byte; a v4 address unit is a cell (D-1)
,.negative -999 , HERE 8 - @ . CR stack empty, printed -999 \n -999 , HERE 1 - @ . CR stack empty, prints -999 \n v3's address unit is a byte; a v4 address unit is a cell (D-1)
+!.basic 10 HERE ! 5 HERE +! HERE @ . CR error unchanged stack empty, prints 15 \n v3 raises an error FORTH-79 does not ask for: a store to HERE
+!.by_zero 42 HERE ! 0 HERE +! HERE @ . CR error unchanged stack empty, prints 42 \n v3 raises an error FORTH-79 does not ask for: a store to HERE
+!.negative 10 HERE ! -3 HERE +! HERE @ . CR error unchanged stack empty, prints 7 \n v3 raises an error FORTH-79 does not ask for: a store to HERE
+!.accumulate 0 HERE ! 1 HERE +! 2 HERE +! 3 HERE +! HERE @ . CR error unchanged stack empty, prints 6 \n v3 raises an error FORTH-79 does not ask for: a store to HERE
HERE.after_comma~2 HERE 42 , HERE SWAP - 1 CELLS = 0 SWAP / error unchanged stack 0, prints nothing the case divides by zero unless HERE moved by one cell; on v3 it had moved by 8
MOVE.basic HERE 65 OVER C! HERE 1+ 66 OVER C! HERE HERE 16 + 2 MOVE HERE 16 + C@ . HERE 17 + C@ . CR stack 302 303, printed 65 66 \n 65 HERE ! 66 HERE 1+ ! HERE HERE 16 + 2 MOVE HERE 16 + @ . HERE 17 + @ . CR stack empty, prints 65 66 \n FORTH-79: MOVE moves cells; and v3's address unit is a byte; a v4 address unit is a cell (D-1)
BASE.base_store 16 BASE ! 255 . CR stack empty, printed 597 \n unchanged stack empty, prints 255 \n 255 read in base 16 and printed in base 16 is 255; v3 printed 467
<#.basic DECIMAL 42 S>D <# #S #> TYPE CR stack empty, printed 774763251095801167872\n unchanged stack empty, prints 42\n v3 prints 774763251095801167872 for 42 S>D <# #S #>
<#.empty 0 <# #> TYPE CR stack 0, printed \n 0 0 <# #> TYPE CR stack empty, prints \n FORTH-79: <# # #S #> work on a double number; v3 takes a single
<#.negative -42 <# #S #> TYPE CR stack empty, printed 42\n -42 DUP ABS 0 <# #S ROT SIGN #> TYPE CR stack empty, prints -42\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#.single_digit 15 <# # #> TYPE CR stack empty, printed 5\n 15 0 <# # #> TYPE CR stack empty, prints 5\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#.multiple 15 <# # # #> TYPE CR stack empty, printed 15\n 15 0 <# # # #> TYPE CR stack empty, prints 15\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#.zero_pad 5 <# # 0 # #> TYPE CR stack 0, printed 05\n 5 0 <# # # #> TYPE CR stack empty, prints 05\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#S.basic 42 <# #S #> TYPE CR stack empty, printed 42\n 42 0 <# #S #> TYPE CR stack empty, prints 42\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#S.zero 0 <# #S #> TYPE CR stack empty, printed 0\n 0 0 <# #S #> TYPE CR stack empty, prints 0\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#S.large 1234567890 <# #S #> TYPE CR stack empty, printed 1234567890\n 1234567890 0 <# #S #> TYPE CR stack empty, prints 1234567890\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
SIGN.negative -42 ABS <# #S SIGN #> TYPE CR stack 0, printed 42\n -42 DUP ABS 0 <# #S ROT SIGN #> TYPE CR stack empty, prints -42\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
SIGN.positive 42 <# #S SIGN #> TYPE CR stack 0, printed 42\n 42 DUP ABS 0 <# #S ROT SIGN #> TYPE CR stack empty, prints 42\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
SIGN.zero 0 <# #S SIGN #> TYPE CR stack 0, printed 0\n 0 DUP ABS 0 <# #S ROT SIGN #> TYPE CR stack empty, prints 0\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#>.normal 42 <# #S #> TYPE CR stack empty, printed 42\n 42 0 <# #S #> TYPE CR stack empty, prints 42\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#>.empty 0 <# #> TYPE CR stack 0, printed \n 0 0 <# #> TYPE CR stack empty, prints \n FORTH-79: <# # #S #> work on a double number; v3 takes a single
#>.stack_effect 42 <# #S #> SWAP . . CR stack empty, printed 24 2 \n 42 0 <# #S #> SWAP DROP . CR stack empty, prints 2 \n FORTH-79: <# # #S #> work on a double number; v3 takes a single
HOLD.basic 42 <# 46 HOLD #S #> TYPE CR stack empty, printed 42.\n 42 0 <# 46 HOLD #S #> TYPE CR stack empty, prints 42.\n FORTH-79: <# # #S #> work on a double number; v3 takes a single
COUNT.basic HERE S" Test" DROP COUNT . . CR stack 302, printed 84 303 \n 4 PAD C! PAD COUNT . PAD - . CR stack empty, prints 4 1 \n v3's address unit is a byte; a v4 address unit is a cell (D-1); and S" is not FORTH-79
COUNT.empty HERE 0 OVER C! COUNT . . CR stack empty, printed 0 308 \n 0 PAD C! PAD COUNT . PAD - . CR stack empty, prints 0 1 \n v3's address unit is a byte; a v4 address unit is a cell (D-1)
COUNT.max_length HERE 255 OVER C! COUNT . . CR stack empty, printed 255 308 \n 255 PAD C! PAD COUNT . PAD - . CR stack empty, prints 255 1 \n v3's address unit is a byte; a v4 address unit is a cell (D-1)
COUNT.basic~2 HERE S" Test" DROP C@ HERE 1+ SWAP COUNT . . CR stack 412 418, printed 0 85 \n 4 PAD C! PAD COUNT . PAD - . CR stack empty, prints 4 1 \n v3's address unit is a byte; a v4 address unit is a cell (D-1)
COUNT.empty~2 HERE 0 OVER C! COUNT . . CR stack empty, printed 0 418 \n 0 PAD C! PAD COUNT . PAD - . CR stack empty, prints 0 1 \n v3's address unit is a byte; a v4 address unit is a cell (D-1)
COUNT.max_length~2 HERE 255 OVER C! COUNT . . CR stack empty, printed 255 418 \n 255 PAD C! PAD COUNT . PAD - . CR stack empty, prints 255 1 \n v3's address unit is a byte; a v4 address unit is a cell (D-1)
CMOVE.basic HERE S" Test" DUP >R HERE 10 + SWAP CMOVE CR stack 325, printed \n 65 PAD C! 66 PAD 1+ C! PAD PAD 10 + 2 CMOVE PAD 10 + C@ . PAD 11 + C@ . CR stack empty, prints 65 66 \n v3's address unit is a byte; a v4 address unit is a cell (D-1); and the v3 case leaves a value on the return stack
TYPE.basic_string HERE S" Hello" DUP >R HERE SWAP CMOVE HERE R> TYPE CR stack 400, printed Hello\n 72 PAD C! 105 PAD 1+ C! PAD 2 TYPE CR stack empty, prints Hi\n v3's address unit is a byte; a v4 address unit is a cell (D-1); and FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
TYPE.numbers HERE S" 12345" DUP >R HERE SWAP CMOVE HERE R> TYPE CR stack 406, printed 12345\n 49 PAD C! 50 PAD 1+ C! PAD 2 TYPE CR stack empty, prints 12\n v3's address unit is a byte; a v4 address unit is a cell (D-1); and FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt
>IN.initial >IN @ . CR stack empty, printed 852597488 \n unchanged stack empty, prints 9 \n FORTH-79: >IN is the offset into the input, 9 here; v3 prints an address
UPDATE.no_block 0 SCR ! UPDATE error unchanged stack empty, prints nothing v3 raises an error FORTH-79 does not ask for
SCR.after_load 1 LOAD SCR @ . CR stack empty, printed `` unchanged stack empty, prints 1 \n v3 prints nothing after 1 LOAD; SCR is still 1 from the LIST before
FIND.existing FIND DUP . CR stack empty, printed 852596848 \n unchanged 0 value(s) left; output not compared FORTH-79: FIND takes the next word, DUP here, and leaves its address, which is printed
FIND.user_word : test5 44 ; FIND test5 . CR stack empty, printed 852923888 \n unchanged 0 value(s) left; output not compared the address FIND leaves is printed
FIND.empty FIND error unchanged stack 0, prints nothing FORTH-79: FIND leaves 0 when there is no word; v3 raises an error
VOCABULARY.cross_vocab_access VOCABULARY V1 V1 DEFINITIONS : V1WORD 11 ; FORTH V1WORD . CR stack empty, printed 11 \n unchanged an error FORTH-79: a word defined in another vocabulary is not found from FORTH; v3 finds it
VOCABULARY.duplicate VOCABULARY TESTVOC VOCABULARY TESTVOC error unchanged stack empty, prints nothing v3 raises an error FORTH-79 does not ask for: defining a name again
IF.true : TEST1 IF 42 ELSE 24 THEN ; -1 TEST1 . CR stack empty, printed 42 \n unchanged stack empty, prints 42 \n v3 runs an older TEST1 from another vocabulary and leaves the flag
IF.false : TEST2 IF 42 ELSE 24 THEN ; 0 TEST2 . CR stack empty, printed 24 \n unchanged stack empty, prints 24 \n v3 runs an older TEST2 from another vocabulary and leaves the flag
LEAVE.basic : TLV 0 5 0 DO I 3 = IF LEAVE THEN 1+ LOOP ; TLV . CR stack empty, printed 3 \n unchanged stack empty, prints 4 \n FORTH-79: LEAVE ends the loop at the next LOOP; v3 jumps out at once
LEAVE.at_start : TLV2 0 5 0 DO LEAVE 1+ LOOP ; TLV2 . CR stack empty, printed 0 \n unchanged stack empty, prints 1 \n FORTH-79: LEAVE ends the loop at the next LOOP; v3 jumps out at once
LEAVE.qdloop : TLV3 0 5 0 ?DO I 2 = IF LEAVE THEN 1+ LOOP ; TLV3 . CR stack empty, printed 2 \n unchanged stack empty, prints 3 \n FORTH-79: LEAVE ends the loop at the next LOOP; v3 jumps out at once

2. Address-dependent (20)

The case leaves or prints a memory address, which is a different number on the two machines. The number of values left is checked, and what is printed unless an address is printed; the values are not.

Case Line Checked
-TRAILING.basic HERE S" Test " -TRAILING TYPE CR values left, and output
-TRAILING.all_spaces HERE S" " -TRAILING TYPE CR values left, and output
-TRAILING.no_spaces HERE S" Test" -TRAILING TYPE CR values left, and output
HERE.stability HERE DUP HERE = . CR values left, and output
PAD.stability PAD DUP PAD = . CR values left, and output
UPDATE.basic 1 BLOCK UPDATE values left, and output
UPDATE.multiple 1 BLOCK UPDATE UPDATE values left, and output
SAVE-BUFFERS.dirty_blocks 1 BLOCK UPDATE SAVE-BUFFERS values left, and output
EMPTY-BUFFERS.after_use 1 BLOCK EMPTY-BUFFERS values left, and output
EMPTY-BUFFERS.dirty_blocks 1 BLOCK UPDATE EMPTY-BUFFERS values left, and output
BUFFER.flush_dirty 2 BLOCK 1+ 2 BUFFER values left, and output
BLOCK.basic 1 BLOCK DUP . CR values left only
BUFFER.basic 1 BUFFER DUP . CR values left only
VOCABULARY.create_and_switch VOCABULARY MYVOC MYVOC DEFINITIONS CONTEXT @ . CR values left only
CONTEXT.basic CONTEXT @ . CR values left only
CONTEXT.initial FORTH CONTEXT @ . CR values left only
CURRENT.basic CURRENT @ . CR values left only
CURRENT.after_def VOCABULARY TEST-VOC8 TEST-VOC8 DEFINITIONS CURRENT @ . CR values left only
LIST.basic 1 LIST values left only
SCR.after_list 1 LIST SCR @ . CR values left only

3. Written by hand (28)

For required words v3 has no case for. The expected results are the standard's; no v3 run stands behind them.

Case Line Must do
U*.small 3 4 U* . . CR stack empty, prints 0 12 \n
U*.zero 0 5 U* . . CR stack empty, prints 0 0 \n
U*.full -1 -1 U* 2+ . 1 = . CR stack empty, prints 0 -1 \n
U/MOD.small 7 0 2 U/MOD . . CR stack empty, prints 3 1 \n
U/MOD.exact 12 0 4 U/MOD . . CR stack empty, prints 3 0 \n
U/MOD.double 0 1 2 U/MOD 2* . . CR stack empty, prints 0 0 \n
U/MOD.inverse -1 -1 U* -1 U/MOD 1+ . . CR stack empty, prints 0 0 \n
U/MOD.carry -1 -2 -1 U/MOD 1+ . 2+ . CR stack empty, prints 0 0 \n
U/MOD.by_zero 1 0 0 U/MOD an error
?.basic VARIABLE Q1 42 Q1 ! Q1 ? CR stack empty, prints 42 \n
EXECUTE.found : E1 7 ; FIND E1 EXECUTE . CR stack empty, prints 7 \n
U..small 42 U. CR stack empty, prints 42 \n
U..unsigned -1 U. CR stack empty, prints 18446744073709551615 \n
CONVERT.digits : C1 0 0 BL WORD CONVERT DROP ; C1 123 . . CR stack empty, prints 0 123 \n
'.found ' DUP 0= . CR stack empty, prints 0 \n
.".in_definition : Q3 ." hi there" ; Q3 CR stack empty, prints hi there\n
LITERAL.basic : Q4 [ 5 ] LITERAL ; Q4 . CR stack empty, prints 5 \n
STATE.interpreting STATE @ . CR stack empty, prints 0 \n
STATE.compiling : Q5 STATE @ 0= . ; IMMEDIATE : Q6 Q5 ; CR stack empty, prints 0 \n
COMPILE.basic : Q7 COMPILE DUP ; IMMEDIATE : Q8 Q7 ; 3 Q8 . . CR stack empty, prints 3 3 \n
[COMPILE].basic : Q9 [COMPILE] IF ; IMMEDIATE : Q10 Q9 1 ELSE 2 THEN ; 0 Q10 . -1 Q10 . CR stack empty, prints 2 1 \n
(.comment 1 ( 2 ) 3 . . CR stack empty, prints 3 1 \n
BLK.terminal BLK @ . CR stack empty, prints 0 \n
79-STANDARD.present 79-STANDARD stack empty, prints nothing
WORD.next_word : W1 BL WORD COUNT TYPE ; W1 HELLO stack empty, prints HELLO
WORD.delimiter : W2 44 WORD COUNT TYPE ; W2 A B, stack empty, prints A B
WORD.count : W3 BL WORD C@ . ; W3 ABC stack empty, prints 3
WORD.skips_leading : W4 BL WORD COUNT TYPE ; W4 X stack empty, prints X

4. v3 cases left out (27)

Case Why
EXPECT.zero_length reads the keyboard
EXPECT.one_item reads the keyboard
EXPECT.empty_stack reads the keyboard
FORGET.nonexistent forgets a word of the system
FORGET.protected forgets a word of the system
CONTEXT.modify damages the system on purpose
CURRENT.protect damages the system on purpose
QUIT.in_definition v3 did not get through it in one session
:.nested v3 did not get through it in one session
].basic v3 did not get through it in one session
ELSE.double v3 did not get through it in one session
THEN.extra v3 did not get through it in one session
CMOVE.bounds moves 1000 bytes over the system; v3 refuses by a bounds rule FORTH-79 has not got
WORD.empty_input v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.space_delim v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.newline_delim v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.tab_delim v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.comma_delim v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.skip_leading v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.single_char v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.long_word v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.zero_delim v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.high_ascii v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.count_format v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.count_value v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
WORD.multi_delim v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand
PAD.usable OPEN: PAD 42 OVER ! faults on v4 -- PAD is a byte address and ! takes a cell address (D-1)

5. Required words POST does not test

KEY EXPECT QUERY QUIT.

KEY, EXPECT and QUERY wait for the keyboard, which a boot cannot type at. QUIT returns to the terminal without ok, which the capsule loader takes for a refused line.

6. Where v3 did not do what its own table expects (6)

POST expects what v3 did, unless section 1 says otherwise.

Case
+!.basic v3's table says no error, v3 raised one
+!.by_zero v3's table says no error, v3 raised one
+!.negative v3's table says no error, v3 raised one
+!.accumulate v3's table says no error, v3 raised one
HERE.after_comma~2 v3's table says no error, v3 raised one
CREATE.long_name v3's table says error, v3 raised none