blacken sources

This commit is contained in:
2021-11-27 21:19:32 -05:00
parent 05cfbb6448
commit 3724bcff8b
22 changed files with 1395 additions and 0 deletions

0
src/gbasm/__init__.py Normal file
View File

View File

@@ -0,0 +1,6 @@
class ArgumentParser(object):
def can_parse(token: str) -> bool:
raise NotImplementedError()
def get_name() -> str:
raise NotImplementedError()

View File

@@ -0,0 +1,141 @@
from .ArgumentParser import ArgumentParser
from . import Arguments
class Address(ArgumentParser):
NAME = "Address"
def __init__(self):
pass
def can_parse(self, token: str) -> bool:
try:
addr = int(token, base=0)
return addr < 0x8000 and addr >= 0
except ValueError:
return False
def get_name(self) -> str:
return Address.NAME
class Label(ArgumentParser):
NAME = "Label"
def __init__(self):
pass
def can_parse(self, token: str) -> bool:
if not token[0] in string.ascii_letters + ["_"]:
return False
return True
def parse(self, token) -> Arguments.Register8:
return Arguments.Label(token)
def get_name(self) -> str:
return Label.NAME
class Register8(ArgumentParser):
NAME = "Register8"
def __init__(self, indirect: bool = False, indirect_increment: bool = False):
self.indirect = indirect
def can_parse(self, token: str) -> bool:
if token in Arguments.Register8.REGISTERS:
return True
if self.indirect and token in Arguments.Register8.REGISTERS_INDIRECT:
return True
if (
self.indirect_increment
and token in Arguments.Register8.REGISTERS_INDIRECT_INCREMENT
):
return True
return False
def parse(self, token) -> Arguments.Register8:
return Arguments.Register8(token)
def get_name(self) -> str:
return Register8.NAME
class Register16(ArgumentParser):
NAME = "Immediate8"
def __init__(self, indirect: bool = False, indirect_increment: bool = False):
self.indirect = indirect
def can_parse(self, token: str) -> bool:
return token in Arguments.Register16.REGISTERS
def to_argument(self, token: str):
return None
def parse(self, token) -> Arguments.Register16:
return Arguments.Register16(token)
def get_name(self) -> str:
return Register16.NAME
class Immediate8(ArgumentParser):
NAME = "Immediate8"
def __init__(self):
pass
def can_parse(self, token: str) -> bool:
try:
addr = int(token, base=0)
return addr <= 0xFF and addr >= 0
except ValueError:
return False
def get_name(self) -> str:
return Immediate8.NAME
class Immediate16(ArgumentParser):
NAME = "Immediate16"
def __init__(self):
pass
def can_parse(self, token: str) -> bool:
try:
addr = int(token, base=0)
return addr <= 0xFFFF and addr >= 0
except ValueError:
return False
def parse(self, token) -> Arguments.Immediate16:
return Arguments.Immediate16(token)
def get_name(self) -> str:
return Immediate16.NAME
class Flag(ArgumentParser):
NAME = "Flag"
def __init__(self):
pass
def can_parse(self, token: str) -> bool:
return token in Arguments.Flag.FLAGS
def parse(self, token) -> Arguments.Immediate16:
return Arguments.Flag(token)
def get_name(self) -> str:
return Flag.NAME

View File

@@ -0,0 +1,72 @@
class Argument(object):
def __init__(self):
pass
class Address(Argument):
NAME = "Address"
def __init__(self, value: int):
self.value = value
class Label(Argument):
NAME = "Label"
def __init__(self, value: str):
self.value = value
class Register8(Argument):
NAME = "Register8"
REGISTERS = ["A", "B", "C", "D", "E", "H", "L"]
REGISTERS_INDIRECT = ["(HL)"]
REGISTERS_INDIRECT_INCREMENT = ["(HL)", "(HL+)", "(HL-)"]
def __init__(self, value: str):
if value not in Register8.REGISTERS + Register8.REGISTERS_INDIRECT_INCREMENT:
raise ValueError("Unknown Register8: {}".format(value))
self.value = value
class Register16(Argument):
NAME = "Immediate8"
REGISTERS = ["BC", "DE", "DE", "HL", "SP"]
def __init__(self, value: int):
if value not in Register16.REGISTERS:
raise ValueError("Unknown Register16: {}".format(value))
self.value = value
class Immediate8(Argument):
NAME = "Immediate8"
def __init__(self, value: int):
self.value = value
class Immediate16(Argument):
NAME = "Immediate16"
def __init__(self, value: int):
self.value = value
class Flag(Argument):
NAME = "Immediate8"
FLAGS = ["Z", "C", "NZ", "NC"]
def __init__(self, value: str):
if value not in Flag.FLAGS:
raise ValueError("Unknown Flag: {}".format(value))
self.value = value

