dreamcast/regs/gen/maple_commands.py

161 lines
4.6 KiB
Python

from dataclasses import dataclass
from typing import Union
import sys
from sh7091 import read_input
from sh7091 import headers
from generate import renderer
@dataclass
class CommandNamespace:
name: str
issuing_right: set[str]
command_code: int
data_size: int
def command_namespace(namespace: CommandNamespace,
data_fields: list[tuple[str, tuple[int, str]]]):
yield f"namespace {namespace.name} {{"
yield f"constexpr uint32_t command_code = {hex(namespace.command_code)};"
if namespace.data_size == (0, None):
assert data_fields == []
# do nothing
else:
length, variable = namespace.data_size
yield ""
if variable is not None:
assert variable.lower() == "n"
yield "template <int N>"
yield "struct data_fields {"
for field_name, field_size in data_fields:
const, var = field_size
if var is None:
yield f"uint8_t {field_name}[{const}];"
elif const == 0:
assert var == "n"
yield f"uint8_t {field_name}[{var.upper()}];"
else:
yield f"uint8_t {field_name}[{const} + {var.upper()}];"
yield "};"
yield ""
if variable is not None:
assert variable == "n"
yield f"static_assert((sizeof (struct data_fields<0>)) == {length});"
else:
yield f"static_assert((sizeof (struct data_fields)) == {length});"
yield "}"
yield ""
def parse_data_size(data_size, base, multiple) -> tuple[int, str]:
def parse_term(s):
try:
return int(s, base) * multiple
except ValueError:
div = s.split("/")
if len(div) == 1:
# this must be a variable
assert multiple == 1, s
a, = div
return a
elif len(div) == 2:
# this must be a variable divided by a number
a, b = div
b = int(b, 10)
assert b == multiple
return a
else:
assert False, div
_terms = data_size.split("+")
terms = tuple(parse_term(term) for term in _terms)
if len(terms) == 1:
term, = terms
if type(term) == str:
return (0, term)
elif type(term) == int:
return (term, None)
else:
assert False, (_terms, terms)
elif len(terms) == 2:
assert type(terms[0]) == int
assert type(terms[1]) == str
return terms
else:
assert False, (_terms, terms)
def new_aggregator():
last_name = None
namespace = None
data_fields = []
all_names = set()
def process_row(row):
nonlocal last_name
nonlocal namespace
nonlocal data_fields
if row["name"] == "":
assert all(v == "" for v in row.values()), row
return
if row["name"] != last_name:
if namespace is not None:
yield namespace, data_fields
else:
assert data_fields == []
assert row["name"] != ""
last_name = row["name"]
issuing_right = set(row["issuing_right"].split(", "))
assert all(s in {"host", "peripheral"} for s in issuing_right), row
assert issuing_right != set(), issuing_right
data_size = parse_data_size(row["data_size"], 16, 4)
namespace = CommandNamespace(
name = row["name"],
issuing_right = issuing_right,
command_code = int(row["command_code"], 16),
data_size = data_size,
)
data_fields = []
# fall through
assert last_name is None or row["name"] == last_name, (row["name"], last_name)
if row["data_field"] != "":
assert row["data_field_size"] != ""
data_fields.append((
row["data_field"],
parse_data_size(row["data_field_size"], 10, 1)
))
def terminate():
nonlocal namespace
nonlocal data_fields
if namespace is not None:
yield namespace, data_fields
def process(rows):
for row in rows:
yield from process_row(row)
yield from terminate()
return process
input_file = sys.argv[1]
rows = read_input(input_file)
process = new_aggregator()
render, out = renderer()
render(headers())
for namespace, data_fields in process(rows):
render(command_namespace(namespace, data_fields))
sys.stdout.write(out.getvalue())