mirror of
https://github.com/doldecomp/melee.git
synced 2026-11-09 17:11:05 -10:00
See tools/dat-cli/README.md. The macros in dat_macros.h write annotations into the DWARF built by the ppc-dwarf CMake preset (melee-dwarf in nix). The tool is AI-assisted and incomplete; tools/dat-cli/TODO.md lists what's missing. It will eventually hook into objdiff and generate its own coverage/progress metrics. The annotations are parsed by tools/dat-cli/src/interop/dwarf/ annotation.rs and expr.rs. The expressions are a bit complex because they need to describe conditionals that reference struct fields, but wouldn't be that hard to implement for downstream tooling in other languages. This makes the DWARF valuable to ports and other use cases for reading dat files. In the DWARF build, the archive loaders record which type each symbol is loaded as: the lbArchive loaders through DAT_ROOTS, and HSD_ArchiveGetPublicAs, a typed HSD_ArchiveGetPublicAddress that replaces most call sites. Checking those types against the archives turned up wrong ones, which are fixed here: DynamicsTemplate is split from DynamicsDesc, UnkStageDat's shadow materials, HSD_AObjDesc.obj_id, the particle banks, the item throw table, and a number of void* destinations. placeholder.h and m2c_macros.h move to libs/doldecomp/include alongside dat_macros.h. --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
163 lines
4.6 KiB
Python
Executable File
163 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import logging
|
|
import re
|
|
from pathlib import Path
|
|
|
|
IN_ROOTS = [
|
|
Path.cwd().joinpath(s).resolve(strict=True)
|
|
for s in [
|
|
"src",
|
|
"src/Runtime",
|
|
"src/MSL",
|
|
"src/melee",
|
|
"src/melee/ft/kinds",
|
|
"libs/doldecomp/include",
|
|
"libs/dolphin/include",
|
|
"libs/dolphin/src",
|
|
"build/GALE01/include",
|
|
]
|
|
]
|
|
|
|
OUT_ROOTS = [
|
|
IN_ROOTS[2], # src/MSL
|
|
IN_ROOTS[0], # src
|
|
IN_ROOTS[5], # libs/dolphin/include
|
|
IN_ROOTS[7], # build/GALE01/include
|
|
]
|
|
|
|
INCLUDE_RE = re.compile(
|
|
r'^#include [<"](?P<path>.*?)[>"](?P<remaining>.*)$', re.MULTILINE
|
|
)
|
|
DEFAULT_FMT = "#include <{}>"
|
|
|
|
|
|
def parse_args() -> list[Path]:
|
|
parser = argparse.ArgumentParser(
|
|
description="Rewrite #include paths in selected C/H files."
|
|
)
|
|
parser.add_argument(
|
|
"paths",
|
|
nargs="+",
|
|
type=Path,
|
|
help="One or more existing .c/.h files under an IN_ROOT.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
files: list[Path] = []
|
|
for raw_path in args.paths:
|
|
try:
|
|
path = raw_path.resolve(strict=True)
|
|
except FileNotFoundError:
|
|
parser.error(f"path does not exist: {raw_path}")
|
|
|
|
if not any(path.is_relative_to(root) for root in IN_ROOTS):
|
|
parser.error(f"path is not under any IN_ROOT: {raw_path}")
|
|
|
|
if not path.is_file() or not path.match("*.[ch]"):
|
|
parser.error(f"path does not match *.[ch]: {raw_path}")
|
|
|
|
files.append(path)
|
|
|
|
# Avoid processing the same file twice if it was supplied more than once.
|
|
return list(dict.fromkeys(files))
|
|
|
|
|
|
def rewrite_file(src_path: Path) -> None:
|
|
logging.debug("Opening `%s`", src_path)
|
|
text = src_path.read_text(encoding="utf-8", errors="strict", newline="")
|
|
local_root = src_path.parent.resolve(strict=True)
|
|
in_roots = [local_root, *IN_ROOTS]
|
|
out_roots = [*OUT_ROOTS]
|
|
|
|
def repl(m: re.Match[str]) -> str:
|
|
mp = Path(m["path"])
|
|
s = m[0]
|
|
logging.debug("Evaluating `%s`", s)
|
|
|
|
def put(p: Path) -> str:
|
|
p = p.resolve()
|
|
|
|
def decide_fmt() -> tuple[Path, str]:
|
|
if p.suffix not in {".c", ".h", ".inc"}:
|
|
logging.warning("Deleting suspicious include `%s`", m[0])
|
|
return p, ""
|
|
|
|
if src_path.suffix == ".c":
|
|
try:
|
|
return p.relative_to(src_path.parent), '#include "{}"{}'
|
|
except ValueError:
|
|
pass
|
|
|
|
for root in out_roots:
|
|
try:
|
|
return p.relative_to(root), "#include <{}>{}"
|
|
except ValueError:
|
|
continue
|
|
logging.error("Out root not found: %s", mp)
|
|
return p, m[0]
|
|
|
|
p, fmt = decide_fmt()
|
|
s = fmt.format(p.as_posix(), m["remaining"])
|
|
logging.info("Replaced `%s`", s)
|
|
return s
|
|
|
|
def search_relative() -> str | None:
|
|
if src_path.suffix == ".c" and mp.is_relative_to(src_path.parent):
|
|
logging.info(
|
|
"Relative include: `%s`", p := mp.relative_to(src_path.parent)
|
|
)
|
|
return put(p)
|
|
return None
|
|
|
|
def search_sus() -> str | None:
|
|
if mp.suffix not in {".c", ".h", ".inc"}:
|
|
return put(mp)
|
|
return None
|
|
|
|
def search_roots() -> str | None:
|
|
for root in in_roots:
|
|
logging.debug("Trying `%s` / `%s`", root, str(mp))
|
|
try:
|
|
resolved = root.joinpath(mp).resolve(strict=True)
|
|
logging.info("Found `%s`", resolved)
|
|
return put(resolved)
|
|
except FileNotFoundError:
|
|
logging.debug("Did not find `%s` in `%s`", mp, root)
|
|
continue
|
|
return None
|
|
|
|
s = next(
|
|
filter(
|
|
lambda x: x is not None,
|
|
map(
|
|
lambda f: f(),
|
|
[
|
|
search_relative,
|
|
search_sus,
|
|
search_roots,
|
|
],
|
|
),
|
|
),
|
|
None,
|
|
)
|
|
if s is None:
|
|
logging.error("Didn't find `%s`", s := m[0])
|
|
|
|
return s
|
|
|
|
_ = src_path.write_text(
|
|
INCLUDE_RE.sub(repl, text), encoding="utf-8", errors="strict", newline=""
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
logging.basicConfig(level=logging.WARNING)
|
|
for src_path in parse_args():
|
|
rewrite_file(src_path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|