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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5d6043e37e
commit
2930349bbf
+70
-68
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-10-05T20:03:22Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-10-05T20:24:47Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
@@ -29,8 +29,8 @@
|
||||
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | n/a |
|
||||
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | n/a |
|
||||
| `user-font-demo.4th` | 4200, 4201, 4202 | `0xce1fd7d1b581a56d` | n/a |
|
||||
| `v4:forth79.4th` | 6000 | `0xa4b74bdc7deaf204` | n/a |
|
||||
| `v4:post79.4th` | 7000, 7001, 7002, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7011, 7012, 7013, 7014, 7015, 7016, 7017, 7018, 7019, 7020, 7021, 7022, 7023, 7024, 7025, 7026, 7027, 7028, 7029, 7030, 7031, 7032, 7033, 7034, 7035, 7036, 7037, 7038, 7039, 7040, 7041, 7042, 7043, 7044, 7045, 7046, 7047, 7048, 7049, 7050, 7051, 7052, 7053, 7054, 7055, 7056, 7057, 7058, 7059, 7060, 7061, 7062, 7063 | `0x24df7b11992128ed` | n/a |
|
||||
| `v4:forth79.4th` | 6000, 6001, 6002 | `0x4055641ee17d176b` | n/a |
|
||||
| `v4:post79.4th` | 7000, 7001, 7002, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7011, 7012, 7013, 7014, 7015, 7016, 7017, 7018, 7019, 7020, 7021, 7022, 7023, 7024, 7025, 7026, 7027, 7028, 7029, 7030, 7031, 7032, 7033, 7034, 7035, 7036, 7037, 7038, 7039, 7040, 7041, 7042, 7043, 7044, 7045, 7046, 7047, 7048, 7049, 7050, 7051, 7052, 7053, 7054, 7055, 7056, 7057, 7058, 7059, 7060, 7061, 7062, 7063 | `0xb987dbb4388990bd` | n/a |
|
||||
| `workload-0.4th` | 2200, 2201 | `0x93f86f60aeba8feb` | n/a |
|
||||
| `workload-1-lite.4th` | 5058, 5059 | `0x44a7a7e3176dcc8d` | n/a |
|
||||
| `workload-1.4th` | 4406, 4415, 4425, 4435 | `0x63e251adb0a03613` | n/a |
|
||||
@@ -368,71 +368,73 @@
|
||||
| 5113 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
|
||||
| 5114 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
|
||||
| 5115 | `sdk.4th` | `0x008fdbbb62c94a3a` | ok |
|
||||
| 6000 | `v4:forth79.4th` | `0xa4b74bdc7deaf204` | ok |
|
||||
| 7000 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7001 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7002 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7003 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7004 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7005 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7006 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7007 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7008 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7009 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7010 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7011 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7012 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7013 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7014 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7015 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7016 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7017 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7018 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7019 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7020 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7021 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7022 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7023 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7024 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7025 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7026 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7027 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7028 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7029 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7030 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7031 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7032 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7033 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7034 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7035 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7036 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7037 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7038 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7039 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7040 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7041 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7042 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7043 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7044 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7045 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7046 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7047 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7048 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7049 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7050 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7051 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7052 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7053 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7054 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7055 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7056 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7057 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7058 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7059 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7060 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7061 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7062 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 7063 | `v4:post79.4th` | `0x24df7b11992128ed` | ok |
|
||||
| 6000 | `v4:forth79.4th` | `0x4055641ee17d176b` | ok |
|
||||
| 6001 | `v4:forth79.4th` | `0x4055641ee17d176b` | ok |
|
||||
| 6002 | `v4:forth79.4th` | `0x4055641ee17d176b` | ok |
|
||||
| 7000 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7001 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7002 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7003 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7004 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7005 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7006 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7007 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7008 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7009 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7010 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7011 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7012 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7013 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7014 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7015 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7016 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7017 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7018 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7019 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7020 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7021 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7022 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7023 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7024 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7025 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7026 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7027 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7028 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7029 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7030 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7031 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7032 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7033 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7034 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7035 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7036 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7037 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7038 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7039 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7040 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7041 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7042 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7043 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7044 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7045 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7046 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7047 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7048 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7049 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7050 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7051 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7052 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7053 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7054 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7055 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7056 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7057 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7058 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7059 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7060 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7061 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7062 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
| 7063 | `v4:post79.4th` | `0xb987dbb4388990bd` | ok |
|
||||
|
||||
## Conflicts
|
||||
|
||||
|
||||
+23
-1
@@ -3,4 +3,26 @@ Block 6000
|
||||
( as colon definitions, loaded when the system boots. )
|
||||
( docs/v4.0.0/NUCLEUS.md. Blocks 6000 up. )
|
||||
( A word moves here from the assembled nucleus, v4/capsule, )
|
||||
( once its colon definition passes POST. None has moved yet. )
|
||||
( once its colon definition passes POST. )
|
||||
Block 6001
|
||||
( U* U/MOD -- FORTH-79 unsigned multiply and divide. )
|
||||
( Neither v3 nor the v4 nucleus had them. )
|
||||
( The first two words count the bits in a cell. )
|
||||
: (BITS) ( -- n )
|
||||
0 1 BEGIN DUP WHILE 2* SWAP 1+ SWAP REPEAT DROP ;
|
||||
(BITS) CONSTANT (NB)
|
||||
VARIABLE (UD)
|
||||
( u1 u2 -- ud the unsigned double product )
|
||||
: U* UM* ;
|
||||
Block 6002
|
||||
( ud u1 -- u2 u3 unsigned: u2 the remainder, u3 the quotient )
|
||||
( One bit a step: shift ud left, and take u1 from its high )
|
||||
( cell whenever it goes, counting that in the low cell. )
|
||||
( A zero divisor is error 11, Division by zero. )
|
||||
: U/MOD
|
||||
DUP 0= IF DROP DROP DROP 11 NODE-ERROR ! THEN (UD) !
|
||||
(NB) 0 DO
|
||||
DUP 0< >R 2* OVER 0< IF 1+ THEN SWAP 2* SWAP
|
||||
R> IF (UD) @ - SWAP 1+ SWAP
|
||||
ELSE DUP (UD) @ U< 0= IF (UD) @ - SWAP 1+ SWAP THEN THEN
|
||||
LOOP SWAP ;
|
||||
|
||||
+310
-263
File diff suppressed because it is too large
Load Diff
+26
-4
@@ -178,10 +178,32 @@ and names each failing case.
|
||||
|
||||
### 6.4 Generating the expected values
|
||||
|
||||
A script reads the v3 test modules, keeps the cases for Required Word Set
|
||||
words, pipes each line through the hosted v3 binary, and writes the
|
||||
`T{ ... }T` lines. It is a development tool, run when the cases change; its
|
||||
output, `post79.4th`, is committed and reviewed like any source.
|
||||
`v4/tools/mkpost.py` reads the v3 test modules, keeps the cases for
|
||||
Required Word Set words, runs them in one session of the hosted v3 binary,
|
||||
and writes `capsules/v4/post79.4th`. It is a development tool
|
||||
(`make -C v4 post79`), run when the cases or the rules change; its output
|
||||
is committed and reviewed like any source. Every case starts from the same
|
||||
state on both machines: empty stack, `DECIMAL`, `FORTH DEFINITIONS`.
|
||||
|
||||
### 6.5 Where POST does not take v3's word (ruled 2026-10-05)
|
||||
|
||||
`v4/tools/post79_rules.py` holds the exceptions, each with its reason, and
|
||||
`mkpost.py` writes them out as `POST79.md`, which lists every one.
|
||||
|
||||
1. **Address-dependent cases.** A case that leaves or prints a memory
|
||||
address is checked for the number of values it leaves, and for what it
|
||||
prints unless an address is printed. The values are not compared.
|
||||
2. **Where v3 departs from FORTH-79.** The expected result is the
|
||||
standard's. Where v3's line is not valid FORTH-79 (`>R` at the prompt, a
|
||||
single number given to `<#`), the line is rewritten so that it is.
|
||||
3. **Cases written for v3's machine.** A v4 address unit is a cell (D-1);
|
||||
cases that assume v3's byte addresses are rewritten in v4's terms.
|
||||
4. **Words v3 has no case for** get cases written by hand, with the
|
||||
standard's results. `U*` and `U/MOD`, which neither v3 nor the v4
|
||||
nucleus had, are colon definitions in `forth79.4th`.
|
||||
|
||||
Not tested: `KEY`, `EXPECT` and `QUERY`, which wait for the keyboard, and
|
||||
`QUIT`, which returns to the terminal without ` ok`.
|
||||
|
||||
## 7. Hashing, signing and parity
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
# 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 |
|
||||
@@ -0,0 +1,112 @@
|
||||
[2J[001;001H[=3h[2J[001;001H[2J[001;001H[8;042;160t[2J[001;001H[2J[001;001H[8;056;240t[2J[001;001HBdsDxe: loading Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
|
||||
BdsDxe: starting Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
|
||||
[2J[001;001H[=3h[2J[001;001HStarKernel UEFI Loader
|
||||
Loading kernel from ESP...
|
||||
[CKPT 001] Entered efi_main - ConOut live
|
||||
RAW SERIAL UP
|
||||
[CKPT 002] Serial (COM1) initialized
|
||||
Monolithic build - kernel linked directly
|
||||
Collecting boot information...
|
||||
CmdLine: parsed OK
|
||||
[CKPT 004] Command line parsed
|
||||
[CKPT 005] Kernel stack allocation decided
|
||||
[CKPT 006] Boot info collected (ACPI table located)
|
||||
GOP: linear framebuffer found
|
||||
[CKPT 007] GOP: linear framebuffer found
|
||||
[CKPT 008] About to enter ExitBootServices retry loop
|
||||
EBS...
|
||||
EBS OK
|
||||
Calling kernel_main (monolithic)...
|
||||
|
||||
|
||||
_____ _ _ __ _
|
||||
/ ____| | | |/ / | |
|
||||
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
|
||||
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
|
||||
____) | || (_| | | | . \ __/ | | | | | __/ |
|
||||
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
|
||||
|
||||
LithosAnanke v2.1.0
|
||||
Architecture: amd64
|
||||
Build: Oct 5 2026 16:18:59
|
||||
|
||||
UEFI BootServices: EXITED
|
||||
|
||||
=== StarKernel Boot Information ===
|
||||
Memory map entries: 135
|
||||
Total memory: 1023 MB
|
||||
Usable memory: 968 MB
|
||||
===================================
|
||||
|
||||
PMM initialized.
|
||||
PMM statistics:
|
||||
Total pages: 249248
|
||||
Free pages : 247725
|
||||
Used pages : 1523
|
||||
Total MB : 973
|
||||
Free MB : 967
|
||||
Used MB : 5
|
||||
|
||||
VMM initialized (mapped RAM, CR3 switched)
|
||||
VMM initialized (mapped RAM, CR3 switched)
|
||||
VMM self-test: mapped OK at 0xffff800000000000
|
||||
VMM self-test complete.
|
||||
|
||||
IDT installed.
|
||||
|
||||
APIC: init...
|
||||
APIC: IA32_APIC_BASE MSR=0x00000000fee00900
|
||||
APIC: stale-ISR drain: 0 EOI(s) issued
|
||||
APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear)
|
||||
APIC: init done
|
||||
|
||||
I/O APIC: init...
|
||||
I/O APIC: base=0xfec00000, gsi_base=0, overrides=5
|
||||
override: source=00 gsi=2 flags=0x0000
|
||||
override: source=05 gsi=5 flags=0x000d
|
||||
override: source=09 gsi=9 flags=0x000d
|
||||
override: source=0a gsi=10 flags=0x000d
|
||||
override: source=0b gsi=11 flags=0x000d
|
||||
i8042: keyboard ACKed enable-scanning
|
||||
i8042: IRQ1 enabled in controller config byte
|
||||
I/O APIC: keyboard IRQ1 routed (masked)
|
||||
|
||||
Timer: init...
|
||||
Timer: init start
|
||||
Timer: PM_TMR_BLK discovered from FADT at port 1544
|
||||
Timer: VM mode detected (hypervisor present).
|
||||
Timer: HPET calibration disabled (VM-exit MMIO would poison timing).
|
||||
Timer: WARNING: invariant TSC not present under hypervisor.
|
||||
Timer: continuing in RELATIVE mode (no determinism guarantees).
|
||||
Timer: RDTSCP not present; using RDTSC (less serialized).
|
||||
Timer: CPUID frequency unavailable; trying PM Timer...
|
||||
Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2100864460 Hz
|
||||
Timer: init done
|
||||
|
||||
Kernel heap initialized.
|
||||
Heap statistics:
|
||||
Total bytes: 536870872
|
||||
Free bytes: 536870872
|
||||
Used bytes: 0
|
||||
Peak bytes: 0
|
||||
Heap base addr: 24641536
|
||||
Heap end addr: 561512448
|
||||
|
||||
Heartbeat: init...
|
||||
APIC Timer: calibrating...
|
||||
APIC Timer: apic_hz=1006177928, tick_hz=100, initial_count=10061779
|
||||
APIC Timer: configured (masked, ready to start)
|
||||
Heartbeat: init done
|
||||
Kernel initialization complete.
|
||||
Boot successful!
|
||||
|
||||
StarForth v4: one host node, the F18-derived engine
|
||||
PARITY:V4_NUCLEUS words=295 image_hash=0x3ed5d5fc83f0b026
|
||||
V4: capsule v4:forth79.4th signature: missing (unsigned)
|
||||
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x1ec33b48cc76a0b5
|
||||
V4: capsule v4:post79.4th signature: missing (unsigned)
|
||||
PARITY:V4_POST tests=550 pass=550 fail=0
|
||||
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x671fdd7aebf0f8d8
|
||||
PARITY:OK
|
||||
POST: PASSED
|
||||
ok>
|
||||
@@ -0,0 +1,86 @@
|
||||
UEFI firmware (version 2025.11-3ubuntu7.3 built at 15:40:26 on Sep 23 2026)
|
||||
[2J[001;001H[=3h[2J[001;001H[2J[001;001H[8;031;100t[2J[001;001HBdsDxe: failed to load Boot0002 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x2,0x0): Not Found
|
||||
BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU USB HARDDRIVE 1-0000:00:04.0-1" from PciRoot(0x0)/Pci(0x4,0x0)/USB(0x0,0x0): Not Found
|
||||
BdsDxe: loading Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0)
|
||||
BdsDxe: starting Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0)
|
||||
[2J[001;001H[=3h[2J[001;001HStarKernel UEFI Loader
|
||||
Loading kernel from ESP...
|
||||
[CKPT 001] Entered efi_main - ConOut live
|
||||
Collecting boot information...
|
||||
[CKPT 004] Command line parsed
|
||||
[CKPT 005] Kernel stack allocation decided
|
||||
[CKPT 006] Boot info collected (ACPI table located)
|
||||
[CKPT 007] GOP: linear framebuffer found
|
||||
[CKPT 008] About to enter ExitBootServices retry loop
|
||||
|
||||
|
||||
_____ _ _ __ _
|
||||
/ ____| | | |/ / | |
|
||||
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
|
||||
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
|
||||
____) | || (_| | | | . \ __/ | | | | | __/ |
|
||||
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
|
||||
|
||||
LithosAnanke v2.1.0
|
||||
Architecture: aarch64
|
||||
Build: Oct 5 2026 16:21:17
|
||||
|
||||
UEFI BootServices: EXITED
|
||||
|
||||
=== StarKernel Boot Information ===
|
||||
Memory map entries: 108
|
||||
Total memory: 4093 MB
|
||||
Usable memory: 4056 MB
|
||||
===================================
|
||||
|
||||
PMM initialized.
|
||||
PMM statistics:
|
||||
Total pages: 1039860
|
||||
Free pages : 1038449
|
||||
Used pages : 1411
|
||||
Total MB : 4061
|
||||
Free MB : 4056
|
||||
Used MB : 5
|
||||
|
||||
VMM initialized (mapped RAM, CR3 switched)
|
||||
VMM initialized (mapped RAM, CR3 switched)
|
||||
VMM self-test: mapped OK at 0xffff800000000000
|
||||
VMM self-test complete.
|
||||
|
||||
AArch64: running at EL1
|
||||
IDT installed.
|
||||
|
||||
APIC: init...
|
||||
PSCI: no DTB -- using HVC (QEMU virt-machine default)
|
||||
GICv2: no DTB GIC node -- using QEMU virt-machine defaults
|
||||
GICv2: distributor+CPU interface enabled, PPI 30
|
||||
APIC: init done
|
||||
|
||||
Timer: init...
|
||||
Timer: AArch64 generic timer initialised.
|
||||
Timer: init done
|
||||
|
||||
Kernel heap initialized.
|
||||
Heap statistics:
|
||||
Total bytes: 2147483608
|
||||
Free bytes: 2147483608
|
||||
Used bytes: 0
|
||||
Peak bytes: 0
|
||||
Heap base addr: 1207959552
|
||||
Heap end addr: 3355443200
|
||||
|
||||
Heartbeat: init...
|
||||
Heartbeat: init done
|
||||
Kernel initialization complete.
|
||||
Boot successful!
|
||||
|
||||
StarForth v4: one host node, the F18-derived engine
|
||||
PARITY:V4_NUCLEUS words=295 image_hash=0x3ed5d5fc83f0b026
|
||||
V4: capsule v4:forth79.4th signature: missing (unsigned)
|
||||
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x1ec33b48cc76a0b5
|
||||
V4: capsule v4:post79.4th signature: missing (unsigned)
|
||||
PARITY:V4_POST tests=550 pass=550 fail=0
|
||||
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x671fdd7aebf0f8d8
|
||||
PARITY:OK
|
||||
POST: PASSED
|
||||
ok>
|
||||
@@ -0,0 +1,156 @@
|
||||
|
||||
OpenSBI v1.8
|
||||
____ _____ ____ _____
|
||||
/ __ \ / ____| _ \_ _|
|
||||
| | | |_ __ ___ _ __ | (___ | |_) || |
|
||||
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
|
||||
| |__| | |_) | __/ | | |____) | |_) || |_
|
||||
\____/| .__/ \___|_| |_|_____/|____/_____|
|
||||
| |
|
||||
|_|
|
||||
|
||||
Platform Name : riscv-virtio,qemu
|
||||
Platform Features : medeleg
|
||||
Platform HART Count : 1
|
||||
Platform HART Protection : pmp
|
||||
Platform IPI Device : aclint-mswi
|
||||
Platform Timer Device : aclint-mtimer @ 10000000Hz
|
||||
Platform Console Device : uart8250
|
||||
Platform HSM Device : ---
|
||||
Platform PMU Device : ---
|
||||
Platform Reboot Device : syscon-reboot
|
||||
Platform Shutdown Device : syscon-poweroff
|
||||
Platform Suspend Device : ---
|
||||
Platform CPPC Device : ---
|
||||
Firmware Base : 0x80000000
|
||||
Firmware Size : 321 KB
|
||||
Firmware RW Offset : 0x40000
|
||||
Firmware RW Size : 65 KB
|
||||
Firmware Heap Offset : 0x47000
|
||||
Firmware Heap Size : 37 KB (total), 0 KB (reserved), 12 KB (used), 23 KB (free)
|
||||
Firmware Scratch Size : 4096 B (total), 1464 B (used), 2632 B (free)
|
||||
Runtime SBI Version : 3.0
|
||||
Standard SBI Extensions : ipi,pmu,srst,sse,hsm,rfnc,fwft,time,base,legacy,dbcn,dbtr
|
||||
Experimental SBI Extensions : none
|
||||
|
||||
Domain0 Name : root
|
||||
Domain0 Boot HART : 0
|
||||
Domain0 HARTs : 0*
|
||||
Domain0 Region00 : 0x0000000080040000-0x000000008005ffff M: (F,R,W) S/U: ()
|
||||
Domain0 Region01 : 0x0000000080000000-0x000000008003ffff M: (F,R,X) S/U: ()
|
||||
Domain0 Region02 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
|
||||
Domain0 Region03 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
|
||||
Domain0 Region04 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
|
||||
Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
|
||||
Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
|
||||
Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
|
||||
Domain0 Next Address : 0x0000000020000000
|
||||
Domain0 Next Arg1 : 0x00000000bfe00000
|
||||
Domain0 Next Mode : S-mode
|
||||
Domain0 SysReset : yes
|
||||
Domain0 SysSuspend : yes
|
||||
|
||||
Boot HART ID : 0
|
||||
Boot HART Domain : root
|
||||
Boot HART Priv Version : v1.12
|
||||
Boot HART Base ISA : rv64imafdch
|
||||
Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
|
||||
Boot HART PMP Count : 16
|
||||
Boot HART PMP Granularity : 2 bits
|
||||
Boot HART PMP Address Bits : 54
|
||||
Boot HART MHPM Info : 16 (0x0007fff8)
|
||||
Boot HART Debug Triggers : 2 triggers
|
||||
Boot HART MIDELEG : 0x0000000000001666
|
||||
Boot HART MEDELEG : 0x0000000000f4b509
|
||||
[2J[004D[=3h[2J[009D[2J[004D[8;031;100t[2J[016DRISC-V EDK2 firmware version 2025.11-3ubuntu7.3
|
||||
Press ESCAPE within 5 seconds for boot options ERROR: C40000002:V03051002 I0 6D33944A-EC75-4855-A54D-809C75241F6C 83FFF850
|
||||
BdsDxe: failed to load Boot0001 "UEFI Misc Device" fr
|
||||
om PciRoot(0x0)/Pci(0x1,0x0): Not Found
|
||||
[2J[001;001H[=3h[2J[009DStarKernel UEFI Loader
|
||||
Loading kernel from ESP...
|
||||
[CKPT 001] Entered efi_main - ConOut live
|
||||
Monolithic build - kernel linked directly
|
||||
Collecting boot information...
|
||||
CmdLine: parsed OK
|
||||
[CKPT 004] Command line parsed
|
||||
[CKPT 005] Kernel stack allocation decided
|
||||
[CKPT 006] Boot info collected (ACPI table located)
|
||||
GOP: linear framebuffer found
|
||||
[CKPT 007] GOP: linear framebuffer found
|
||||
[CKPT 008] About to enter ExitBootServices retry loop
|
||||
Calling kernel_main (monolithic)...
|
||||
riscv64 item 4.3.5a: satp state at kernel entry (before switch)
|
||||
satp.MODE = 0x000000000000000a
|
||||
satp.PPN = 0x00000000000bf868
|
||||
__kernel_start = 0x00000000bde97936
|
||||
riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a)
|
||||
|
||||
|
||||
_____ _ _ __ _
|
||||
/ ____| | | |/ / | |
|
||||
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
|
||||
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
|
||||
____) | || (_| | | | . \ __/ | | | | | __/ |
|
||||
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
|
||||
|
||||
LithosAnanke v2.1.0
|
||||
Architecture: riscv64
|
||||
Build: Oct 5 2026 16:24:48
|
||||
|
||||
UEFI BootServices: EXITED
|
||||
|
||||
=== StarKernel Boot Information ===
|
||||
Memory map entries: 100
|
||||
Total memory: 1020 MB
|
||||
Usable memory: 977 MB
|
||||
===================================
|
||||
|
||||
PMM initialized.
|
||||
PMM statistics:
|
||||
Total pages: 250774
|
||||
Free pages : 250122
|
||||
Used pages : 652
|
||||
Total MB : 979
|
||||
Free MB : 977
|
||||
Used MB : 2
|
||||
|
||||
VMM initialized (mapped RAM, CR3 switched)
|
||||
VMM initialized (mapped RAM, CR3 switched)
|
||||
VMM self-test: mapped OK at 0xffff800000000000
|
||||
VMM self-test complete.
|
||||
|
||||
IDT installed.
|
||||
|
||||
APIC: init...
|
||||
PLIC: no DTB PLIC node -- using QEMU virt-machine default (base=0x0c000000)
|
||||
PLIC: init (S-mode context 1, threshold=0)
|
||||
APIC: init done
|
||||
|
||||
Timer: init...
|
||||
Timer: RISC-V time CSR @ 10000000 Hz (FALLBACK, no devicetree)
|
||||
Timer: init done
|
||||
|
||||
Kernel heap initialized.
|
||||
Heap statistics:
|
||||
Total bytes: 536870872
|
||||
Free bytes: 536870872
|
||||
Used bytes: 0
|
||||
Peak bytes: 0
|
||||
Heap base addr: 2214588416
|
||||
Heap end addr: 2751459328
|
||||
|
||||
Heartbeat: init...
|
||||
Heartbeat: init done
|
||||
Kernel initialization complete.
|
||||
Boot successful!
|
||||
|
||||
StarForth v4: one host node, the F18-derived engine
|
||||
PARITY:V4_NUCLEUS words=295 image_hash=0x3ed5d5fc83f0b026
|
||||
V4: capsule v4:forth79.4th signature: missing (unsigned)
|
||||
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x1ec33b48cc76a0b5
|
||||
V4: capsule v4:post79.4th signature: missing (unsigned)
|
||||
PARITY:V4_POST tests=550 pass=550 fail=0
|
||||
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x671fdd7aebf0f8d8
|
||||
PARITY:OK
|
||||
POST: PASSED
|
||||
ok>
|
||||
+9
-12
@@ -129,27 +129,24 @@ $(BINDIR)/boot-$(1).txt: $(BINDIR)/starforth4-$(1)
|
||||
endef
|
||||
$(foreach i,$(HOSTED_ISAS),$(eval $(call HOSTED_RULE,$(i))))
|
||||
|
||||
# `make post` builds the amd64 system with POST in its boot
|
||||
# (V4_POST_AT_BOOT=1, system/boot.c) and boots it: the POST capsule,
|
||||
# ../capsules/v4/post79.4th, is loaded after forth79.4th and must end with a
|
||||
# clean tally. `make post79` writes that capsule again from v3's cases
|
||||
# (tools/mkpost.py); it needs the hosted v3 binary.
|
||||
# `make post` boots the amd64 system alone, which is the quick check after a
|
||||
# change to the vocabulary: nucleus, forth79.4th, POST, prompt. `make
|
||||
# post79` writes the POST capsule, ../capsules/v4/post79.4th, again from
|
||||
# v3's cases and tools/post79_rules.py (tools/mkpost.py); it needs the
|
||||
# hosted v3 binary.
|
||||
.PHONY: post post79
|
||||
$(BINDIR)/starforth4-post: $(HERE)/tools/hosted.c $(BINDIR)/v4_image_64.c $(CAPSULE_DIR_C) $(ENGINE_SRCS) $(SYSTEM_SRCS) $(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
|
||||
$(CC_amd64) $(CFLAGS) -D_POSIX_C_SOURCE=200809L -I$(HERE)/include -DV4_CELL_BITS=64 $(HOST_DEFS) -c $(HERE)/tools/hosted.c -o $(BINDIR)/hosted-post.o
|
||||
$(CC_amd64) $(SYSTEM_CFLAGS) -DV4_POST_AT_BOOT=1 -static $(BINDIR)/hosted-post.o $(BINDIR)/v4_image_64.c $(CAPSULE_DIR_C) $(ENGINE_SRCS) $(SYSTEM_SRCS) -o $@
|
||||
post: $(BINDIR)/starforth4-post
|
||||
@$(BINDIR)/starforth4-post < /dev/null
|
||||
post: $(BINDIR)/starforth4-amd64
|
||||
@$(BINDIR)/starforth4-amd64 < /dev/null
|
||||
post79:
|
||||
python3 $(HERE)/tools/mkpost.py
|
||||
|
||||
# `make hosted` builds the three. `make hosted-check` boots each with no
|
||||
# input and requires that all three print the same lines, ending in
|
||||
# PARITY:OK and the prompt: the same hashes on every ISA.
|
||||
# PARITY:OK, POST: PASSED and the prompt: the same hashes on every ISA.
|
||||
.PHONY: hosted hosted-check
|
||||
hosted: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/starforth4-$(i))
|
||||
hosted-check: $(foreach i,$(HOSTED_ISAS),$(BINDIR)/boot-$(i).txt)
|
||||
@grep -q '^PARITY:OK$$' $(BINDIR)/boot-amd64.txt || { echo "hosted-check: no PARITY:OK"; exit 1; }
|
||||
@grep -q '^POST: PASSED$$' $(BINDIR)/boot-amd64.txt || { echo "hosted-check: no POST: PASSED"; exit 1; }
|
||||
@cmp $(BINDIR)/boot-amd64.txt $(BINDIR)/boot-aarch64.txt
|
||||
@cmp $(BINDIR)/boot-amd64.txt $(BINDIR)/boot-riscv64.txt
|
||||
@echo "hosted-check: amd64, aarch64 and riscv64 boot identically"
|
||||
|
||||
+32
-37
@@ -55,58 +55,53 @@ and fails unless their output is identical and ends in `PARITY:OK`.
|
||||
A capsule line the node does not answer ` ok` to ends the boot, naming the
|
||||
capsule, block and line, with `PARITY:FAIL` and `POST: FAILED`.
|
||||
|
||||
State, 2026-10-05, after the POST work began: all six builds load
|
||||
`v4:forth79.4th` and reach `ok>` with the same lines:
|
||||
State, 2026-10-05: all six builds start the nucleus, load `v4:forth79.4th`,
|
||||
pass POST and reach `ok>`, with the same lines:
|
||||
|
||||
```
|
||||
PARITY:V4_NUCLEUS words=295 image_hash=0x3ed5d5fc83f0b026
|
||||
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0xa4b74bdc7deaf204 capsule_hash=0xa4b74bdc7deaf204 dict_hash=0x118de9d37e682ea4
|
||||
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x1ec33b48cc76a0b5
|
||||
PARITY:V4_POST tests=550 pass=550 fail=0
|
||||
PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb987dbb4388990bd capsule_hash=0xb987dbb4388990bd dict_hash=0x671fdd7aebf0f8d8
|
||||
PARITY:OK
|
||||
POST: PASSED
|
||||
ok>
|
||||
```
|
||||
|
||||
Bare-metal logs: `logs/20261005-160144/amd64/`, `logs/20261005-160234/aarch64/`,
|
||||
`logs/20261005-160400/riscv64/`. Each run was ended once the prompt was in the
|
||||
Bare-metal logs: `logs/20261005-161924/amd64/`, `logs/20261005-162142/aarch64/`,
|
||||
`logs/20261005-162524/riscv64/`. Each run was ended once the prompt was in the
|
||||
log; nothing was typed at a bare-metal prompt, so keyboard input there is not
|
||||
yet verified. The capsules were unsigned (no signing key on this machine).
|
||||
|
||||
`forth79.4th` holds no definitions yet: every word is still in the nucleus.
|
||||
**What this does not yet show.** `forth79.4th` holds two definitions, `U*`
|
||||
and `U/MOD`. Every other word is still in the assembled nucleus, so POST is
|
||||
so far a test of the assembled words. Moving them to the capsule, a group at
|
||||
a time with POST after each, is the next step (`NUCLEUS.md` section 8).
|
||||
|
||||
## POST
|
||||
|
||||
`capsules/v4/post79.4th` is v3's POST cases for the FORTH-79 Required Word
|
||||
Set, written by `v4/tools/mkpost.py` (`make -C v4 post79`): 537 cases in
|
||||
blocks 7000–7112, covering 111 of the 130 required words. Each case carries
|
||||
what the hosted v3 binary did with the same line — error or not, the stack,
|
||||
the length and checksum of what was printed — and v4 must do the same.
|
||||
`capsules/v4/post79.4th`: 550 cases in blocks 7000 up, covering 126 of the
|
||||
130 words of the FORTH-79 Required Word Set. 443 are v3's cases with what
|
||||
the hosted v3 binary did as the expected result. The other 107, and the 27
|
||||
v3 cases left out, are listed with reasons in `docs/v4.0.0/POST79.md`.
|
||||
|
||||
**POST does not pass yet, so it is not in the boot.** `make -C v4 post` builds
|
||||
the amd64 system with it (`-DV4_POST_AT_BOOT=1`) and boots it:
|
||||
- `make -C v4 post` boots the amd64 system: the quick check after a change.
|
||||
- `make -C v4 hosted-check` boots all three hosted ISAs and compares them.
|
||||
- `make -C v4 post79` writes the capsule again (`tools/mkpost.py`,
|
||||
`tools/post79_rules.py`); it needs the hosted v3 binary.
|
||||
|
||||
```
|
||||
PARITY:V4_POST tests=537 pass=439 fail=98
|
||||
PARITY:FAIL
|
||||
POST: FAILED
|
||||
```
|
||||
POST is FORTH: a harness of FORTH-79 words and two nucleus hooks, `(CATCH)`
|
||||
and `(EMIT-HOOK)`. It forgets itself when it has finished. The boot passes
|
||||
only on seeing POST's tally line with `fail=0` (`v4/system/boot.c`). A colon
|
||||
definition broken on purpose (`U/MOD` without its last `SWAP`) fails five
|
||||
cases and the boot stops with `POST: FAILED`.
|
||||
|
||||
The 98 are not yet sorted into v4 defects and differences that need a
|
||||
ruling. A first reading finds four kinds:
|
||||
Not tested: `KEY`, `EXPECT`, `QUERY` (the keyboard) and `QUIT` (it returns
|
||||
without ` ok`).
|
||||
|
||||
| Kind | About | Examples |
|
||||
|---|---|---|
|
||||
| The case prints or leaves a memory address, which differs between the two machines | 40 | `HERE`, `PAD`, `1 BLOCK`, `BL WORD`, `CONTEXT @ .`, `FIND` |
|
||||
| v3 departs from FORTH-79 and v4 follows the standard | 35 | `PICK` and `ROLL` count from 1; `<# # #S #>` take a double number; `LEAVE` ends the loop at the next `LOOP`; `>R` `R>` only inside a definition |
|
||||
| The case assumes v3's byte address units, where a v4 address unit is a cell (D-1) | 8 | `42 , HERE 8 - @`, `PAD 42 OVER !` |
|
||||
| Not yet understood | 15 | vocabulary cases, `LIST`'s layout, `16 BASE ! 255 .`, `321 EMIT`, cases v3 ends in an error and v4 does not |
|
||||
Open: `PAD 42 OVER !` faults on v4, because `PAD` is a byte address and `!`
|
||||
takes a cell address (D-1). FORTH-79 expects it to work. The v3 case for it
|
||||
is left out until that is ruled.
|
||||
|
||||
Required words with no v3 case, and so no POST case: `U*` `U/MOD` `?`
|
||||
`EXECUTE` `KEY` `EXPECT` `QUERY` `U.` `CONVERT` `'` `."` `LITERAL` `STATE`
|
||||
`COMPILE` `[COMPILE]` `(` `QUIT` `BLK` `79-STANDARD`. `U*` and `U/MOD` are
|
||||
in neither v3 nor v4.
|
||||
|
||||
Twelve v3 cases are left out: three read the keyboard, two forget a word of
|
||||
the system, two store to the search order, and five leave v3 unable to
|
||||
continue in one session (`mkpost.py` prints the list).
|
||||
|
||||
The boot passes POST only on seeing the tally line with `fail=0`
|
||||
(`v4/system/boot.c`); that no line was refused is not enough.
|
||||
The Required Word Set list in `tools/mkpost.py` was written from memory of
|
||||
the standard and has not been checked against the document.
|
||||
|
||||
+12
-8
@@ -11,11 +11,11 @@
|
||||
#include "starkernel/capsule_blocks.h"
|
||||
|
||||
/* The capsules, in the order they are loaded. */
|
||||
/* POST is not yet part of the boot: v4:post79.4th does not pass yet
|
||||
* (v4/README.md). Build with -DV4_POST_AT_BOOT=1 to load it and to require
|
||||
* its clean tally; `make -C v4 post` does. */
|
||||
/* POST is part of the boot: v4:post79.4th is loaded after the vocabulary it
|
||||
* tests and must end with a clean tally. -DV4_POST_AT_BOOT=0 leaves it
|
||||
* out, for work on a vocabulary that does not pass yet. */
|
||||
#ifndef V4_POST_AT_BOOT
|
||||
#define V4_POST_AT_BOOT 0
|
||||
#define V4_POST_AT_BOOT 1
|
||||
#endif
|
||||
static const char *const boot_capsules[] = {
|
||||
"v4:forth79.4th",
|
||||
@@ -136,14 +136,18 @@ static int text_at(const char *line, unsigned len, unsigned at, const char *want
|
||||
static void post_watch(char c)
|
||||
{
|
||||
static const char clean[] = " fail=0";
|
||||
unsigned i;
|
||||
if (c != '\n') {
|
||||
if (post_len < sizeof post_line) post_line[post_len++] = c;
|
||||
return;
|
||||
}
|
||||
if (text_at(post_line, post_len, 0, "PARITY:V4_POST ")) {
|
||||
post_seen = 1;
|
||||
post_clean = post_len >= sizeof clean - 1 && text_at(post_line, post_len, post_len - (unsigned)(sizeof clean - 1), clean);
|
||||
}
|
||||
/* the node's prompt comes before it on the line, so look along the line */
|
||||
for (i = 0; i < post_len; i++)
|
||||
if (text_at(post_line, post_len, i, "PARITY:V4_POST ")) {
|
||||
post_seen = 1;
|
||||
post_clean = post_len >= sizeof clean - 1 && text_at(post_line, post_len, post_len - (unsigned)(sizeof clean - 1), clean);
|
||||
break;
|
||||
}
|
||||
post_len = 0;
|
||||
}
|
||||
|
||||
|
||||
+139
-23
@@ -36,6 +36,9 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import post79_rules as rules # noqa: E402
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
MODULE_DIR = os.path.join(ROOT, "v3", "src", "test_runner", "modules")
|
||||
OUTPUT = os.path.join(ROOT, "capsules", "v4", "post79.4th")
|
||||
@@ -83,15 +86,15 @@ HARNESS = r"""
|
||||
( edit. docs/v4.0.0/NUCLEUS.md section 6. )
|
||||
VARIABLE T# VARIABLE TPASS VARIABLE TFAIL VARIABLE TOPEN
|
||||
VARIABLE TBAD VARIABLE TWANT VARIABLE TLEN VARIABLE TSUM
|
||||
VARIABLE TDEP VARIABLE TBASE
|
||||
VARIABLE TDEP
|
||||
CREATE TBUF 64 ALLOT CREATE TSTK 16 ALLOT CREATE TNAME 8 ALLOT
|
||||
0 T# ! 0 TPASS ! 0 TFAIL ! 0 TOPEN ! 10 TBASE !
|
||||
0 T# ! 0 TPASS ! 0 TFAIL ! 0 TOPEN !
|
||||
: TB ( -- baddr ) TBUF 4 * ;
|
||||
: TN ( -- baddr ) TNAME 4 * ;
|
||||
( T-EMIT is given every character a case prints: the first 255 )
|
||||
( are kept, all are counted and summed. )
|
||||
: T-EMIT ( c -- )
|
||||
DUP TSUM @ 31 * + 16777215 AND TSUM !
|
||||
255 AND DUP TSUM @ 31 * + 16777215 AND TSUM !
|
||||
TLEN @ 255 < IF TB TLEN @ + C! ELSE DROP THEN 1 TLEN +! ;
|
||||
=====
|
||||
: T-CLEAR ( i*x -- ) BEGIN DEPTH WHILE DROP REPEAT ;
|
||||
@@ -107,34 +110,38 @@ CREATE TBUF 64 ALLOT CREATE TSTK 16 ALLOT CREATE TNAME 8 ALLOT
|
||||
TBAD @ IF 1 TFAIL +!
|
||||
." POST FAIL: " TN COUNT TYPE T-SHOW CR
|
||||
ELSE 1 TPASS +! THEN
|
||||
0 TOPEN ! 0 (CATCH) ! TBASE @ BASE !
|
||||
0 TOPEN ! 0 (CATCH) !
|
||||
THEN ;
|
||||
=====
|
||||
( T{ name begin a case: clean stack, errors caught )
|
||||
( T{ name begin a case: clean stack, decimal, FORTH )
|
||||
: T{ ( i*x -- ) T-CLOSE T-CLEAR
|
||||
DECIMAL [COMPILE] FORTH DEFINITIONS
|
||||
BL WORD COUNT 31 MIN DUP TN C! TN 1+ SWAP CMOVE
|
||||
1 T# +! 1 TOPEN ! 0 TBAD ! 0 TWANT ! 0 TLEN ! 0 TSUM !
|
||||
0 TDEP ! 1 (CATCH) ! ;
|
||||
( T| ... a line of the case, what it prints captured )
|
||||
: T| ' T-EMIT (EMIT-HOOK) ! ; IMMEDIATE
|
||||
: T-END ( -- ) 0 (EMIT-HOOK) ! BASE @ TBASE ! DECIMAL ;
|
||||
: T-END ( -- ) 0 (EMIT-HOOK) ! DECIMAL ;
|
||||
( -> ... the case is over: set its stack aside, top first )
|
||||
: -> ( i*x -- ) T-END 0
|
||||
BEGIN DEPTH 1 > WHILE
|
||||
DUP 16 < IF SWAP OVER TSTK + ! ELSE SWAP DROP 1 TBAD ! THEN
|
||||
1+ REPEAT
|
||||
16 MIN TDEP ! ;
|
||||
=====
|
||||
( ->ERR the case is over, and must have ended in an error )
|
||||
: ->ERR ( i*x -- ) T-END -1 TWANT ! T-CLEAR ;
|
||||
=====
|
||||
( ... }T the stack the case must have left, in order )
|
||||
: }T ( i*x -- )
|
||||
DEPTH TDEP @ = 0= IF 1 TBAD ! T-CLEAR EXIT THEN
|
||||
0 BEGIN DEPTH 1 > WHILE
|
||||
SWAP OVER TSTK + @ = 0= IF 1 TBAD ! THEN 1+ REPEAT DROP ;
|
||||
( n }TD only how many values the case must have left )
|
||||
: }TD ( n -- ) TDEP @ = 0= IF 1 TBAD ! THEN ;
|
||||
( len sum T= what the case must have printed )
|
||||
: T= ( len sum -- ) TSUM @ = 0= IF 1 TBAD ! THEN
|
||||
TLEN @ = 0= IF 1 TBAD ! THEN ;
|
||||
=====
|
||||
( the tally; a failure is an error, which ends the boot )
|
||||
: T-REPORT ( -- ) T-CLOSE 0 (CATCH) ! DECIMAL
|
||||
." PARITY:V4_POST tests=" T# @ T. ." pass=" TPASS @ T.
|
||||
@@ -312,13 +319,13 @@ def run_v3(v3, cases):
|
||||
None if v3 did not get through it, else (error, stack, output)."""
|
||||
script = [": T| ; IMMEDIATE", ": TCLR BEGIN DEPTH WHILE DROP REPEAT ;"]
|
||||
for n, case in enumerate(cases):
|
||||
script.append('TCLR ." [[B%d]]"' % n)
|
||||
script.append('TCLR DECIMAL FORTH DEFINITIONS ." [[B%d]]"' % n)
|
||||
script.extend(case["lines"])
|
||||
script.append('." [[E%d]]" BASE @ DECIMAL .S BASE ! ." [[S%d]]"' % (n, n))
|
||||
script.append("BYE")
|
||||
done = subprocess.run([v3, "--log-none"], input="\n".join(script) + "\n", capture_output=True,
|
||||
text=True, errors="replace", timeout=600)
|
||||
text = ANSI.sub("", done.stdout)
|
||||
done = subprocess.run([v3, "--log-none"], input=("\n".join(script) + "\n").encode("latin-1"),
|
||||
capture_output=True, timeout=600)
|
||||
text = ANSI.sub("", done.stdout.decode("latin-1")) # a byte is a character
|
||||
results = []
|
||||
for n, case in enumerate(cases):
|
||||
begin, mid, end = "[[B%d]] ok\nok> " % n, "ok> [[E%d]]" % n, "[[S%d]]" % n
|
||||
@@ -362,6 +369,7 @@ def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--v3", default=os.path.join(ROOT, "lfs", "amd64", "starforth"))
|
||||
ap.add_argument("--report", default=None)
|
||||
ap.add_argument("--dump", default=None, help="write what v3 did with every case")
|
||||
args = ap.parse_args()
|
||||
|
||||
required, left_out, cases, seen = set(REQUIRED), [], [], {}
|
||||
@@ -410,21 +418,60 @@ def main():
|
||||
current.extend(part.strip("\n").split("\n"))
|
||||
flush()
|
||||
|
||||
disagreements = []
|
||||
disagreements, applied = [], {"address": 0, "standard": 0, "extra": 0}
|
||||
|
||||
def expectation(error, stack, output, mode="full"):
|
||||
"""The lines that end a case."""
|
||||
if error:
|
||||
return ["->ERR"]
|
||||
if mode == "depth":
|
||||
return ["-> %d }TD" % len(stack)]
|
||||
if mode == "depth+output":
|
||||
return pack("", ["->", str(len(stack)), "}TD", str(len(output)), str(checksum(output)), "T="])
|
||||
return pack("", ["->"] + [str(v) for v in stack] + ["}T", str(len(output)), str(checksum(output)), "T="])
|
||||
|
||||
def emit(label, lines, ending):
|
||||
block = ["T{ " + label] + lines + ending
|
||||
if len(current) + len(block) > 16:
|
||||
flush()
|
||||
current.extend(block)
|
||||
|
||||
dump = []
|
||||
for case, (error, stack, output) in zip(cases, results):
|
||||
label = case["label"]
|
||||
dump.append("%s\n input: %s\n v3: %s stack=%s out=%r\n"
|
||||
% (label, " ".join(case["text"].split()), "ERROR" if error else "ok", stack, output))
|
||||
if error != case["should_error"]:
|
||||
disagreements.append((case["label"], "v3's table says %s, v3 %s"
|
||||
disagreements.append((label, "v3's table says %s, v3 %s"
|
||||
% ("error" if case["should_error"] else "no error",
|
||||
"raised one" if error else "raised none")))
|
||||
lines = ["T{ " + case["label"]] + case["lines"]
|
||||
if error:
|
||||
lines.append("->ERR")
|
||||
if label in rules.STANDARD:
|
||||
rule = rules.STANDARD[label]
|
||||
if rule is None: # the case is dropped
|
||||
applied["standard"] += 1
|
||||
left_out.append((label, rules.DROPPED[label]))
|
||||
continue
|
||||
lines = pack("T|", atoms(rule.get("text", case["text"])))
|
||||
assert lines, label
|
||||
if "depth" in rule:
|
||||
emit(label, lines, ["-> %d }TD" % rule["depth"]])
|
||||
else:
|
||||
emit(label, lines, expectation(rule.get("error", False), rule.get("stack", []), rule.get("out", "")))
|
||||
applied["standard"] += 1
|
||||
elif label in rules.ADDRESS:
|
||||
emit(label, case["lines"], expectation(error, stack, output, rules.ADDRESS[label]))
|
||||
applied["address"] += 1
|
||||
else:
|
||||
tail = ["->"] + [str(v) for v in stack] + ["}T", str(len(output)), str(checksum(output)), "T="]
|
||||
lines.extend(pack("", tail))
|
||||
if len(current) + len(lines) > 16:
|
||||
flush()
|
||||
current.extend(lines)
|
||||
emit(label, case["lines"], expectation(error, stack, output))
|
||||
for rule in rules.EXTRA:
|
||||
lines = pack("T|", atoms(rule["text"]))
|
||||
assert lines, rule
|
||||
emit(rule["label"], lines, expectation(rule.get("error", False), rule.get("stack", []), rule.get("out", "")))
|
||||
applied["extra"] += 1
|
||||
unknown = [k for k in list(rules.STANDARD) + list(rules.ADDRESS) if k not in {c["label"] for c in cases}]
|
||||
assert not unknown, "rules for cases that do not exist: %s" % unknown
|
||||
if args.dump:
|
||||
open(args.dump, "w").write("\n".join(dump))
|
||||
flush()
|
||||
blocks.append(["T-REPORT", "FORGET T# DECIMAL"])
|
||||
|
||||
@@ -436,9 +483,15 @@ def main():
|
||||
assert len(line) <= LINE_MAX, line
|
||||
out.write(line + "\n")
|
||||
|
||||
tested = sorted({c["word"] for c in cases})
|
||||
tested = sorted({c["word"] for c in cases} | {r["label"].rsplit(".", 1)[0] for r in rules.EXTRA})
|
||||
untested = [w for w in REQUIRED if w not in set(tested)]
|
||||
report = ["POST cases: %d, in %d blocks (%d to %d)" % (len(cases), len(blocks), FIRST_BLOCK, FIRST_BLOCK + len(blocks) - 1),
|
||||
dropped = sum(1 for v in rules.STANDARD.values() if v is None)
|
||||
total = len(cases) - dropped + len(rules.EXTRA)
|
||||
report = ["POST cases: %d, in %d blocks (%d to %d)" % (total, len(blocks), FIRST_BLOCK, FIRST_BLOCK + len(blocks) - 1),
|
||||
" from v3 unchanged: %d" % (len(cases) - applied["address"] - applied["standard"]),
|
||||
" from v3, address-dependent (post79_rules.ADDRESS): %d" % applied["address"],
|
||||
" rewritten or dropped for FORTH-79 (post79_rules.STANDARD): %d, of which dropped %d" % (applied["standard"], dropped),
|
||||
" written by hand (post79_rules.EXTRA): %d" % applied["extra"],
|
||||
"Required words with at least one case: %d of %d" % (len(tested), len(REQUIRED)),
|
||||
"Required words with no case: " + " ".join(untested), "",
|
||||
"Left out (%d):" % len(left_out)]
|
||||
@@ -446,10 +499,73 @@ def main():
|
||||
report += ["", "v3 did not do what its own table expects (%d):" % len(disagreements)]
|
||||
report += [" %-32s %s" % item for item in disagreements]
|
||||
text = "\n".join(report) + "\n"
|
||||
write_doc(cases, results, left_out, disagreements, untested, total, applied, dropped)
|
||||
if args.report:
|
||||
open(args.report, "w").write(text)
|
||||
sys.stdout.write(text)
|
||||
|
||||
|
||||
DOC = os.path.join(ROOT, "docs", "v4.0.0", "POST79.md")
|
||||
|
||||
|
||||
def code(text):
|
||||
return "`" + " ".join(text.split()).replace("|", "\\|") + "`"
|
||||
|
||||
|
||||
def expects(rule):
|
||||
if rule.get("error"):
|
||||
return "an error"
|
||||
if "depth" in rule:
|
||||
return "%d value(s) left; output not compared" % rule["depth"]
|
||||
return "stack `%s`, prints %s" % (" ".join(str(v) for v in rule.get("stack", [])) or "empty",
|
||||
"`%s`" % rule["out"].replace("\n", "\\n") if rule.get("out") else "nothing")
|
||||
|
||||
|
||||
def write_doc(cases, results, left_out, disagreements, untested, total, applied, dropped):
|
||||
by_label = {c["label"]: (c, r) for c, r in zip(cases, results)}
|
||||
out = ["# 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 %d cases. %d are v3's, with what the hosted v3 binary did as the" % (total, len(cases) - applied["address"] - applied["standard"]),
|
||||
"expected result. This file lists every other case, and why.", "",
|
||||
"## 1. Rewritten for FORTH-79 (%d)" % (applied["standard"] - dropped), "",
|
||||
"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 |", "|---|---|---|---|---|---|"]
|
||||
for label, rule in rules.STANDARD.items():
|
||||
if rule is None:
|
||||
continue
|
||||
case, (error, stack, output) = by_label[label]
|
||||
did = "error" if error else "stack `%s`, printed `%s`" % (" ".join(map(str, stack)) or "empty", output.replace("\n", "\\n")[:40])
|
||||
ran = code(rule["text"]) if "text" in rule else "unchanged"
|
||||
out.append("| `%s` | %s | %s | %s | %s | %s |" % (label, code(case["text"]), did, ran, expects(rule), rule["why"]))
|
||||
out += ["", "## 2. Address-dependent (%d)" % applied["address"], "",
|
||||
"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 |", "|---|---|---|"]
|
||||
for label, mode in rules.ADDRESS.items():
|
||||
out.append("| `%s` | %s | %s |" % (label, code(by_label[label][0]["text"]),
|
||||
"values left, and output" if mode == "depth+output" else "values left only"))
|
||||
out += ["", "## 3. Written by hand (%d)" % len(rules.EXTRA), "",
|
||||
"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 |", "|---|---|---|"]
|
||||
for rule in rules.EXTRA:
|
||||
out.append("| `%s` | %s | %s |" % (rule["label"], code(rule["text"]), expects(rule)))
|
||||
out += ["", "## 4. v3 cases left out (%d)" % len(left_out), "", "| Case | Why |", "|---|---|"]
|
||||
out += ["| `%s` | %s |" % item for item in left_out]
|
||||
out += ["", "## 5. Required words POST does not test", "",
|
||||
" ".join("`%s`" % w for w in untested) + ".", "",
|
||||
"`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 (%d)" % len(disagreements), "",
|
||||
"POST expects what v3 did, unless section 1 says otherwise.", "", "| Case | |", "|---|---|"]
|
||||
out += ["| `%s` | %s |" % item for item in disagreements]
|
||||
open(DOC, "w").write("\n".join(out) + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
"""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
|
||||
|
||||
"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),
|
||||
}
|
||||
|
||||
# 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)",
|
||||
"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"),
|
||||
]
|
||||
Reference in New Issue
Block a user