Files
LithosAnanake/v4/tools/post79_rules.py
T
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

197 lines
12 KiB
Python

"""post79_rules.py -- where POST does not take v3's word for it.
mkpost.py takes every case from v3 and, by default, what v3 did with it.
This file holds the exceptions, each with its reason. Ruled by Captain Bob,
2026-10-05 (docs/v4.0.0/NUCLEUS.md 6.5):
ADDRESS the case leaves or prints a memory address, which is not the
same number on the two machines. "depth+output": the number of
values left and what was printed are checked, the values are
not. "depth": only the number of values, because an address is
printed.
STANDARD v3 departs from FORTH-79, or the case is written for v3's
machine; v4 follows the standard. The entry gives the case as
POST runs it: `text` if the line had to change, then what it
must do -- `error`, or `stack` and `out`, or `depth` alone where
an address is printed. None drops the case. `why` is the
reason, and is what docs/v4.0.0/POST79.md lists.
EXTRA cases written by hand, for required words v3 has no case for.
"""
D1 = "v3's address unit is a byte; a v4 address unit is a cell (D-1)"
PIC = "FORTH-79: <# # #S #> work on a double number; v3 takes a single"
RS = "FORTH-79: >R R> R@ are for use inside a definition; v4 refuses them at the prompt"
PICK = "FORTH-79: PICK and ROLL count from 1; v3 counts from 0"
LEAVE = "FORTH-79: LEAVE ends the loop at the next LOOP; v3 jumps out at once"
V3WORD = "v3's WORD does not take the next word from the input, as FORTH-79's does; see the WORD cases written by hand"
V3ERR = "v3 raises an error FORTH-79 does not ask for"
VOC = "FORTH-79: a word defined in another vocabulary is not found from FORTH; v3 finds it"
ADDRESS = {
"-TRAILING.basic": "depth+output", "-TRAILING.all_spaces": "depth+output", "-TRAILING.no_spaces": "depth+output",
"HERE.stability": "depth+output", "PAD.stability": "depth+output",
"UPDATE.basic": "depth+output", "UPDATE.multiple": "depth+output", "SAVE-BUFFERS.dirty_blocks": "depth+output",
"EMPTY-BUFFERS.after_use": "depth+output", "EMPTY-BUFFERS.dirty_blocks": "depth+output",
"BUFFER.flush_dirty": "depth+output",
"BLOCK.basic": "depth", "BUFFER.basic": "depth",
"VOCABULARY.create_and_switch": "depth", "CONTEXT.basic": "depth", "CONTEXT.initial": "depth",
"CURRENT.basic": "depth", "CURRENT.after_def": "depth",
# the standard leaves the layout of a listing to the system
"LIST.basic": "depth", "SCR.after_list": "depth",
}
STANDARD = {
"PICK.pick_0": dict(error=True, why=PICK + "; 0 PICK is an error"),
"PICK.pick_1": dict(stack=[1, 2, 3], out="3 \n", why=PICK),
"PICK.pick_2": dict(stack=[1, 2, 3], out="2 \n", why=PICK),
"ROLL.roll_1": dict(out="3 2 1 \n", why=PICK),
">R.basic": dict(text=": RS1 42 >R R@ . R> . CR ; RS1", out="42 42 \n", why=RS),
">R.zero": dict(text=": RS2 0 >R R@ . R> . CR ; RS2", out="0 0 \n", why=RS),
">R.negative": dict(text=": RS3 -123 >R R@ . R> . CR ; RS3", out="-123 -123 \n", why=RS),
">R.multiple": dict(text=": RS4 1 2 >R >R R@ . R> . R@ . R> . CR ; RS4", out="1 1 2 2 \n", why=RS),
"R>.basic": dict(text=": RS5 42 >R R> . CR ; RS5", out="42 \n", why=RS),
"R>.lifo_order": dict(text=": RS6 1 2 >R >R R> . R> . CR ; RS6", out="1 2 \n", why=RS),
"R@.basic": dict(text=": RS7 42 >R R@ . R> DROP CR ; RS7", out="42 \n", why=RS),
"R@.non_destructive": dict(text=": RS8 99 >R R@ R@ = . R> DROP CR ; RS8", out="-1 \n", why=RS),
",.basic": dict(text="42 , HERE 1 - @ . CR", out="42 \n", why=D1),
",.negative": dict(text="-999 , HERE 1 - @ . CR", out="-999 \n", why=D1),
"+!.basic": dict(out="15 \n", why=V3ERR + ": a store to HERE"),
"+!.by_zero": dict(out="42 \n", why=V3ERR + ": a store to HERE"),
"+!.negative": dict(out="7 \n", why=V3ERR + ": a store to HERE"),
"+!.accumulate": dict(out="6 \n", why=V3ERR + ": a store to HERE"),
"HERE.after_comma~2": dict(stack=[0], why="the case divides by zero unless HERE moved by one cell; on v3 it had moved by 8"),
"MOVE.basic": dict(text="65 HERE ! 66 HERE 1+ ! HERE HERE 16 + 2 MOVE HERE 16 + @ . HERE 17 + @ . CR",
out="65 66 \n", why="FORTH-79: MOVE moves cells; and " + D1),
"PAD.usable": None, # see OPEN below
# see OPEN below: HERE handed to a word that takes a byte address
"C!.basic": None,
"C!.zero": None,
"C!.high_byte": None,
"C!.truncation": None,
"C@.after_cstore": None,
"C@.zero_byte": None,
"FILL.basic": None,
"FILL.zero_byte": None,
"CMOVE.overlap": None,
"TYPE.single_char": None,
"COUNT.string_bounds": None,
"COUNT.zero_addr_plus_one": None,
"BASE.base_store": dict(out="255 \n", why="255 read in base 16 and printed in base 16 is 255; v3 printed 467"),
"<#.basic": dict(out="42\n", why="v3 prints 774763251095801167872 for 42 S>D <# #S #>"),
"<#.empty": dict(text="0 0 <# #> TYPE CR", out="\n", why=PIC),
"<#.negative": dict(text="-42 DUP ABS 0 <# #S ROT SIGN #> TYPE CR", out="-42\n", why=PIC),
"#.single_digit": dict(text="15 0 <# # #> TYPE CR", out="5\n", why=PIC),
"#.multiple": dict(text="15 0 <# # # #> TYPE CR", out="15\n", why=PIC),
"#.zero_pad": dict(text="5 0 <# # # #> TYPE CR", out="05\n", why=PIC),
"#S.basic": dict(text="42 0 <# #S #> TYPE CR", out="42\n", why=PIC),
"#S.zero": dict(text="0 0 <# #S #> TYPE CR", out="0\n", why=PIC),
"#S.large": dict(text="1234567890 0 <# #S #> TYPE CR", out="1234567890\n", why=PIC),
"SIGN.negative": dict(text="-42 DUP ABS 0 <# #S ROT SIGN #> TYPE CR", out="-42\n", why=PIC),
"SIGN.positive": dict(text="42 DUP ABS 0 <# #S ROT SIGN #> TYPE CR", out="42\n", why=PIC),
"SIGN.zero": dict(text="0 DUP ABS 0 <# #S ROT SIGN #> TYPE CR", out="0\n", why=PIC),
"#>.normal": dict(text="42 0 <# #S #> TYPE CR", out="42\n", why=PIC),
"#>.empty": dict(text="0 0 <# #> TYPE CR", out="\n", why=PIC),
"#>.stack_effect": dict(text="42 0 <# #S #> SWAP DROP . CR", out="2 \n", why=PIC),
"HOLD.basic": dict(text="42 0 <# 46 HOLD #S #> TYPE CR", out="42.\n", why=PIC),
"COUNT.basic": dict(text="4 PAD C! PAD COUNT . PAD - . CR", out="4 1 \n", why=D1 + "; and S\" is not FORTH-79"),
"COUNT.empty": dict(text="0 PAD C! PAD COUNT . PAD - . CR", out="0 1 \n", why=D1),
"COUNT.max_length": dict(text="255 PAD C! PAD COUNT . PAD - . CR", out="255 1 \n", why=D1),
"COUNT.basic~2": dict(text="4 PAD C! PAD COUNT . PAD - . CR", out="4 1 \n", why=D1),
"COUNT.empty~2": dict(text="0 PAD C! PAD COUNT . PAD - . CR", out="0 1 \n", why=D1),
"COUNT.max_length~2": dict(text="255 PAD C! PAD COUNT . PAD - . CR", out="255 1 \n", why=D1),
"CMOVE.basic": dict(text="65 PAD C! 66 PAD 1+ C! PAD PAD 10 + 2 CMOVE PAD 10 + C@ . PAD 11 + C@ . CR",
out="65 66 \n", why=D1 + "; and the v3 case leaves a value on the return stack"),
"CMOVE.bounds": None, # moves 1000 bytes over the system; v3 refuses by a bounds rule FORTH-79 has not got
"TYPE.basic_string": dict(text="72 PAD C! 105 PAD 1+ C! PAD 2 TYPE CR", out="Hi\n", why=D1 + "; and " + RS),
"TYPE.numbers": dict(text="49 PAD C! 50 PAD 1+ C! PAD 2 TYPE CR", out="12\n", why=D1 + "; and " + RS),
">IN.initial": dict(out="9 \n", why="FORTH-79: >IN is the offset into the input, 9 here; v3 prints an address"),
"WORD.empty_input": None, "WORD.space_delim": None, "WORD.newline_delim": None, "WORD.tab_delim": None,
"WORD.comma_delim": None, "WORD.skip_leading": None, "WORD.single_char": None, "WORD.long_word": None,
"WORD.zero_delim": None, "WORD.high_ascii": None, "WORD.count_format": None, "WORD.count_value": None,
"WORD.multi_delim": None,
"UPDATE.no_block": dict(why=V3ERR),
"SCR.after_load": dict(out="1 \n", why="v3 prints nothing after 1 LOAD; SCR is still 1 from the LIST before"),
"FIND.existing": dict(depth=0, why="FORTH-79: FIND takes the next word, DUP here, and leaves its address, which is printed"),
"FIND.user_word": dict(depth=0, why="the address FIND leaves is printed"),
"FIND.empty": dict(stack=[0], why="FORTH-79: FIND leaves 0 when there is no word; v3 raises an error"),
"VOCABULARY.cross_vocab_access": dict(error=True, why=VOC),
"VOCABULARY.duplicate": dict(why=V3ERR + ": defining a name again"),
"IF.true": dict(out="42 \n", why="v3 runs an older TEST1 from another vocabulary and leaves the flag"),
"IF.false": dict(out="24 \n", why="v3 runs an older TEST2 from another vocabulary and leaves the flag"),
"LEAVE.basic": dict(out="4 \n", why=LEAVE),
"LEAVE.at_start": dict(out="1 \n", why=LEAVE),
"LEAVE.qdloop": dict(out="3 \n", why=LEAVE),
}
# Found 2026-10-06 (docs/v4.0.0/MESH.md section 10, step 4): on v4 HERE is a
# cell address and C! C@ FILL CMOVE COUNT TYPE take byte addresses, so these
# cases wrote into, or read from, the nucleus's own code at cell HERE/4 --
# and passed, reading back what they had written there. Ruled: left out
# until how HERE and the byte words agree is settled, as its own step.
HERE_BYTE = ("OPEN: HERE is a cell address on v4 and this word takes a byte address (D-1): "
"the case wrote to or read from the nucleus's code at cell HERE/4")
# Dropped, and why. mkpost.py prints these with the cases it leaves out.
DROPPED = {
"PAD.usable": "OPEN: PAD 42 OVER ! faults on v4 -- PAD is a byte address and ! takes a cell address (D-1)",
"C!.basic": HERE_BYTE,
"C!.zero": HERE_BYTE,
"C!.high_byte": HERE_BYTE,
"C!.truncation": HERE_BYTE,
"C@.after_cstore": HERE_BYTE,
"C@.zero_byte": HERE_BYTE,
"FILL.basic": HERE_BYTE,
"FILL.zero_byte": HERE_BYTE,
"CMOVE.overlap": HERE_BYTE,
"TYPE.single_char": HERE_BYTE,
"COUNT.string_bounds": HERE_BYTE,
"COUNT.zero_addr_plus_one": HERE_BYTE,
"CMOVE.bounds": "moves 1000 bytes over the system; v3 refuses by a bounds rule FORTH-79 has not got",
}
for _name in [k for k, v in STANDARD.items() if v is None and k.startswith("WORD.")]:
DROPPED[_name] = V3WORD
EXTRA = [
dict(label="U*.small", text="3 4 U* . . CR", out="0 12 \n"),
dict(label="U*.zero", text="0 5 U* . . CR", out="0 0 \n"),
dict(label="U*.full", text="-1 -1 U* 2+ . 1 = . CR", out="0 -1 \n"), # (2^N-1)^2: high cell 2^N-2, low cell 1
dict(label="U/MOD.small", text="7 0 2 U/MOD . . CR", out="3 1 \n"),
dict(label="U/MOD.exact", text="12 0 4 U/MOD . . CR", out="3 0 \n"),
dict(label="U/MOD.double", text="0 1 2 U/MOD 2* . . CR", out="0 0 \n"), # 2^N / 2: quotient 2^(N-1), remainder 0
dict(label="U/MOD.inverse", text="-1 -1 U* -1 U/MOD 1+ . . CR", out="0 0 \n"),
dict(label="U/MOD.carry", text="-1 -2 -1 U/MOD 1+ . 2+ . CR", out="0 0 \n"),
dict(label="U/MOD.by_zero", text="1 0 0 U/MOD", error=True),
dict(label="?.basic", text="VARIABLE Q1 42 Q1 ! Q1 ? CR", out="42 \n"),
dict(label="EXECUTE.found", text=": E1 7 ; FIND E1 EXECUTE . CR", out="7 \n"),
dict(label="U..small", text="42 U. CR", out="42 \n"),
dict(label="U..unsigned", text="-1 U. CR", out="18446744073709551615 \n"),
dict(label="CONVERT.digits", text=": C1 0 0 BL WORD CONVERT DROP ; C1 123 . . CR", out="0 123 \n"),
dict(label="'.found", text="' DUP 0= . CR", out="0 \n"),
dict(label='.".in_definition', text=': Q3 ." hi there" ; Q3 CR', out="hi there\n"),
dict(label="LITERAL.basic", text=": Q4 [ 5 ] LITERAL ; Q4 . CR", out="5 \n"),
dict(label="STATE.interpreting", text="STATE @ . CR", out="0 \n"),
dict(label="STATE.compiling", text=": Q5 STATE @ 0= . ; IMMEDIATE : Q6 Q5 ; CR", out="0 \n"),
dict(label="COMPILE.basic", text=": Q7 COMPILE DUP ; IMMEDIATE : Q8 Q7 ; 3 Q8 . . CR", out="3 3 \n"),
dict(label="[COMPILE].basic", text=": Q9 [COMPILE] IF ; IMMEDIATE : Q10 Q9 1 ELSE 2 THEN ; 0 Q10 . -1 Q10 . CR", out="2 1 \n"),
dict(label="(.comment", text="1 ( 2 ) 3 . . CR", out="3 1 \n"),
dict(label="BLK.terminal", text="BLK @ . CR", out="0 \n"),
dict(label="79-STANDARD.present", text="79-STANDARD"),
dict(label="WORD.next_word", text=": W1 BL WORD COUNT TYPE ; W1 HELLO", out="HELLO"),
dict(label="WORD.delimiter", text=": W2 44 WORD COUNT TYPE ; W2 A B,", out="A B"),
dict(label="WORD.count", text=": W3 BL WORD C@ . ; W3 ABC", out="3 "),
dict(label="WORD.skips_leading", text=": W4 BL WORD COUNT TYPE ; W4 X", out="X"),
]