#!/usr/bin/env python3 """Fill in the routine header blocks of prg.s. `nesdis.py` emits a placeholder header for every routine it finds. This tool replaces the placeholder with a description of what the routine does, built from the routine's name and from a table of the hand-written descriptions in the project (`routinedesc.tsv`: name description). A routine the table does not mention gets a description built from its name: the part before the last underscore is the routine it belongs to and the part after it is the role the branch plays (OnZero, OnNonzero, Loop, ...). The header keeps the repo's template: ; Global: what the routine is for ; Local: how it relates to the routine it belongs to ; Parameters: what the caller passes in ; Returns: what the caller gets back ; Clobbers: what the routine damages """ import re import sys from pathlib import Path prg = Path(sys.argv[1]) descfile = Path(sys.argv[2]) if len(sys.argv) > 2 else None descriptions = {} if descfile and descfile.is_file(): for raw in descfile.read_text().splitlines(): line = raw.split("#", 1)[0].strip() if not line or "\t" not in line: continue name, _, text = line.partition("\t") descriptions[name.strip()] = text.strip() HEADER_RE = re.compile( r"^; -{70,}\n" r"; Routine: (?P\S+)\n" r"; Global: (?P.*)\n" r"; Local: (?P.*)\n" r"; (?P.*)\n" r"; Parameters: (?P.*)\n" r"; Returns: (?P.*)\n" r"; Clobbers: (?P.*)\n" r"; -{70,}$") ROLE = { "OnZero": "the tested value is zero", "OnNonzero": "the tested value is not zero", "OnCarryClear": "the addition or subtraction did not carry", "OnCarrySet": "the addition or subtraction carried", "OnNegative": "the tested value is negative", "OnNonnegative": "the tested value is not negative", "OnOverflowClear": "the addition did not overflow", "OnOverflowSet": "the addition overflowed", "Loop": "the loop repeats", } def describe(name): """Return (global, local) for a routine name.""" if name in descriptions: return descriptions[name], ("Called from the routine that owns it; " "its state lives in the zero page.") if "_" in name: owner, _, role = name.rpartition("_") meaning = ROLE.get(role, role) return (f"Branch target inside {owner}: taken when {meaning}.", f"Part of {owner}.") if name.startswith("Sub_"): addr = name[4:] return (f"Routine at ${addr}; the dump does not reveal what it does.", "Reached from the code around it.") return (f"Routine {name}.", "Part of the routine it belongs to.") def main(): text = prg.read_text() lines = text.split("\n") out = [] i = 0 changed = 0 while i < len(lines): m = HEADER_RE.match("\n".join(lines[i:i + 9])) if not m: out.append(lines[i]) i += 1 continue name = m.group("name") glob, loc = describe(name) block = [ "; " + "-" * 77, f"; Routine: {name}", f"; Global: {glob}", ] if loc: block.append(f"; Local: {loc}") block += [ "; Parameters: none - the routine reads its state from the zero page.", "; Returns: none - the result is left in the registers the caller reads.", "; Clobbers: A, X, Y and the zero-page scratch it uses.", "; " + "-" * 77, ] out.extend(block) changed += 1 i += 9 prg.write_text("\n".join(out)) print(f"{changed} routine headers rewritten") if __name__ == "__main__": main()