An emulator, assembler, and disassembler for the Sega Game Gear
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

164 lines
4.8 KiB

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
  4. # Released under the terms of the MIT License. See LICENSE for details.
  5. """
  6. This script generates 'src/assembler/instructions.inc.c' from
  7. 'src/assembler/instructions.yml'. It should be run automatically by make
  8. when the latter is modified, but can also be run manually.
  9. """
  10. from __future__ import print_function
  11. import re
  12. import time
  13. SOURCE = "src/assembler/instructions.yml"
  14. DEST = "src/assembler/instructions.inc.c"
  15. ENCODING = "utf8"
  16. TAB = " " * 4
  17. try:
  18. import yaml
  19. except ImportError:
  20. print("Error: PyYAML is required (https://pypi.python.org/pypi/PyYAML)\n"
  21. "If you don't want to rebuild {0}, do:\n`make -t {0}`".format(DEST))
  22. exit(1)
  23. re_date = re.compile(r"^(\s*@AUTOGEN_DATE\s*)(.*?)$", re.M)
  24. re_inst = re.compile(
  25. r"(/\* @AUTOGEN_INST_BLOCK_START \*/\n*)(.*?)"
  26. r"(\n*/\* @AUTOGEN_INST_BLOCK_END \*/)", re.S)
  27. re_lookup = re.compile(
  28. r"(/\* @AUTOGEN_LOOKUP_BLOCK_START \*/\n*)(.*?)"
  29. r"(\n*/\* @AUTOGEN_LOOKUP_BLOCK_END \*/)", re.S)
  30. class Instruction(object):
  31. """
  32. Represent a single ASM instruction mnemonic.
  33. """
  34. ARG_TYPES = {
  35. "register": "AT_REGISTER",
  36. "immediate": "AT_IMMEDIATE",
  37. "indirect": "AT_INDIRECT",
  38. "indexed": "AT_INDEXED|AT_INDIRECT",
  39. "condition": "AT_CONDITION",
  40. "port": "AT_PORT"
  41. }
  42. def __init__(self, name, data):
  43. self._name = name
  44. self._data = data
  45. def _get_arg_parse_mask(self, num):
  46. """
  47. Return the appropriate mask to parse_args() for the num-th argument.
  48. """
  49. types = set()
  50. optional = False
  51. for case in self._data["cases"]:
  52. if num < len(case["type"]):
  53. types.add(self.ARG_TYPES[case["type"][num]])
  54. else:
  55. optional = True
  56. if not types:
  57. return "AT_NONE"
  58. if optional:
  59. types.add("AT_OPTIONAL")
  60. return "|".join(types)
  61. def _handle_return(self, arg, indent=1):
  62. """
  63. Return code to handle an instruction return statement.
  64. """
  65. tabs = TAB * indent
  66. if arg == "error":
  67. return tabs + "INST_ERROR(ARG_SYNTAX)"
  68. else:
  69. data = ", ".join("0x%02X" % byte for byte in arg)
  70. return tabs + "INST_RETURN({0}, {1})".format(len(arg), data)
  71. def _handle_case(self, case):
  72. """
  73. TODO
  74. """
  75. return [TAB + "// " + str(case)]
  76. def render(self):
  77. """
  78. Convert data for an individual instruction into a C parse function.
  79. """
  80. lines = []
  81. if self._data["args"]:
  82. lines.append("{tab}INST_TAKES_ARGS(\n{tab2}{0}, \n{tab2}{1}, "
  83. "\n{tab2}{2}\n{tab})".format(
  84. self._get_arg_parse_mask(0), self._get_arg_parse_mask(1),
  85. self._get_arg_parse_mask(2), tab=TAB, tab2=TAB * 2))
  86. else:
  87. lines.append(TAB + "INST_TAKES_NO_ARGS")
  88. if "return" in self._data:
  89. lines.append(self._handle_return(self._data["return"]))
  90. elif "cases" in self._data:
  91. for case in self._data["cases"]:
  92. lines.extend(self._handle_case(case))
  93. lines.append(TAB + "INST_ERROR(ARG_TYPE)")
  94. else:
  95. msg = "Missing return or case block for {0} instruction"
  96. raise RuntimeError(msg.format(self._name))
  97. contents = "\n".join(lines)
  98. return "INST_FUNC({0})\n{{\n{1}\n}}".format(self._name, contents)
  99. def build_inst_block(data):
  100. """
  101. Return the instruction parser block, given instruction data.
  102. """
  103. return "\n\n".join(
  104. Instruction(k, v).render() for k, v in sorted(data.items()))
  105. def build_lookup_block(data):
  106. """
  107. Return the instruction lookup block, given instruction data.
  108. """
  109. macro = TAB + "HANDLE({0})"
  110. return "\n".join(macro.format(inst) for inst in sorted(data.keys()))
  111. def process(template, data):
  112. """
  113. Return C code generated from a source template and instruction data.
  114. """
  115. inst_block = build_inst_block(data)
  116. lookup_block = build_lookup_block(data)
  117. date = time.asctime(time.gmtime())
  118. result = re_date.sub(r"\1{0} UTC".format(date), template)
  119. result = re_inst.sub(r"\1{0}\3".format(inst_block), result)
  120. result = re_lookup.sub(r"\1{0}\3".format(lookup_block), result)
  121. return result
  122. def main():
  123. """
  124. Main script entry point.
  125. """
  126. with open(SOURCE, "r") as fp:
  127. text = fp.read().decode(ENCODING)
  128. with open(DEST, "r") as fp:
  129. template = fp.read().decode(ENCODING)
  130. data = yaml.load(text)
  131. result = process(template, data)
  132. # with open(DEST, "w") as fp:
  133. # fp.write(result.encode(ENCODING))
  134. print(result) # TODO: remove me!
  135. if __name__ == "__main__":
  136. main()