View File

@@ -0,0 +1,10 @@
from .ArgumentParser import ArgumentParser
from .ArgumentParsers import (
Label,
Address,
Immediate8,
Immediate16,
Register8,
Register16,
)
from .Arguments import Argument

178
src/gbasm/gbasm.py Executable file
View File

@@ -0,0 +1,178 @@
#!/usr/bin/python3
import argparse
import logging
import sys
from .instructions import Instruction
from .instructions.inc import Inc
from .instructions.dec import Dec
from .instructions.nop import Nop
from .instructions.stop import Stop
from .instructions.jr import Jr
from .instructions.ld import Ld
from .arguments import ArgumentParser, Argument
from typing import Callable, Dict, List, Optional
logger = logging.getLogger(__name__)
COMMENT_CHAR = "#"
LABEL_SUFFIX = ":"
GB_INSTRUCTIONS = [
Nop(),
Stop(),
Inc(),
Dec(),
Jr(),
Ld(),
]
def build_instruction_map() -> Dict[str, Instruction]:
d = {} # type: Dict[str, Instruction]
for i in GB_INSTRUCTIONS:
d[i.token] = i
return d
def try_parse_arguments(
args: List[str], arg_types: List[ArgumentParser]
) -> Optional[List[Argument]]:
if len(args) != len(arg_types):
return None
out_args = []
for (arg, arg_type) in zip(args, arg_types):
try:
out_args.append(arg_type.parse(arg))
except ValueError:
return None
return out_args
def parse_line_size(instruction: Instruction, arguments: List[str]) -> bytes:
for argtype_list in instruction.argument_specs:
args = try_parse_arguments(arguments, argtype_list)
if args is not None:
return instruction.num_bytes(args)
raise ValueError("Failed to parse line.")
def parse_line_bytes(
instruction: Instruction,
arguments: List[str],
instruction_addr: int,
label_resolver: Callable[[str], int],
) -> bytes:
for argtype_list in instruction.argument_specs:
args = try_parse_arguments(arguments, argtype_list)
if args is not None:
return instruction.to_bytes(args, instruction_addr, label_resolver)
raise ValueError("Failed to parse line.")
def assemble_file(infile) -> bytes:
program = infile.readlines()
return assemble(program)
def assemble(lines: str) -> bytes:
instruction_map = build_instruction_map()
logger.debug("Instruction map: %s", instruction_map)
labels = {} # type: Dict[str, int]
program = bytes()
def label_resolver(label: str) -> int:
nonlocal labels
return labels[label]
for step in ["SIZE", "CONTENT"]:
logger.debug("Starting step: %s", step)
byte_offset = 0
for line_num, line in enumerate(lines):
# Remove comments
line = line.split(COMMENT_CHAR)[0]
# Tokenize
tokens = line.split()
if len(tokens) == 0:
continue
instruction_name = tokens[0]
args = tokens[1:]
try:
instruction = instruction_map[instruction_name]
except KeyError:
if instruction_name[-1] == LABEL_SUFFIX:
if step == "SIZE":
label = instruction_name[:-1]
logger.debug("Found label '%s' at %s", label, byte_offset)
if label in labels.keys():
raise KeyError(
"Label '%s' defined at %s and %s",
label,
labels[label],
line_num,
)
labels[label] = byte_offset
continue
raise KeyError(
'Unknown instruction "%s" on line %s', instruction_name, line_num
)
if step == "CONTENT":
try:
program += parse_line_bytes(
instruction, args, byte_offset, label_resolver
)
except ValueError:
raise ValueError("Failed to parse line %s,\n%s", line_num, line)
byte_offset += parse_line_size(instruction, args)
if step == "SIZE":
logger.info("Program size: %s bytes", byte_offset)
logger.debug("Found labels: %s", labels)
return program
def main() -> None:
parser = argparse.ArgumentParser(description="An assembler for Gameboy assembly")
parser.add_argument(
"--infile", "-i", type=argparse.FileType("r"), default=sys.stdin
)
parser.add_argument(
"--outfile", "-o", type=argparse.FileType("wb", 0), default=sys.stdout
)
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
logging.basicConfig(format="%(levelname)s: %(message)s")
logger.setLevel(logging.INFO)
if args.verbose:
logging.basicConfig(
format="%(levelname)s: %(filename)s:%(lineno)d: %(message)s"
)
logger.setLevel(logging.DEBUG)
program = assemble_file(args.infile)
args.outfile.write(program)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,19 @@
from typing import Callable, List
from ..arguments import Argument, ArgumentParser
class Instruction(object):
def __init__(self, token: str, argument_specs: List[List[ArgumentParser]]):
self.token = token
self.argument_specs = argument_specs
def num_bytes(self, arguments) -> int:
raise NotImplementedError()
def to_bytes(
self,
arguments: List[Argument],
instruction_addr: int,
label_resolver: Callable[[str], int],
) -> bytes:
raise NotImplementedError()

