Star forth v4.0.0 #3

Closed
admin wants to merge 136 commits from StarForth-v4.0.0 into master
11 changed files with 2779 additions and 14 deletions
Showing only changes of commit 5d6043e37e - Show all commits
+67 -2
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-10-05T19:24:40Z -->
<!-- Generated by mkcapsule --manifest 2026-10-05T20:03:22Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -30,6 +30,7 @@
| `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 |
| `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,10 +369,74 @@
| 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 |
## Conflicts
None.
---
*37 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
*38 capsule(s) scanned. Re-run `mkcapsule --manifest <dir>` to refresh.*
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,108 @@
[=3hBdsDxe: 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)
[=3hStarKernel 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:01:18
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 131
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=2102894063 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=1010732930, tick_hz=100, initial_count=10107329
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=0xa4b74bdc7deaf204 capsule_hash=0xa4b74bdc7deaf204 dict_hash=0x118de9d37e682ea4
PARITY:OK
ok>
@@ -0,0 +1,82 @@
UEFI firmware (version 2025.11-3ubuntu7.3 built at 15:40:26 on Sep 23 2026)
[=3hBdsDxe: 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)
[=3hStarKernel 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:02:10
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=0xa4b74bdc7deaf204 capsule_hash=0xa4b74bdc7deaf204 dict_hash=0x118de9d37e682ea4
PARITY:OK
ok>
@@ -0,0 +1,152 @@
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
[=3hRISC-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
[=3hStarKernel 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 = 0x00000000bde99936
riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a)
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0
Architecture: riscv64
Build: Oct 5 2026 16:03:23
UEFI BootServices: EXITED
=== StarKernel Boot Information ===
Memory map entries: 100
Total memory: 1020 MB
Usable memory: 977 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 250775
Free pages : 250123
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=0xa4b74bdc7deaf204 capsule_hash=0xa4b74bdc7deaf204 dict_hash=0x118de9d37e682ea4
PARITY:OK
ok>
+14
View File
@@ -129,6 +129,20 @@ $(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.
.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
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.
+46 -9
View File
@@ -55,21 +55,58 @@ 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: capsule loading works on all six builds. The three hosted
binaries and the three bare-metal kernels (`clean qemu` with `STARFORTH_V4=1`)
print the same four lines and reach `ok>`:
State, 2026-10-05, after the POST work began: all six builds load
`v4:forth79.4th` and reach `ok>` with the same lines:
```
PARITY:V4_NUCLEUS words=292 image_hash=0x60b74e4f87adb0ac
PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0xa4b74bdc7deaf204 capsule_hash=0xa4b74bdc7deaf204 dict_hash=0x0baed67626b4fac4
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:OK
ok>
```
Bare-metal logs: `logs/20261005-152955/amd64/`, `logs/20261005-154021/aarch64/`,
`logs/20261005-154150/riscv64/`. Each run was ended once the prompt was in the
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
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: all 292 words are still in the
nucleus. There is no POST yet.
`forth79.4th` holds no definitions yet: every word is still in the nucleus.
## 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.
**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:
```
PARITY:V4_POST tests=537 pass=439 fail=98
PARITY:FAIL
POST: FAILED
```
The 98 are not yet sorted into v4 defects and differences that need a
ruling. A first reading finds four kinds:
| 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 |
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.
+5
View File
@@ -68,6 +68,11 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro
\ FORTH-79: clear the return stack, set execution mode, return control to
\ the terminal; no message is given. The data stack is left as it is.
\ NODE-ERROR, by name: how a definition written in FORTH raises an error.
\ A store of a code that is not zero is the trap of D-18 -- the word goes no
\ further, the message for the code is printed (-1: the word has printed
\ its own) and the line ends with ERROR. The codes are listed in core.v4.
header NODE-ERROR inline : NODE-ERROR' NODE-ERROR ;
header (CATCH) inline : (CATCH)' (CATCH) ;
header (EMIT-HOOK) inline : (EMIT-HOOK)' (EMIT-HOOK) ;
+4 -1
View File
@@ -6,8 +6,11 @@
* the text goes. So the two start the same way and print the same lines:
*
* PARITY:V4_NUCLEUS words=N image_hash=0x...
* PARITY:V4_CAPSULE name=... capsule_id=0x... capsule_hash=0x... dict_hash=0x...
* PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x...
* PARITY:V4_POST tests=N pass=N fail=N (printed by the POST capsule)
* PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0x... capsule_hash=0x... dict_hash=0x...
* PARITY:OK
* POST: PASSED
* ok>
*
* or, if anything is wrong, what was wrong and then
+59 -2
View File
@@ -11,7 +11,18 @@
#include "starkernel/capsule_blocks.h"
/* The capsules, in the order they are loaded. */
static const char *const boot_capsules[] = { "v4:forth79.4th" };
/* 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. */
#ifndef V4_POST_AT_BOOT
#define V4_POST_AT_BOOT 0
#endif
static const char *const boot_capsules[] = {
"v4:forth79.4th",
#if V4_POST_AT_BOOT
"v4:post79.4th",
#endif
};
#define LINE_MAX 80u /* QUERY takes 80 characters, the new-line among them */
#define STEP_LIMIT 4000000000ULL /* instruction words one line may take */
@@ -105,6 +116,37 @@ typedef struct {
unsigned len;
} tail;
/* POST's verdict. The POST capsule ends by printing one line,
* PARITY:V4_POST tests=N pass=N fail=N
* and the boot passes only if it has seen that line with fail=0. That no
* line was refused is not enough: a POST that was broken half way would
* refuse nothing. */
static char post_line[96];
static unsigned post_len;
static int post_seen, post_clean;
static int text_at(const char *line, unsigned len, unsigned at, const char *want)
{
unsigned i;
for (i = 0; want[i]; i++)
if (at + i >= len || line[at + i] != want[i]) return 0;
return 1;
}
static void post_watch(char c)
{
static const char clean[] = " fail=0";
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);
}
post_len = 0;
}
static void tail_put(const v4_boot *b, tail *t, int show, char c)
{
unsigned i;
@@ -130,7 +172,10 @@ static int run_to_prompt(const v4_boot *b, int show)
for (;;) {
(void)v4_exec_step_word(n, b->es, b->h);
if (n->console_len) {
for (i = 0; i < n->console_len; i++) tail_put(b, &t, show, (char)n->console[i]);
for (i = 0; i < n->console_len; i++) {
post_watch((char)n->console[i]);
tail_put(b, &t, show, (char)n->console[i]);
}
n->console_len = 0;
}
if (n->stopped) { say(b, "\nV4: the node stopped on a fault\n"); return 0; }
@@ -233,6 +278,7 @@ int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
{
unsigned i;
post_len = 0; post_seen = 0; post_clean = 0;
if (!v4_image_boot(b->n, b->es, b->h, b->im, disk, blocks)) {
say(b, "V4: the nucleus image is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
return 0;
@@ -247,7 +293,18 @@ int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
for (i = 0; i < sizeof boot_capsules / sizeof boot_capsules[0]; i++)
if (!load_capsule(b, boot_capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
#if V4_POST_AT_BOOT
if (!post_seen || !post_clean) {
say(b, post_seen ? "V4: POST reported failures\n" : "V4: POST did not report\n");
say(b, "PARITY:FAIL\nPOST: FAILED\n");
return 0;
}
#endif
#if V4_POST_AT_BOOT
say(b, "PARITY:OK\nPOST: PASSED\nok> ");
#else
say(b, "PARITY:OK\nok> ");
#endif
return 1;
}
+455
View File
@@ -0,0 +1,455 @@
#!/usr/bin/env python3
"""mkpost.py -- write capsules/v4/post79.4th from v3's POST cases.
v4/tools/mkpost.py [--v3 PATH] [--report FILE]
A development tool, run when the cases change; its output is committed and
reviewed like any source. docs/v4.0.0/NUCLEUS.md section 6.
What it does:
1. Reads v3's case tables, v3/src/test_runner/modules/*.c, in the order
v3's own POST runs them. A case is a word, a name, a line of FORTH and
whether an error is expected.
2. Keeps the cases for words of the FORTH-79 Required Word Set.
3. Runs every kept case, in order, in one session of the hosted v3 binary,
and records what v3 did: an error or not, the data stack, and what was
printed. v3 is the reference.
4. Writes the harness and one case after another as a .4th capsule:
T{ DUP.basic name the case, start clean
T| 5 DUP . . a line of the case, its output captured
-> 5 5 }T 0 0 T= v3's stack; v3's output: length, checksum
or, for a case v3 ended with an error, ->ERR in place of the last
line.
A case is left out, and listed in the report, when: its word reads the
keyboard; it forgets a word of the system or damages it on purpose; v3 could
not finish it in one
session; or one of its lines cannot be cut to fit a 64-character block
line.
"""
import argparse
import os
import re
import subprocess
import sys
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")
FIRST_BLOCK = 7000
LINE_MAX = 64
# v3's POST order (v3/src/test_runner/test_runner.c, test_modules[]), the
# modules that test FORTH-79 words.
MODULES = [
"stack_words_test.c", "return_stack_words_test.c", "memory_words_test.c",
"arithmetic_words_test.c", "logical_words_test.c",
"mixed_arithmetic_words_test.c", "double_words_test.c",
"format_words_test.c", "string_words_test.c", "io_words_test.c",
"block_words_test.c", "dictionary_words_test.c",
"dictionary_manipulation_words_test.c", "vocabulary_words_test.c",
"system_words_test.c", "defining_words_tests.c", "control_words_test.c",
]
# The FORTH-79 Standard's Required Word Set, by the standard's own groups.
# Written from the standard's word list; check it against the document
# before relying on it for a claim of compliance.
REQUIRED = """
DUP DROP SWAP OVER ROT PICK ROLL ?DUP DEPTH >R R> R@
< = > 0< 0= 0> D< U< NOT
+ - * / MOD /MOD */ */MOD 1+ 1- 2+ 2- MAX MIN ABS NEGATE D+ DNEGATE
U* U/MOD AND OR XOR
@ ! C@ C! ? +! MOVE CMOVE FILL
DO LOOP +LOOP I J LEAVE IF ELSE THEN BEGIN UNTIL WHILE REPEAT EXIT EXECUTE
CR EMIT SPACE SPACES TYPE COUNT -TRAILING KEY EXPECT QUERY WORD
BASE DECIMAL . U. CONVERT <# # #S HOLD SIGN #>
LIST LOAD SCR BLOCK UPDATE BUFFER SAVE-BUFFERS EMPTY-BUFFERS
: ; VARIABLE CONSTANT VOCABULARY CREATE DOES>
CONTEXT CURRENT FORTH DEFINITIONS ' FIND FORGET
, ALLOT ." IMMEDIATE LITERAL STATE [ ] COMPILE [COMPILE]
( >IN ABORT QUIT HERE PAD BLK 79-STANDARD
""".split()
# Words whose cases wait for the keyboard, which a boot cannot type at.
KEYBOARD = {"KEY", "EXPECT", "QUERY"}
# ---- the harness: blocks FIRST_BLOCK up, FORTH-79 and the two nucleus hooks ----
HARNESS = r"""
( post79.4th -- POST for the FORTH-79 Required Word Set on v4. )
( Generated by v4/tools/mkpost.py from v3's POST cases: do not )
( 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
CREATE TBUF 64 ALLOT CREATE TSTK 16 ALLOT CREATE TNAME 8 ALLOT
0 T# ! 0 TPASS ! 0 TFAIL ! 0 TOPEN ! 10 TBASE !
: 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 !
TLEN @ 255 < IF TB TLEN @ + C! ELSE DROP THEN 1 TLEN +! ;
=====
: T-CLEAR ( i*x -- ) BEGIN DEPTH WHILE DROP REPEAT ;
: T. ( n -- ) 0 <# #S #> TYPE ;
( what a failing case printed, and the stack it left )
: T-SHOW ( -- )
." out<" TB TLEN @ 255 MIN TYPE ." > stack<"
TDEP @ BEGIN DUP WHILE 1- DUP TSTK + @ . REPEAT DROP ." >" ;
( the verdict on the case that is open, if one is )
: T-CLOSE ( -- )
TOPEN @ IF
(CATCH) @ -1 = TWANT @ = 0= IF 1 TBAD ! THEN
TBAD @ IF 1 TFAIL +!
." POST FAIL: " TN COUNT TYPE T-SHOW CR
ELSE 1 TPASS +! THEN
0 TOPEN ! 0 (CATCH) ! TBASE @ BASE !
THEN ;
=====
( T{ name begin a case: clean stack, errors caught )
: T{ ( i*x -- ) T-CLOSE T-CLEAR
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 ;
( -> ... 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 ;
( 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.
." fail=" TFAIL @ T. CR
TFAIL @ IF -1 NODE-ERROR ! THEN ;
"""
# ---- reading v3's case tables ----------------------------------------------
TOKEN = re.compile(r'''
\s+ | /\*.*?\*/ | //[^\n]* |
(?P<str>"(?:\\.|[^"\\])*") |
(?P<punct>[{}(),;=\[\]&*]) |
(?P<word>[A-Za-z0-9_.+\-]+)
''', re.S | re.X)
ESCAPES = {"n": "\n", "t": "\t", "\\": "\\", '"': '"', "r": "\r", "0": "\0"}
def unescape(lit):
out, i, body = [], 0, lit[1:-1]
while i < len(body):
if body[i] == "\\" and i + 1 < len(body):
out.append(ESCAPES.get(body[i + 1], body[i + 1]))
i += 2
else:
out.append(body[i])
i += 1
return "".join(out)
def tokens(text):
pos, out = 0, []
while pos < len(text):
m = TOKEN.match(text, pos)
if not m:
pos += 1
continue
pos = m.end()
if m.group("str"):
value = unescape(m.group("str"))
if out and out[-1][0] == "str": # adjacent literals join
out[-1] = ("str", out[-1][1] + value)
else:
out.append(("str", value))
elif m.group("punct"):
out.append(("punct", m.group("punct")))
elif m.group("word"):
out.append(("word", m.group("word")))
return out
def parse_braces(toks, i):
"""toks[i] is '{'; returns (nested list, index after the matching '}')."""
items = []
i += 1
while toks[i] != ("punct", "}"):
if toks[i] == ("punct", "{"):
sub, i = parse_braces(toks, i)
items.append(sub)
elif toks[i] == ("punct", ","):
i += 1
else:
items.append(toks[i][1] if toks[i][0] != "str" else ("s", toks[i][1]))
i += 1
return items, i + 1
def read_module(path):
"""Yields (word, case name, input, should_error) for every implemented case."""
toks = tokens(open(path, encoding="utf-8", errors="replace").read())
for i, t in enumerate(toks):
if t != ("word", "WordTestSuite"):
continue
j = i
while j < len(toks) and toks[j] != ("punct", "=") and toks[j] != ("punct", ";") and toks[j] != ("punct", "("):
j += 1
if j >= len(toks) or toks[j] != ("punct", "="):
continue
suites, _ = parse_braces(toks, j + 1)
for suite in suites:
if not isinstance(suite, list) or not suite or not isinstance(suite[0], tuple):
continue
word = suite[0][1]
for case in suite[1]:
if not isinstance(case, list) or len(case) < 6 or not isinstance(case[0], tuple):
continue
name, text = case[0][1], case[1][1]
should_error, implemented = case[4] != "0", case[5] != "0"
if implemented:
yield word, name, text, should_error
# ---- cutting a case's line to block lines ------------------------------------
STRING_OPENERS = {'."', 'S"', 'ABORT"', '.('}
def atoms(text):
"""The line as pieces that must each stay on one line: a word, or a word
that takes text together with its text. None if the line has a new-line
inside such a text."""
out, i, n = [], 0, len(text)
while i < n:
if text[i].isspace():
i += 1
continue
j = i
while j < n and not text[j].isspace():
j += 1
word = text[i:j]
upper = word.upper()
if upper in STRING_OPENERS or upper == "(":
close = ")" if upper in ("(", ".(") else '"'
k = text.find(close, j + 1 if j < n else j)
if k < 0:
k = n - 1
piece = text[i:k + 1]
if "\n" in piece:
return None
out.append(piece)
i = k + 1
elif word == "\\":
break # the rest of the line is a comment
else:
out.append(word)
i = j
return out
def pack(prefix, pieces, width=LINE_MAX):
"""Pieces into lines no longer than `width`, each begun with `prefix`
(which may be empty). None if a piece cannot fit on a line."""
head = [prefix] if prefix else []
lines, current = [], []
for piece in pieces:
if len(" ".join(head + [piece])) > width:
return None
if current and len(" ".join(head + current + [piece])) > width:
lines.append(current)
current = []
current.append(piece)
if current:
lines.append(current)
return [" ".join(head + line) for line in lines]
# A store to the search order, which v3's C test runner survives and a
# running FORTH system does not.
DAMAGES = re.compile(r"\b(CONTEXT|CURRENT)\s+!")
def forgets_system_word(text):
"""True if the line FORGETs a word it did not itself define. In a plain
v3 session that takes the rest of the system with it, and v4 refuses."""
words = text.split()
for i, word in enumerate(words[:-1]):
if word.upper() == "FORGET":
target = words[i + 1]
made = any(words[k] == target and k > 0 and words[k - 1].upper() in
(":", "CREATE", "VARIABLE", "CONSTANT", "VOCABULARY") for k in range(i))
if not made:
return True
return False
# ---- what v3 does --------------------------------------------------------------
ANSI = re.compile(r"\x1b\[[0-9;]*m")
def run_v3(v3, cases):
"""Runs the cases in one session. Returns a list, one entry per case:
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.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)
results = []
for n, case in enumerate(cases):
begin, mid, end = "[[B%d]] ok\nok> " % n, "ok> [[E%d]]" % n, "[[S%d]]" % n
b = text.find(begin)
e = text.find(mid, b) if b >= 0 else -1
s = text.find(end, e) if e >= 0 else -1
if b < 0 or e < 0 or s < 0:
results.append(None)
continue
body = text[b + len(begin):e]
# one "ok> " and one " ok" or " ERROR" for every line of the case
chunks = body.split("ok> ")
output, error, ok = "", False, True
for chunk in chunks:
if chunk.endswith(" ERROR\n"):
output += chunk[:-len(" ERROR\n")]
error = True
elif chunk.endswith(" ok\n"):
output += chunk[:-len(" ok\n")]
else:
ok = False
m = re.match(r"<(\d+)>((?: -?\d+)*) \n", text[e + len(mid):s])
if not ok or not m:
results.append(None)
continue
stack = [int(v) for v in m.group(2).split()]
results.append((error, stack[:-1], output)) # the last value is BASE
return results
def checksum(output):
total = 0
for ch in output.encode("latin-1", errors="replace"):
total = (total * 31 + ch) & 0xFFFFFF
return total
# ---- writing the capsule -------------------------------------------------------
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--v3", default=os.path.join(ROOT, "lfs", "amd64", "starforth"))
ap.add_argument("--report", default=None)
args = ap.parse_args()
required, left_out, cases, seen = set(REQUIRED), [], [], {}
for module in MODULES:
for word, name, text, should_error in read_module(os.path.join(MODULE_DIR, module)):
if word not in required:
continue
label = ("%s.%s" % (word, name))[:31]
seen[label] = seen.get(label, 0) + 1
if seen[label] > 1:
label = (label[:28] + "~%d" % seen[label])[:31]
if word in KEYBOARD:
left_out.append((label, "reads the keyboard"))
continue
if forgets_system_word(text):
left_out.append((label, "forgets a word of the system"))
continue
if DAMAGES.search(text.upper()):
left_out.append((label, "damages the system on purpose"))
continue
pieces = atoms(text)
lines = pack("T|", pieces) if pieces is not None else None
if not lines:
left_out.append((label, "a line cannot be cut to fit a block line"))
continue
cases.append({"label": label, "word": word, "text": text, "lines": lines, "should_error": should_error})
# v3 is run until every case left is one it gets through
while True:
results = run_v3(args.v3, cases)
bad = [i for i, r in enumerate(results) if r is None]
if not bad:
break
first = bad[0]
left_out.append((cases[first]["label"], "v3 did not get through it in one session"))
del cases[first]
blocks, current = [], []
def flush():
if current:
blocks.append(list(current))
del current[:]
for part in HARNESS.strip("\n").split("=====\n"):
current.extend(part.strip("\n").split("\n"))
flush()
disagreements = []
for case, (error, stack, output) in zip(cases, results):
if error != case["should_error"]:
disagreements.append((case["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")
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)
flush()
blocks.append(["T-REPORT", "FORGET T# DECIMAL"])
with open(OUTPUT, "w", encoding="ascii") as out:
for k, block in enumerate(blocks):
assert len(block) <= 16, block
out.write("Block %d\n" % (FIRST_BLOCK + k))
for line in block:
assert len(line) <= LINE_MAX, line
out.write(line + "\n")
tested = sorted({c["word"] for c in cases})
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),
"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)]
report += [" %-32s %s" % item for item in left_out]
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"
if args.report:
open(args.report, "w").write(text)
sys.stdout.write(text)
if __name__ == "__main__":
main()