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))}()" 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 void W65C02v1::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(); } """ ) USE_NEXT = False def goto_implement(emit, top): goto_prefix(emit) goto_dispatch(emit, top) if USE_NEXT: emit("_next:") emit("goto *dispatch[fetch()];") for instruction in top: emit( f"""_{func_name(instruction)}: {cname(instruction.opcode)}({remap_operand(instruction.operands)});""" ) if USE_NEXT: emit("goto _next;") else: emit("goto *dispatch[fetch()];") goto_suffix(emit) @click.command() @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)) def main(opcodes: list, out): def emit(*args): print(*args, file=out) goto_implement(emit, list(parse(opcodes))) if __name__ == "__main__": main()