TR2X/tools/generate_funcs
2023-10-02 11:04:35 +02:00

186 lines
5.2 KiB
Python
Executable File

#!/usr/bin/env python3
import re
from collections.abc import Callable, Iterable
from dataclasses import dataclass
from enum import StrEnum
from pathlib import Path
REPO_DIR = Path(__file__).parent.parent
DOCS_DIR = REPO_DIR / "docs"
PROGRESS_TXT_FILE = DOCS_DIR / "progress.txt"
FUNCS_H_FILE = REPO_DIR / "src/global/funcs.h"
VARS_H_FILE = REPO_DIR / "src/global/vars.h"
COMMON_HEADER = [
"// This file is autogenerated. To update it, run tools/generate_funcs.",
"",
"#pragma once",
"",
]
class ProgressFileSection(StrEnum):
TYPES = "types"
FUNCTIONS = "functions"
VARIABLES = "variables"
@dataclass
class Symbol:
offset: int
signature: str
flags: str = ""
@dataclass
class ProgressFile:
type_definitions: str
functions: list[Symbol]
variables: list[Symbol]
def to_int(source: str) -> int | None:
source = source.strip()
if source.startswith("/*"):
source = source[2:]
if source.endswith("*/"):
source = source[:-2]
source = source.strip()
if not source.replace("-", ""):
return None
if source.startswith(("0x", "0X")):
source = source[2:]
return int(source, 16)
def parse_progress_file(path: Path) -> ProgressFile:
result = ProgressFile(type_definitions="", functions=[], variables=[])
section: ProgressFileSection | None = None
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if match := re.match("^# ([A-Z]+)$", line):
section_name = match.group(1).lower()
if section_name in list(ProgressFileSection):
section = ProgressFileSection(section_name)
if line.startswith("#") or not line:
continue
if section == ProgressFileSection.TYPES:
result.type_definitions += line + "\n"
if section == ProgressFileSection.FUNCTIONS:
offset, size, flags, signature = re.split(r"\s+", line, maxsplit=3)
result.functions.append(
Symbol(
signature=signature,
offset=to_int(offset),
flags=flags,
)
)
if section == ProgressFileSection.VARIABLES:
offset, flags, signature = re.split(r"\s+", line, maxsplit=2)
result.variables.append(
Symbol(
signature=signature,
offset=to_int(offset),
flags=flags,
)
)
return result
def make_func_pointer_define(function: Symbol) -> str:
if match := re.match(
r"(?P<ret_type>.+?)\s+(?P<func_name>\w+)\s*\((?P<args>.+)\);?",
function.signature,
):
ret_type = match.group("ret_type")
func_name = match.group("func_name")
args = match.group("args")
return f"#define {func_name} (({ret_type} (*)({args}))0x{function.offset:08X})"
return ""
def make_var_pointer_define(variable: Symbol) -> str:
if match := re.match(
r"^(?P<ret_type>.+?)\s*\((?P<call_type>.*?\s)\s*\*\s*(?P<func_name>\w+)\)\s*\((?P<args>.+)\);?$",
variable.signature,
):
ret_type = match.group("ret_type")
call_type = match.group("call_type")
func_name = match.group("func_name")
args = match.group("args")
ret = f"#define {func_name} VAR_U_(0x{variable.offset:08X}, {ret_type}({call_type}*)({args}))"
return ret
if match := re.match(
r"^(?P<ret_type>.+?)\s*(?P<var_name>\w+)\s*(?P<array_def>\[[^\]]*?\])?(\s*=\s*(?P<value_def>.*?))?;?$",
variable.signature,
):
ret_type = match.group("ret_type")
var_name = match.group("var_name")
array_def = match.group("array_def")
value_def = match.group("value_def")
if array_def:
return f"#define {var_name} ARRAY_(0x{variable.offset:08X}, {ret_type}, {array_def})"
elif value_def:
return f"#define {var_name} VAR_I_(0x{variable.offset:08X}, {ret_type}, {value_def})"
else:
return f"#define {var_name} VAR_U_(0x{variable.offset:08X}, {ret_type})"
return ""
def make_funcs_h(functions: list[Symbol]) -> None:
header = [
*COMMON_HEADER,
'#include "global/types.h"',
"",
"// clang-format off",
]
footer = ["// clang-format on"]
defines = []
for function in functions:
if "+" not in function.flags and (
define := make_func_pointer_define(function)
):
defines.append(define)
FUNCS_H_FILE.write_text("\n".join([*header, *defines, *footer]))
def make_vars_h(variables: list[Symbol]) -> None:
header = [
*COMMON_HEADER,
'#include "global/types.h"',
'#include "inject_util.h"',
"",
"// clang-format off",
]
footer = ["// clang-format on"]
defines = []
for variable in variables:
if "+" not in variable.flags and (
define := make_var_pointer_define(variable)
):
defines.append(define)
VARS_H_FILE.write_text("\n".join([*header, *defines, *footer]))
def main() -> None:
progress_file = parse_progress_file(PROGRESS_TXT_FILE)
make_funcs_h(progress_file.functions)
make_vars_h(progress_file.variables)
if __name__ == "__main__":
main()