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:
Anachronaut
2026-09-05 10:21:06 -04:00
co-authored by Claude Opus 5
parent 7a55cfe151
commit 3527812c41
4 changed files with 139 additions and 14 deletions
+66
View File
@@ -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: