feat(v4.0.0): POST's cases as a table the kernel holds

mkpost.py writes v4/system/post_cases.c where it wrote the capsule: for
each case its name, its lines, and what it must do -- end in an error, or
leave v3's stack and print v3's output, which is now held in full where
the capsule held its length and a checksum.

The same 538 cases: checked against capsules/v4/post79.4th case by case --
names, lines, stacks, and the length and checksum of each output -- with
no difference.  The capsule is still what the boot runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-07 10:34:03 -04:00
co-authored by Claude Opus 5.5
parent b04ae55ec5
commit c2f4b9808a
4 changed files with 1190 additions and 126 deletions
+5 -6
View File
@@ -1,7 +1,7 @@
# POST for the FORTH-79 Required Word Set: where it departs from v3 # 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. 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`. Design: `NUCLEUS.md` section 6. The cases are `v4/system/post_cases.c`.
POST runs 538 cases. 431 are v3's, with what the hosted v3 binary did as the POST runs 538 cases. 431 are v3's, with what the hosted v3 binary did as the
expected result. This file lists every other case, and why. expected result. This file lists every other case, and why.
@@ -60,11 +60,11 @@ standard's and not v3's.
| `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 | | `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.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 | | `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 `1009040112 \n` | unchanged | stack `empty`, prints `9 \n` | FORTH-79: >IN is the offset into the input, 9 here; v3 prints an address | | `>IN.initial` | `>IN @ . CR` | stack `empty`, printed `860486384 \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 | | `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 | | `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 `1009039472 \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.existing` | `FIND DUP . CR` | stack `empty`, printed `860485744 \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 `1009366512 \n` | unchanged | 0 value(s) left; output not compared | the address FIND leaves is printed | | `FIND.user_word` | `: test5 44 ; FIND test5 . CR` | stack `empty`, printed `860812560 \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 | | `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.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 | | `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 |
@@ -188,8 +188,7 @@ standard's; no v3 run stands behind them.
`KEY` `EXPECT` `QUERY` `QUIT`. `KEY` `EXPECT` `QUERY` `QUIT`.
`KEY`, `EXPECT` and `QUERY` wait for the keyboard, which a boot cannot type `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 at. `QUIT` returns to the terminal without `ok`.
takes for a refused line.
## 6. Where v3 did not do what its own table expects (6) ## 6. Where v3 did not do what its own table expects (6)
+32
View File
@@ -0,0 +1,32 @@
/* post.h -- POST, which is the kernel's. docs/v4.0.0/NUCLEUS.md section 6.
*
* As in v3, the kernel holds the cases and runs them on the node: nothing
* of POST is in the node's dictionary. A case is a name, its lines of
* FORTH, and what it must do. The table is generated from v3's own cases
* (v4/tools/mkpost.py -> v4/system/post_cases.c).
*/
#ifndef V4_POST_H
#define V4_POST_H
#include "v4/node.h"
#define V4_POST_STACK 0 /* its data stack and what it prints are compared */
#define V4_POST_DEPTH 1 /* only how many values it leaves; what it prints is not compared */
#define V4_POST_DEPTH_OUTPUT 2 /* how many values it leaves, and what it prints */
#define V4_POST_ERROR 3 /* it must end in an error; nothing else is compared */
typedef struct {
const char *name;
const char *const *lines; /* line_count of them, sent one at a time */
unsigned line_count;
int expect; /* V4_POST_* */
const long long *stack; /* V4_POST_STACK: depth values, the deepest first; else 0 */
unsigned depth;
const char *output; /* output_len characters */
unsigned output_len;
} v4_post_case;
extern const v4_post_case v4_post_cases[];
extern const unsigned v4_post_case_count;
#endif /* V4_POST_H */
File diff suppressed because it is too large Load Diff
+54 -120
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""mkpost.py -- write capsules/v4/post79.4th from v3's POST cases. """mkpost.py -- write v4/system/post_cases.c from v3's POST cases.
v4/tools/mkpost.py [--v3 PATH] [--report FILE] v4/tools/mkpost.py [--v3 PATH] [--report FILE]
@@ -7,7 +7,6 @@ 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. reviewed like any source. docs/v4.0.0/NUCLEUS.md section 6.
What it does: What it does:
1. Reads v3's case tables, v3/src/test_runner/modules/*.c, in the order 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 v3's own POST runs them. A case is a word, a name, a line of FORTH and
whether an error is expected. whether an error is expected.
@@ -15,20 +14,16 @@ What it does:
3. Runs every kept case, in order, in one session of the hosted v3 binary, 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 and records what v3 did: an error or not, the data stack, and what was
printed. v3 is the reference. printed. v3 is the reference.
4. Writes the harness and one case after another as a .4th capsule: 4. Writes the cases as a table in C, which the kernel holds and feeds to
the node (v4/system/post.c): for each its name, its lines, and what it
T{ DUP.basic name the case, start clean must do -- end in an error, or leave v3's stack and print v3's output.
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 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 keyboard; it forgets a word of the system or damages it on purpose; v3 could
not finish it in one not finish it in one session; or one of its lines cannot be cut to fit a
session; or one of its lines cannot be cut to fit a 64-character block 64-character line. (The cases were a capsule of 64-character block lines
line. until MESH.md step 6b; the cutting is kept so that they are the same cases
and the same lines.)
""" """
import argparse import argparse
import os import os
@@ -41,8 +36,7 @@ import post79_rules as rules # noqa: E402
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_DIR = os.path.join(ROOT, "v3", "src", "test_runner", "modules") MODULE_DIR = os.path.join(ROOT, "v3", "src", "test_runner", "modules")
OUTPUT = os.path.join(ROOT, "capsules", "v4", "post79.4th") OUTPUT = os.path.join(ROOT, "v4", "system", "post_cases.c")
FIRST_BLOCK = 7000
LINE_MAX = 64 LINE_MAX = 64
# v3's POST order (v3/src/test_runner/test_runner.c, test_modules[]), the # v3's POST order (v3/src/test_runner/test_runner.c, test_modules[]), the
@@ -79,77 +73,6 @@ CONTEXT CURRENT FORTH DEFINITIONS ' FIND FORGET
# Words whose cases wait for the keyboard, which a boot cannot type at. # Words whose cases wait for the keyboard, which a boot cannot type at.
KEYBOARD = {"KEY", "EXPECT", "QUERY"} 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
CREATE TBUF 64 ALLOT CREATE TSTK 16 ALLOT CREATE TNAME 8 ALLOT
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 -- )
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 ;
: 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) !
THEN ;
=====
( 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) ! 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.
." fail=" TFAIL @ T. CR
TFAIL @ IF -1 NODE-ERROR ! THEN ;
"""
# ---- reading v3's case tables ---------------------------------------------- # ---- reading v3's case tables ----------------------------------------------
TOKEN = re.compile(r''' TOKEN = re.compile(r'''
@@ -407,34 +330,24 @@ def main():
left_out.append((cases[first]["label"], "v3 did not get through it in one session")) left_out.append((cases[first]["label"], "v3 did not get through it in one session"))
del cases[first] del cases[first]
blocks, current = [], [] table = []
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, applied = [], {"address": 0, "standard": 0, "extra": 0} disagreements, applied = [], {"address": 0, "standard": 0, "extra": 0}
def expectation(error, stack, output, mode="full"): def expectation(error, stack, output, mode="full"):
"""The lines that end a case.""" """What a case must do."""
if error: if error:
return ["->ERR"] return {"expect": "V4_POST_ERROR", "stack": [], "depth": 0, "out": ""}
if mode == "depth": if mode == "depth":
return ["-> %d }TD" % len(stack)] return {"expect": "V4_POST_DEPTH", "stack": [], "depth": len(stack), "out": ""}
if mode == "depth+output": if mode == "depth+output":
return pack("", ["->", str(len(stack)), "}TD", str(len(output)), str(checksum(output)), "T="]) return {"expect": "V4_POST_DEPTH_OUTPUT", "stack": [], "depth": len(stack), "out": output}
return pack("", ["->"] + [str(v) for v in stack] + ["}T", str(len(output)), str(checksum(output)), "T="]) return {"expect": "V4_POST_STACK", "stack": list(stack), "depth": len(stack), "out": output}
def emit(label, lines, ending): def emit(label, lines, ending):
block = ["T{ " + label] + lines + ending # the lines were cut for a block, each begun "T| ": the same lines, without it
if len(current) + len(block) > 16: assert all(line.startswith("T| ") for line in lines), lines
flush() table.append((label, [line[3:] for line in lines], ending))
current.extend(block)
dump = [] dump = []
for case, (error, stack, output) in zip(cases, results): for case, (error, stack, output) in zip(cases, results):
@@ -454,7 +367,7 @@ def main():
lines = pack("T|", atoms(rule.get("text", case["text"]))) lines = pack("T|", atoms(rule.get("text", case["text"])))
assert lines, label assert lines, label
if "depth" in rule: if "depth" in rule:
emit(label, lines, ["-> %d }TD" % rule["depth"]]) emit(label, lines, {"expect": "V4_POST_DEPTH", "stack": [], "depth": rule["depth"], "out": ""})
else: else:
emit(label, lines, expectation(rule.get("error", False), rule.get("stack", []), rule.get("out", ""))) emit(label, lines, expectation(rule.get("error", False), rule.get("stack", []), rule.get("out", "")))
applied["standard"] += 1 applied["standard"] += 1
@@ -472,22 +385,14 @@ def main():
assert not unknown, "rules for cases that do not exist: %s" % unknown assert not unknown, "rules for cases that do not exist: %s" % unknown
if args.dump: if args.dump:
open(args.dump, "w").write("\n".join(dump)) open(args.dump, "w").write("\n".join(dump))
flush() write_table(table)
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} | {r["label"].rsplit(".", 1)[0] for r in rules.EXTRA}) 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)] untested = [w for w in REQUIRED if w not in set(tested)]
dropped = sum(1 for v in rules.STANDARD.values() if v is None) dropped = sum(1 for v in rules.STANDARD.values() if v is None)
total = len(cases) - dropped + len(rules.EXTRA) 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), assert total == len(table), (total, len(table))
report = ["POST cases: %d, in %s" % (total, os.path.relpath(OUTPUT, ROOT)),
" from v3 unchanged: %d" % (len(cases) - applied["address"] - applied["standard"]), " from v3 unchanged: %d" % (len(cases) - applied["address"] - applied["standard"]),
" from v3, address-dependent (post79_rules.ADDRESS): %d" % applied["address"], " 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), " rewritten or dropped for FORTH-79 (post79_rules.STANDARD): %d, of which dropped %d" % (applied["standard"], dropped),
@@ -505,6 +410,36 @@ def main():
sys.stdout.write(text) sys.stdout.write(text)
def c_string(text):
"""`text` as a C string literal: everything that is not plain is octal."""
out = []
for ch in text.encode("latin-1", errors="replace"):
if 32 <= ch < 127 and ch not in (34, 92, 63): # not " \ or ? (no trigraphs)
out.append(chr(ch))
else:
out.append("\\%03o" % ch)
return '"' + "".join(out) + '"'
def write_table(table):
with open(OUTPUT, "w", encoding="ascii") as out:
out.write("/* post_cases.c -- POST's cases for the FORTH-79 Required Word Set on v4.\n"
" * Generated by v4/tools/mkpost.py from v3's POST cases: do not edit.\n"
" * docs/v4.0.0/NUCLEUS.md section 6; docs/v4.0.0/POST79.md lists every case\n"
" * that is not v3's own with v3's result.\n */\n"
'#include "v4/post.h"\n\n')
for k, (label, lines, want) in enumerate(table):
out.write("static const char *const l%d[] = { %s };\n" % (k, ", ".join(c_string(line) for line in lines)))
if want["stack"]:
out.write("static const long long s%d[] = { %s };\n" % (k, ", ".join("%dLL" % v if v != -9223372036854775808 else "(-9223372036854775807LL - 1)" for v in want["stack"])))
out.write("\nconst v4_post_case v4_post_cases[] = {\n")
for k, (label, lines, want) in enumerate(table):
out.write(" { %s, l%d, %du, %s, %s, %du, %s, %du },\n"
% (c_string(label), k, len(lines), want["expect"], ("s%d" % k) if want["stack"] else "0", want["depth"],
c_string(want["out"]), len(want["out"].encode("latin-1", errors="replace"))))
out.write("};\n\nconst unsigned v4_post_case_count = %du;\n" % len(table))
DOC = os.path.join(ROOT, "docs", "v4.0.0", "POST79.md") DOC = os.path.join(ROOT, "docs", "v4.0.0", "POST79.md")
@@ -525,7 +460,7 @@ def write_doc(cases, results, left_out, disagreements, untested, total, applied,
by_label = {c["label"]: (c, r) for c, r in zip(cases, results)} 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", "", 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.", "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`.", "", "Design: `NUCLEUS.md` section 6. The cases are `v4/system/post_cases.c`.", "",
"POST runs %d cases. %d are v3's, with what the hosted v3 binary did as the" % (total, len(cases) - applied["address"] - applied["standard"]), "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.", "", "expected result. This file lists every other case, and why.", "",
"## 1. Rewritten for FORTH-79 (%d)" % (applied["standard"] - dropped), "", "## 1. Rewritten for FORTH-79 (%d)" % (applied["standard"] - dropped), "",
@@ -559,8 +494,7 @@ def write_doc(cases, results, left_out, disagreements, untested, total, applied,
out += ["", "## 5. Required words POST does not test", "", out += ["", "## 5. Required words POST does not test", "",
" ".join("`%s`" % w for w in untested) + ".", "", " ".join("`%s`" % w for w in untested) + ".", "",
"`KEY`, `EXPECT` and `QUERY` wait for the keyboard, which a boot cannot type", "`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", "at. `QUIT` returns to the terminal without `ok`.", "",
"takes for a refused line.", "",
"## 6. Where v3 did not do what its own table expects (%d)" % len(disagreements), "", "## 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 | |", "|---|---|"] "POST expects what v3 did, unless section 1 says otherwise.", "", "| Case | |", "|---|---|"]
out += ["| `%s` | %s |" % item for item in disagreements] out += ["| `%s` | %s |" % item for item in disagreements]