mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
127 lines
5.6 KiB
Python
127 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""sweep_citations.py — deterministically localise every gcc-source citation in a markdown doc.
|
|
|
|
WHY THIS IS A TOOL AND NOT AN AGENT WAVE. The codegen-map audits (T34/T35) established that our
|
|
docs cite `tools/reference/gcc-papermario/` = gcc **2.8.1**, while we build with **2.7.2**
|
|
(`tools/reference/gcc-2.7.2/`), and that the drift is large and NON-uniform (+27 in sched.c, +611 in
|
|
reload1.c) — large enough that a citation lands inside a DIFFERENT FUNCTION and every sentence built
|
|
on it still reads plausibly. Deciding *whether a claim is true* needs judgement. Deciding *where a
|
|
line number actually points* does not: it is a lookup, and a lookup belongs in zero-token tooling.
|
|
|
|
So this tool answers the mechanical half exactly, and leaves only genuine judgement for a human/agent:
|
|
|
|
symbol-form cite (`combine_regs:1825`) -> compare to the symbol's REAL 2.7.2 definition line.
|
|
Verdict is fully determined: OK / DRIFT(+N) / NO-SYMBOL.
|
|
file-form cite (`cse.c:8626`) -> report which function that line falls INSIDE in 2.7.2,
|
|
so a reader can see at a glance whether the doc's
|
|
surrounding prose is talking about that function at all.
|
|
|
|
A cite whose enclosing function differs from the symbol the prose names is the exact signature of the
|
|
"landed in a neighbouring function" failure — that is what to grep the report for.
|
|
|
|
python3 tools/sweep_citations.py docs/matching-cookbook.md [--src tools/reference/gcc-2.7.2]
|
|
"""
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
DEF_RE = re.compile(r"^([a-z_][a-z0-9_]*)\s*\(") # K&R definition: name on its own line
|
|
# `sym:1234` or `file.c:1234` (optionally a range `1234-1250`)
|
|
CITE_RE = re.compile(r"([A-Za-z_][A-Za-z0-9_./-]*?)\s*:\s*(\d{2,5})(?:\s*-\s*\d{2,5})?")
|
|
SKIP_PREFIX = ("http", "https", "ftp", "Phase", "phase", "§")
|
|
|
|
|
|
def index_source(src):
|
|
"""(defs, lines) — defs: symbol -> [(file, line)] ; lines: file -> [text]."""
|
|
defs, lines = {}, {}
|
|
for root, _dirs, files in os.walk(src):
|
|
for f in files:
|
|
if not f.endswith((".c", ".h", ".md")):
|
|
continue
|
|
p = os.path.join(root, f)
|
|
rel = os.path.relpath(p, src)
|
|
try:
|
|
L = open(p, errors="replace").read().split("\n")
|
|
except Exception:
|
|
continue
|
|
lines[rel] = L
|
|
lines.setdefault(os.path.basename(rel), L) # allow bare "cse.c"
|
|
for i, ln in enumerate(L, 1):
|
|
m = DEF_RE.match(ln)
|
|
if m:
|
|
defs.setdefault(m.group(1), []).append((rel, i))
|
|
return defs, lines
|
|
|
|
|
|
def enclosing(lines, fname, line):
|
|
"""Name of the function whose K&R definition most recently precedes `line`."""
|
|
L = lines.get(fname) or lines.get(os.path.basename(fname))
|
|
if not L or not (1 <= line <= len(L)):
|
|
return None, len(L) if L else 0
|
|
best = None
|
|
for i in range(line - 1, -1, -1):
|
|
m = DEF_RE.match(L[i])
|
|
if m:
|
|
best = (m.group(1), i + 1)
|
|
break
|
|
return best, len(L)
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("doc")
|
|
ap.add_argument("--src", default="tools/reference/gcc-2.7.2")
|
|
ap.add_argument("--only-problems", action="store_true")
|
|
a = ap.parse_args()
|
|
|
|
if not os.path.isdir(a.src):
|
|
print("no source tree at %s" % a.src)
|
|
sys.exit(2)
|
|
defs, lines = index_source(a.src)
|
|
|
|
rows, seen = [], set()
|
|
for n, raw in enumerate(open(a.doc, errors="replace").read().split("\n"), 1):
|
|
for m in CITE_RE.finditer(raw):
|
|
who, ln = m.group(1), int(m.group(2))
|
|
if who.startswith(SKIP_PREFIX) or who.isdigit():
|
|
continue
|
|
key = (who, ln)
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
is_file = who.endswith((".c", ".h", ".md")) or "/" in who
|
|
if is_file:
|
|
base = os.path.basename(who)
|
|
if base not in lines and base.replace(".md", "") not in lines:
|
|
continue
|
|
enc, total = enclosing(lines, base, ln)
|
|
if enc is None:
|
|
rows.append((n, who, ln, "OUT-OF-RANGE", "file has %d lines" % total))
|
|
else:
|
|
rows.append((n, who, ln, "IN-FN", "%s (defined :%d)" % (enc[0], enc[1])))
|
|
else:
|
|
if who not in defs:
|
|
continue # not a gcc symbol we can resolve; skip quietly
|
|
cands = defs[who]
|
|
near = min(cands, key=lambda c: abs(c[1] - ln))
|
|
d = ln - near[1]
|
|
verdict = "OK" if abs(d) <= 3 else "DRIFT %+d" % d
|
|
rows.append((n, who, ln, verdict, "%s real :%d" % (near[0], near[1])))
|
|
|
|
probs = [r for r in rows if r[3].startswith("DRIFT") or r[3] == "OUT-OF-RANGE"]
|
|
print("%s — %d resolvable citations" % (a.doc, len(rows)))
|
|
print(" symbol-form OK : %d" % len([r for r in rows if r[3] == "OK"]))
|
|
print(" symbol-form DRIFTED : %d" % len([r for r in rows if r[3].startswith("DRIFT")]))
|
|
print(" file-form localised : %d" % len([r for r in rows if r[3] == "IN-FN"]))
|
|
print(" out of range : %d" % len([r for r in rows if r[3] == "OUT-OF-RANGE"]))
|
|
print()
|
|
show = probs if a.only_problems else rows
|
|
print("%-6s %-30s %-7s %-12s %s" % ("mdline", "cite", "line", "verdict", "in real 2.7.2"))
|
|
for n, who, ln, v, note in show:
|
|
print("%-6d %-30s %-7d %-12s %s" % (n, who[:30], ln, v, note))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|