View File

@@ -0,0 +1 @@
from .Instruction import Instruction

View File

@@ -0,0 +1,52 @@
from .Instruction import Instruction
from ..arguments.ArgumentParsers import Register8, Register16
from ..arguments import Argument
from typing import Callable, List
class Dec(Instruction):
def __init__(self):
argtypes = [[Register8()], [Register16()]]
super().__init__("DEC", argtypes)
def num_bytes(self, arguments) -> int:
return 1
def to_bytes(
self,
arguments: List[Argument],
instruction_addr: int,
label_resolver: Callable[[str], int],
) -> bytes:
if len(arguments) != 1:
raise ValueError("Incorrect number of arguments")
value = arguments[0].value
if value == "BC":
return bytes([0x0B])
if value == "DE":
return bytes([0x1B])
if value == "HL":
return bytes([0x2B])
if value == "SP":
return bytes([0x3B])
if value == "A":
return bytes([0x3D])
if value == "B":
return bytes([0x05])
if value == "C":
return bytes([0x0D])
if value == "D":
return bytes([0x15])
if value == "E":
return bytes([0x1D])
if value == "H":
return bytes([0x25])
if value == "L":
return bytes([0x2D])
if value == "(HL)":
return bytes([0x35])
raise ValueError("Unknown value: {}".format(value))

View File

@@ -0,0 +1,52 @@
from .Instruction import Instruction
from ..arguments.ArgumentParsers import Register8, Register16
from ..arguments import Argument
from typing import Callable, List
class Inc(Instruction):
def __init__(self):
argtypes = [[Register8()], [Register16()]]
super().__init__("INC", argtypes)
def num_bytes(self, arguments) -> int:
return 1
def to_bytes(
self,
arguments: List[Argument],
instruction_addr: int,
label_resolver: Callable[[str], int],
) -> bytes:
if len(arguments) != 1:
raise ValueError("Incorrect number of arguments")
value = arguments[0].value
if value == "BC":
return bytes([0x03])
if value == "DE":
return bytes([0x13])
if value == "HL":
return bytes([0x23])
if value == "SP":
return bytes([0x33])
if value == "A":
return bytes([0x3C])
if value == "B":
return bytes([0x04])
if value == "C":
return bytes([0x0C])
if value == "D":
return bytes([0x14])
if value == "E":
return bytes([0x1C])
if value == "H":
return bytes([0x24])
if value == "L":
return bytes([0x2C])
if value == "(HL)":
return bytes([0x34])
raise ValueError("Unknown value: {}".format(value))

View File

