A symbol table says which memory, and where the name was written
The dump was an address and a name. Both of the questions it gets asked were only half answered. "What is at this address" was ambiguous, because Program and Data are separate memories and an address alone does not say which one. That is easy to miss in a loadable program, where the segments are usually based far apart - and immediate in a boot image, where both start at zero: replCalculator has a Program 0003 and a Data 0003 and the old file printed both as "0003 <name>". "Where is this defined" was not answered at all, and it is the one that matters more as a program grows. A name defined once and called in forty places is hard to find by searching. Lander's table names five files besides its own; CosmOS and its libraries define over a thousand names across a dozen. So: memory, address, name, file, line, separated by tabs, sorted by memory and then address with Program first. Tabs because that makes it a table cut, awk and sort already read, and no heading line because nothing should have to know to skip one. Everything needed was already being passed to addLabel and thrown away; the file name points at the copy the include list owns, which outlives the label table. The manual describes the five fields, and docs.sh now settles that description against a real dump - the shape, not the values, so that an example cannot go stale and turn editing a program into editing a manual. Verified with break.sh three ways: a reordered field, a dropped field, and a field renamed in the manual. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
7a55cfe151
commit
3527812c41
@@ -820,6 +820,72 @@ for line in readme.split("\n"):
|
||||
problems.append("the CosmOS README says %s is %s bytes and it is %s"
|
||||
% (named[0], said.group(1), real.group(1)))
|
||||
|
||||
# ---- The symbol file has the fields the manual says it has ----
|
||||
#
|
||||
# The manual describes -S field by field, and a program that reads a symbol file is written
|
||||
# against that description rather than against the assembler. So a field reordered, renamed
|
||||
# or added would leave every such program cutting the wrong column, and the manual would go
|
||||
# on describing a format nobody writes.
|
||||
#
|
||||
# The values are not checked, because an example naming real addresses in real files goes
|
||||
# stale the moment either changes and would turn every edit to a program into an edit to the
|
||||
# manual. The SHAPE is the contract: five fields, tab separated, in the documented order.
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
if "## Running the Assembler:" not in am:
|
||||
pass # Already reported above.
|
||||
else:
|
||||
section = am.split("## Running the Assembler:")[1].split("\n## ")[0]
|
||||
said = re.findall(r'^\| (Memory|Address|Name|File|Line) \|', section, re.M)
|
||||
if said != ["Memory", "Address", "Name", "File", "Line"]:
|
||||
problems.append("the Assembler Manual does not describe the symbol file's five"
|
||||
" fields in order; it lists %s" % (said or "none"))
|
||||
else:
|
||||
with tempfile.TemporaryDirectory() as scratch:
|
||||
dump = os.path.join(scratch, "symbols")
|
||||
# A program with labels in BOTH memories, so that both names the first field can
|
||||
# take are seen. replCalculator is a boot image, which is also the case where the
|
||||
# memory field is load bearing: both segments start at zero.
|
||||
built = subprocess.run(["./Assembler", "-I", "Programs/Libraries",
|
||||
"-I", "Programs/CosmOS/Source", "-S", dump,
|
||||
"-o", os.path.join(scratch, "out.bin"),
|
||||
"Programs/Examples/replCalculator.asm"],
|
||||
capture_output=True, text=True)
|
||||
if built.returncode != 0:
|
||||
problems.append("could not assemble a program to check the symbol file")
|
||||
else:
|
||||
memories = set()
|
||||
for number, line in enumerate(open(dump).read().splitlines(), 1):
|
||||
fields = line.split("\t")
|
||||
if len(fields) != 5:
|
||||
problems.append("line %d of a symbol file has %d fields and the"
|
||||
" Assembler Manual describes five"
|
||||
% (number, len(fields)))
|
||||
break
|
||||
memory, address, name, source, where = fields
|
||||
memories.add(memory)
|
||||
if memory not in ("Program", "Data"):
|
||||
problems.append("a symbol file says a label is in %r, and the"
|
||||
" Assembler Manual allows Program and Data" % memory)
|
||||
break
|
||||
if not re.fullmatch(r'[0-9A-F]{4}', address):
|
||||
problems.append("a symbol file gives %r as an address, and the"
|
||||
" Assembler Manual says four hexadecimal digits"
|
||||
% address)
|
||||
break
|
||||
if not name or not source or not where.isdigit():
|
||||
problems.append("a symbol file line is missing a name, a file or a"
|
||||
" line number: %r" % line)
|
||||
break
|
||||
else:
|
||||
# Only meaningful if every line was well formed, which is what the else
|
||||
# on the loop says.
|
||||
if memories != {"Program", "Data"}:
|
||||
problems.append("a program with labels in both memories produced a"
|
||||
" symbol file naming only %s"
|
||||
% ", ".join(sorted(memories)))
|
||||
|
||||
if problems:
|
||||
print("The manuals and the code disagree:")
|
||||
for p in problems:
|
||||
|
||||
Reference in New Issue
Block a user