The test system had grown to seven scripts making five genuinely different kinds of claim, and nothing said which was which. A recorded transcript and a byte-for-byte comparison against a second implementation both print [ok ] and are worth wildly different amounts, so the fourth manual exists to say so: what each script can and cannot answer, why every determinism rule is there, how to add a test, and - the part written nowhere else - where the suite is blind. That last section is the reason for the document. Three buffer overruns into adjacent variables were all found by a person using the machine and none by the suite, the sanitizers cannot see them because emulated Data Memory is one legitimate host array, and there is no second opinion about the CPU at all. A document listing only strengths teaches the wrong lesson. The bullets describing each script move out of the README, so docs.sh now reads the manual for them, and five more numbers in it are settled from the source rather than trusted: the shape of the manifest, the xfail count, how many fixture disks makedisks.sh builds, how large the lint baseline is, and the tool count in either document. Each of the new checks was broken on purpose and watched to report before being kept, which is the discipline the manual itself argues for. Also drops the stale "70 instructions" from instructiontable.py's docstring. There are 72, and a number that carries no meaning is better removed than corrected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
63 lines
2.2 KiB
Python
Executable File
63 lines
2.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""The instruction table, as the assembler has it.
|
|
|
|
The monitor needs the same instructions the assembler does, with the same names and the
|
|
same lengths, and a disassembler that disagreed with the assembler about how long an
|
|
instruction is would not merely print one thing wrong - it would lose its place and print
|
|
everything after it wrong too. So the table is generated from assembly.c rather than typed
|
|
out again, and Tests/docs.sh checks the generated form against what is in the monitor.
|
|
|
|
Shapes are what follows the opcode:
|
|
0 nothing 1 an address 2 one byte 3 a Data Pointer selector
|
|
4 selector, byte 5 selector, address 6 two selectors
|
|
"""
|
|
import re
|
|
import sys
|
|
|
|
# Branches and the two calls that carry an address: the ten in 0x6X, plus RCAL and CALL.
|
|
ADDRESS = {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71}
|
|
ONE_BYTE = {0x72, 0x26, 0x27}
|
|
TWO_SELECTORS = {0x4A, 0x4B}
|
|
SELECTOR = {0x65, 0x33, 0x36, 0x40, 0x41, 0x42, 0x43, 0x44,
|
|
0x45, 0x46, 0x47, 0x48, 0x49, 0x4C, 0x4D,
|
|
0x4E, 0x4F, 0x50, 0x51}
|
|
|
|
|
|
def shapeOf(opcode):
|
|
if opcode in TWO_SELECTORS:
|
|
return 6
|
|
if opcode == 0x47: # SETD, a selector and then an address
|
|
return 5
|
|
if opcode in (0x48, 0x49): # DPUP and DPDN, a selector and then a byte
|
|
return 4
|
|
if opcode in SELECTOR:
|
|
return 3
|
|
if opcode in ADDRESS:
|
|
return 1
|
|
if opcode in ONE_BYTE or (opcode & 0xF0) in (0xD0, 0xE0):
|
|
return 2
|
|
return 0
|
|
|
|
|
|
def table(path="Source/Assembler/assembly.c"):
|
|
source = open(path).read()
|
|
found = re.findall(r'\{0x([0-9A-Fa-f]{2}),\s*"([A-Z0-9]+)"\}', source)
|
|
return [(int(code, 16), name) for code, name in found]
|
|
|
|
|
|
def asAssembly(entries):
|
|
lines = []
|
|
for opcode, name in entries:
|
|
padded = (name + " ")[:4]
|
|
lines.append(' 0x%02X 0d%d "%s"' % (opcode, shapeOf(opcode), padded))
|
|
return lines
|
|
|
|
|
|
if __name__ == "__main__":
|
|
entries = table()
|
|
if len(sys.argv) > 1 and sys.argv[1] == "--count":
|
|
print(len(entries))
|
|
else:
|
|
for line in asAssembly(entries):
|
|
print(line)
|