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
+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