ch65/tools/gendispatch.py

178 lines
4 KiB
Python
Raw Permalink Normal View History

import sys
import pprint
import dataclasses
import pyparsing as pp
import click
pp.ParserElement.set_default_whitespace_chars(" \t")
newline = pp.Word("\r\n").suppress()
comment = pp.Group(pp.Literal("#") + ... + newline).suppress()
blank = newline
prologue = (comment | blank)[...] # (comment | blank)[...]
heading = pp.Literal("[").suppress() + ... + pp.Literal("]").suppress() + newline
statement = pp.Group(pp.Word(pp.alphanums + "(-") + ... + newline)
operand = pp.Word(pp.alphanums + "()#")
accumulator = pp.Literal("A")
immediate = pp.Literal("#d8")
zp = pp.Literal("a8")
x_ind_zp = pp.Literal("a8,X")
y_ind_zp = pp.Literal("a8,Y")
x_ind_zp_ind = pp.Literal("(a8,X)")
zp_ind_y_ind = pp.Literal("(a8),Y")
absolute = pp.Literal("a16")
x_ind_absolute = pp.Literal("a16,X")
y_ind_absolute = pp.Literal("a16,Y")
absolute_ind = pp.Literal("(a16)")
relative = pp.Literal("r8")
mode = (
accumulator
| immediate
| x_ind_zp
| y_ind_zp
| zp
| x_ind_zp_ind
| zp_ind_y_ind
| x_ind_absolute
| y_ind_absolute
| absolute
| absolute_ind
| relative
)
opcode = pp.Group(pp.common.hex_integer + pp.Word(pp.alphas) + pp.Opt(mode) + newline)
body = (comment | opcode | statement | blank)[...]
section = pp.Group(heading + body)
doc = prologue + section[...]
@dataclasses.dataclass
class Instruction:
opcode: str
operands: list[str]
bytes: list[int]
_RESERVED = ("and", "or", "xor")
def cname(name: str) -> str:
name = (
name.lower()
.replace("(", "")
.replace("#", "")
.replace(")", "i")
.replace(",", "_")
.replace("+", "_")
.replace("'", "p")
)
if name in _RESERVED:
return name + "_"
return name
def func_name(instruction: Instruction) -> str:
parts = [instruction.opcode] + instruction.operands
return cname("_".join(parts))
def remap_operand(operands: list[str]):
if not operands:
return ""
elif operands == ["A"]:
return "regs.a"
return f"{cname('_'.join(operands))}()"
2026-05-11 07:46:55 +02:00
def parse(opcodes: list):
for ops in opcodes:
top = doc.parse_file(ops, parse_all=True).as_list()
for section in top:
if section[0] == "opcodes":
for opcode in section[1:]:
if isinstance(opcode[0], int):
yield Instruction(
opcode=opcode[1],
operands=[opcode[2]] if len(opcode) > 2 else [],
bytes=[opcode[0]],
)
def goto_prefix(emit):
emit(
"""#pragma once
#include "cores/w65c02v1.h"
template<typename Board>
void W65C02v1<Board>::run() {
"""
)
def goto_dispatch(emit, top: list[Instruction]):
by_bytes = {}
for instruction in top:
by_bytes[instruction.bytes[0]] = instruction
emit(f"static const void* const dispatch[] = {{")
for i in range(256):
if i in by_bytes:
emit(f"&&_{func_name(by_bytes[i])},")
else:
emit(f"&&_illegal,")
emit("};")
def goto_suffix(emit):
emit(
"""
_illegal:
illegal();
}
"""
)
2026-05-10 19:45:06 +02:00
USE_NEXT = False
def goto_implement(emit, top):
goto_prefix(emit)
goto_dispatch(emit, top)
2026-05-10 19:45:06 +02:00
if USE_NEXT:
emit("_next:")
emit("goto *dispatch[fetch()];")
for instruction in top:
emit(
f"""_{func_name(instruction)}:
2026-05-10 19:45:06 +02:00
{cname(instruction.opcode)}({remap_operand(instruction.operands)});"""
)
2026-05-10 19:45:06 +02:00
if USE_NEXT:
emit("goto _next;")
else:
emit("goto *dispatch[fetch()];")
goto_suffix(emit)
@click.command()
2026-05-11 07:46:55 +02:00
@click.option("--opcodes", required=True, multiple=True, type=click.File(mode="r"))
@click.option("--out", required=True, type=click.File(mode="w", lazy=True, atomic=True))
2026-05-11 07:46:55 +02:00
def main(opcodes: list, out):
def emit(*args):
print(*args, file=out)
goto_implement(emit, list(parse(opcodes)))
if __name__ == "__main__":
main()