@@ -0,0 +1,60 @@
from .Instruction import Instruction
from ..arguments.ArgumentParsers import Flag, Label
from ..arguments import Argument
from typing import Callable, List
class Jr(Instruction):
INSTRUCTION_SIZE = 2
def __init__(self):
argtypes = [[Label()], [Flag(), Label()]]
super().__init__("JR", argtypes)
def num_bytes(self, arguments) -> int:
return Jr.INSTRUCTION_SIZE
def to_bytes(
self,
arguments: List[Argument],
instruction_address: int,
label_resolver: Callable[[str], int],
) -> bytes:
out_bytes = bytearray()
if len(arguments) == 1:
out_bytes.append(0x18)
dest_arg = arguments[0].value
elif len(arguments) == 2:
flag_dict = {"NZ": 0x20, "NC": 0x30, "Z": 0x28, "C": 0x38}
flag = arguments[0].value
try:
out_bytes.append(flag_dict[flag])
except KeyError:
logger.exception("Instruction JR does not accept flag %s", arg)
dest_arg = arguments[1].value
else:
raise ValueError("Incorrect number of arguments")
if isinstance(dest_arg, str):
label = dest_arg
dest_addr = label_resolver(label)
else:
dest_addr = dest_arg
addr_offset = dest_addr - (instruction_address + Jr.INSTRUCTION_SIZE)
if addr_offset > 127 or addr_offset < -128:
raise ValueError(
"JR instruction cannot jump to {} ({}), from {}, {} bytes away".format(
label, dest_addr, instruction_address, addr_offset
)
)
if addr_offset < 0:
addr_offset += 256
out_bytes.append(addr_offset)
return bytes(out_bytes)

View File

@@ -0,0 +1,40 @@
from .Instruction import Instruction
from ..arguments.ArgumentParsers import Register16, Immediate16
from ..arguments import Argument
from typing import Callable, List
class Ld(Instruction):
def __init__(self):
argtypes = [[Register16(), Immediate16()]]
super().__init__("LD", argtypes)
def num_bytes(self, arguments) -> int:
# the instruction_address and label_resolver are unused
return len(self.to_bytes(arguments, 0, lambda x: None))
def encode_reg_16(self, register: Register16, immediate: Immediate16) -> bytes:
reg_dict = {"BC": 0x01, "DE": 0x11, "HL": 0x21, "SP": 0x31}
imm = int(immediate.value, 0)
print("immediate", imm)
out = bytearray()
out.append(reg_dict[register.value])
out.extend(imm.to_bytes(2, "little", signed=imm < 0))
return bytes(out)
def to_bytes(
self,
arguments: List[Argument],
instruction_address: int,
label_resolver: Callable[[str], int],
) -> bytes:
# print(arguments)
# print("Register16", type(arguments[0]))
# print("Immediate16", isinstance(arguments[1], Immediate16))
# if isinstance(arguments[0], Register16) and \
# isinstance(arguments[1], Immediate16):
return self.encode_reg_16(arguments[0], arguments[1])
raise TypeError("Unhandled argument types")

View File

@@ -0,0 +1,24 @@
from .Instruction import Instruction
from ..arguments import Argument
from typing import Callable, List
class Nop(Instruction):
def __init__(self):
argtypes = [[]]
super().__init__("NOP", argtypes)
def num_bytes(self, arguments) -> int:
return 1
def to_bytes(
self,
arguments: List[Argument],
instruction_addr: int,
label_resolver: Callable[[str], int],
) -> bytes:
if len(arguments) != 0:
raise ValueError("Incorrect number of arguments")
return bytes([0x00])

View File

@@ -0,0 +1,24 @@
from .Instruction import Instruction
from ..arguments import Argument
from typing import Callable, List
class Stop(Instruction):
def __init__(self):
argtypes = [[]]
super().__init__("STOP", argtypes)
def num_bytes(self, arguments) -> int:
return 1
def to_bytes(
self,
arguments: List[Argument],
instruction_addr: int,
label_resolver: Callable[[str], int],
) -> bytes:
if len(arguments) != 0:
raise ValueError("Incorrect number of arguments")
return bytes([0x10])