104 lines
3.1 KiB
Python
Executable File
104 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""Apply deterministic C++ source fixes from gate diagnostics."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import re
|
||
from pathlib import Path
|
||
from typing import List, Set
|
||
|
||
KNOWN_STL_HEADERS = {"vector", "map", "set", "optional", "tuple", "memory", "string", "algorithm"}
|
||
SYMBOL_TO_HEADER = {
|
||
"std::vector": "vector",
|
||
"std::map": "map",
|
||
"std::set": "set",
|
||
"std::optional": "optional",
|
||
"std::tuple": "tuple",
|
||
"std::shared_ptr": "memory",
|
||
"std::unique_ptr": "memory",
|
||
}
|
||
|
||
|
||
def parse_headers_from_stderr(stderr: str) -> Set[str]:
|
||
out: Set[str] = set()
|
||
for m in re.finditer(r"header [‘']<([^>]+)>[’']", stderr):
|
||
h = m.group(1).strip()
|
||
if h in KNOWN_STL_HEADERS:
|
||
out.add(h)
|
||
return out
|
||
|
||
|
||
def parse_headers_from_code_symbols(code: str) -> Set[str]:
|
||
out: Set[str] = set()
|
||
for symbol, header in SYMBOL_TO_HEADER.items():
|
||
if symbol in code:
|
||
out.add(header)
|
||
return out
|
||
|
||
|
||
def existing_headers(lines: List[str]) -> Set[str]:
|
||
out: Set[str] = set()
|
||
for line in lines:
|
||
m = re.match(r"\s*#include\s*<([^>]+)>", line)
|
||
if m:
|
||
out.add(m.group(1).strip())
|
||
return out
|
||
|
||
|
||
def insert_headers(code: str, headers_to_add: Set[str]) -> str:
|
||
if not headers_to_add:
|
||
return code
|
||
lines = code.splitlines()
|
||
have = existing_headers(lines)
|
||
needed = sorted(h for h in headers_to_add if h not in have)
|
||
if not needed:
|
||
return code
|
||
|
||
insert_at = 0
|
||
for i, line in enumerate(lines):
|
||
if line.strip().startswith("#include"):
|
||
insert_at = i + 1
|
||
include_lines = [f"#include <{h}>" for h in needed]
|
||
new_lines = lines[:insert_at] + include_lines + lines[insert_at:]
|
||
return "\n".join(new_lines) + ("\n" if code.endswith("\n") else "")
|
||
|
||
|
||
def main() -> None:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--code-file", required=True)
|
||
ap.add_argument("--gates-json", required=True)
|
||
ap.add_argument("--out-code-file", required=True)
|
||
ap.add_argument("--out-report", default="")
|
||
args = ap.parse_args()
|
||
|
||
code_path = Path(args.code_file)
|
||
gates_path = Path(args.gates_json)
|
||
code = code_path.read_text()
|
||
gates = json.loads(gates_path.read_text()) if gates_path.exists() else {}
|
||
|
||
compile_stderr = str(((gates.get("gates") or {}).get("compile") or {}).get("stderr", ""))
|
||
test_stderr = str(((gates.get("gates") or {}).get("tests") or {}).get("stderr", ""))
|
||
|
||
diag_headers = parse_headers_from_stderr(compile_stderr + "\n" + test_stderr)
|
||
symbol_headers = parse_headers_from_code_symbols(code)
|
||
headers = diag_headers | symbol_headers
|
||
|
||
fixed = insert_headers(code, headers)
|
||
Path(args.out_code_file).write_text(fixed)
|
||
|
||
report = {
|
||
"changed": fixed != code,
|
||
"headers_requested": sorted(headers),
|
||
"headers_from_diagnostics": sorted(diag_headers),
|
||
"headers_from_symbols": sorted(symbol_headers),
|
||
}
|
||
if args.out_report:
|
||
Path(args.out_report).write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
|
||
print(json.dumps(report, indent=2, sort_keys=True))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|