feat: globals_build --build merge + generated JSON/MD (Task 2)
build/globals.json is generated (build/ is gitignored, regenerable via globals_build.py --build); docs/global-reference.md is the tracked human view. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
16403
docs/global-reference.md
Normal file
16403
docs/global-reference.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,94 @@ def lint(entries: dict[int, dict], all_addrs: set[int]) -> tuple[list[str], list
|
||||
return errors, warnings
|
||||
|
||||
|
||||
GEN_JSON_META = {"generated_from": "vm-map/globals.toml",
|
||||
"note": "DO NOT EDIT -- generated by tools/globals_build.py --build"}
|
||||
|
||||
|
||||
def load_auto(path) -> dict:
|
||||
try:
|
||||
return json.loads(Path(path).read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _auto_category(e: dict) -> str:
|
||||
"""Best-effort category for an auto shape-map entry (unreliable; curation overrides)."""
|
||||
kind = e.get("kind") or ""
|
||||
if kind == "record-table":
|
||||
return "data-table"
|
||||
if kind == "array-1d":
|
||||
return "data-table"
|
||||
lbl = e.get("label") or ""
|
||||
if lbl.startswith("string-table"):
|
||||
return "string-table"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def merge(curated: dict[int, dict], auto: dict) -> dict[int, dict]:
|
||||
out: dict[int, dict] = {}
|
||||
for addr_s, e in auto.get("globals", {}).items():
|
||||
if not e.get("label"):
|
||||
continue
|
||||
addr = int(addr_s, 16)
|
||||
out[addr] = {"address": f"0x{addr:x}", "name": None, "category": _auto_category(e),
|
||||
"type": e.get("type"), "value_domain": None, "usage": e.get("label"),
|
||||
"source": "auto-shape", "confidence": e.get("confidence") or "low",
|
||||
"depends_on": [], "provenance": "auto"}
|
||||
for addr, e in curated.items():
|
||||
out[addr] = {"address": f"0x{addr:x}", "name": e.get("name"),
|
||||
"category": e.get("category", "unknown"), "type": e.get("type"),
|
||||
"value_domain": e.get("value_domain"), "usage": e.get("usage", ""),
|
||||
"source": e.get("source", "inference"), "confidence": e.get("confidence", "low"),
|
||||
"depends_on": [f"0x{_parse_addr(d):x}" for d in e.get("depends_on", [])],
|
||||
"provenance": "curated"}
|
||||
return out
|
||||
|
||||
|
||||
def emit_json(merged: dict[int, dict], meta: dict) -> str:
|
||||
out = {"meta": {**GEN_JSON_META, **meta},
|
||||
"globals": {e["address"]: e for _, e in sorted(merged.items())}}
|
||||
return json.dumps(out, ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
|
||||
def emit_reference_md(merged: dict[int, dict]) -> str:
|
||||
L = ["<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->",
|
||||
"# Global Variable Reference (generated)", "",
|
||||
f"{len(merged)} globals ({sum(1 for e in merged.values() if e['provenance']=='curated')} curated, "
|
||||
f"{sum(1 for e in merged.values() if e['provenance']=='auto')} auto shape-inferred). "
|
||||
"Source of truth: `vm-map/globals.toml`.", ""]
|
||||
by_cat = collections.defaultdict(list)
|
||||
for addr, e in merged.items():
|
||||
by_cat[e["category"]].append(addr)
|
||||
for cat in sorted(by_cat):
|
||||
L += [f"## {cat}", "", "| address | name | conf | source | usage |", "|---|---|---|---|---|"]
|
||||
# curated first, then by address
|
||||
for addr in sorted(by_cat[cat], key=lambda a: (merged[a]["provenance"] != "curated", a)):
|
||||
e = merged[addr]
|
||||
name = e["name"] or "—"
|
||||
usage = (e["usage"] or "").replace("|", "\\|").replace("\n", " ")
|
||||
L.append(f"| `{e['address']}` | {name} | {e['confidence']} | {e['source']} | {usage} |")
|
||||
L.append("")
|
||||
return "\n".join(L) + "\n"
|
||||
|
||||
|
||||
def build(tp: Path) -> int:
|
||||
curated, meta = load_toml(tp)
|
||||
auto = load_auto(AUTO_MAP)
|
||||
all_addrs = set(curated) | {int(a, 16) for a in auto.get("globals", {})}
|
||||
errors, warnings = lint(curated, all_addrs)
|
||||
for m in warnings: print("warn:", m)
|
||||
if errors:
|
||||
for m in errors: print("error:", m)
|
||||
return 1
|
||||
merged = merge(curated, auto)
|
||||
paths.BUILD.mkdir(parents=True, exist_ok=True)
|
||||
(paths.BUILD / "globals.json").write_text(emit_json(merged, meta), encoding="utf-8")
|
||||
(paths.REPO / "docs" / "global-reference.md").write_text(emit_reference_md(merged), encoding="utf-8")
|
||||
print(f"build: {len(merged)} globals -> build/globals.json, docs/global-reference.md")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--build", action="store_true")
|
||||
|
||||
@@ -27,8 +27,22 @@ def test_lint_catches_bad_vocab():
|
||||
check(any("category" in e for e in errors), "lint flags bad category")
|
||||
check(any("confidence" in e for e in errors), "lint flags auto-shape claiming high confidence")
|
||||
|
||||
def test_merge_precedence():
|
||||
curated, _ = G.load_toml(paths.VM_MAP / "globals.toml")
|
||||
auto = G.load_auto(paths.BUILD / "global-var-map.json")
|
||||
merged = G.merge(curated, auto)
|
||||
check(merged[0xa57]["name"] == "lily_form_a", "curated 0xa57 name wins over auto label")
|
||||
check(merged[0xa57]["category"] == "story-flag", "curated 0xa57 category overrides auto string-table")
|
||||
check(merged[0xa57]["provenance"] == "curated", "0xa57 marked curated")
|
||||
# an address only in the auto map falls through as provenance=auto
|
||||
auto_only = next((a for a in auto.get("globals", {})
|
||||
if int(a, 16) not in curated and auto["globals"][a].get("label")), None)
|
||||
check(auto_only is not None and merged[int(auto_only, 16)]["provenance"] == "auto",
|
||||
"auto-only address retained with provenance=auto")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_load_and_lint()
|
||||
test_lint_catches_bad_vocab()
|
||||
test_merge_precedence()
|
||||
print(f"\n{len(FAILS)} failures")
|
||||
sys.exit(1 if FAILS else 0)
|
||||
|
||||
Reference in New Issue
Block a user