Merge branch 'initial' into 'master'
Initial See merge request max/gb-asm!1
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
tmp/
|
||||
|
||||
*.py[cod]
|
||||
*.egg
|
||||
build
|
||||
htmlcov
|
||||
src/gbasm.egg-info
|
||||
**/.mypy_cache
|
||||
**/.pytest_cache
|
||||
**/.tox
|
||||
**/__pycache__
|
||||
59
.gitlab-ci.yml
Normal file
59
.gitlab-ci.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
# Change pip's cache directory to be inside the project directory since we can
|
||||
# only cache local items.
|
||||
variables:
|
||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
||||
|
||||
# Pip's cache doesn't store the python packages
|
||||
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
|
||||
#
|
||||
# If you want to also cache the installed packages, you have to install
|
||||
# them in a virtualenv and cache it as well.
|
||||
cache:
|
||||
paths:
|
||||
- .cache/pip
|
||||
- venv/
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
|
||||
before_script:
|
||||
- python -V
|
||||
- pip install virtualenv
|
||||
- virtualenv venv
|
||||
- source venv/bin/activate
|
||||
|
||||
lint:
|
||||
stage: lint
|
||||
image: python:3
|
||||
script:
|
||||
- pip3 install black
|
||||
- black --check src/
|
||||
|
||||
python-37:
|
||||
stage: test
|
||||
image: python:3.7
|
||||
script:
|
||||
- pip3 install tox
|
||||
- tox -e py37
|
||||
|
||||
python-38:
|
||||
stage: test
|
||||
image: python:3.8
|
||||
script:
|
||||
- pip3 install tox
|
||||
- tox -e py38
|
||||
|
||||
python-39:
|
||||
stage: test
|
||||
image: python:3.9
|
||||
script:
|
||||
- pip3 install tox
|
||||
- tox -e py39
|
||||
|
||||
python-310:
|
||||
stage: test
|
||||
image: python:3.10
|
||||
script:
|
||||
- pip3 install tox
|
||||
- tox -e py310
|
||||
36
setup.cfg
Normal file
36
setup.cfg
Normal file
@@ -0,0 +1,36 @@
|
||||
[metadata]
|
||||
name = gbasm
|
||||
version = 0.0.1
|
||||
author = Max Regan
|
||||
author_email = mgregan2@gmail.com
|
||||
description = An assembler for Gameboy assembly language
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: OS Independent
|
||||
|
||||
[options]
|
||||
package_dir =
|
||||
= src
|
||||
packages = find:
|
||||
python_requires = >=3.7
|
||||
tests_require = pytest
|
||||
test_suite = test_assemble
|
||||
requires = pyyaml, foobarbdfasf
|
||||
|
||||
[options.packages.find]
|
||||
where =
|
||||
src
|
||||
test
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
gbasm = gbasm.gbasm:main
|
||||
|
||||
[tox:tox]
|
||||
envlist = py3{7,8,9,10}
|
||||
|
||||
[testenv]
|
||||
deps = pytest
|
||||
pyyaml
|
||||
commands = pytest
|
||||
0
src/gbasm/__init__.py
Normal file
0
src/gbasm/__init__.py
Normal file
6
src/gbasm/arguments/ArgumentParser.py
Normal file
6
src/gbasm/arguments/ArgumentParser.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class ArgumentParser(object):
|
||||
def can_parse(token: str) -> bool:
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_name() -> str:
|
||||
raise NotImplementedError()
|
||||
141
src/gbasm/arguments/ArgumentParsers.py
Normal file
141
src/gbasm/arguments/ArgumentParsers.py
Normal 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
|
||||
72
src/gbasm/arguments/Arguments.py
Normal file
72
src/gbasm/arguments/Arguments.py
Normal 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
|
||||
10
src/gbasm/arguments/__init__.py
Normal file
10
src/gbasm/arguments/__init__.py
Normal 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
178
src/gbasm/gbasm.py
Executable 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()
|
||||
19
src/gbasm/instructions/Instruction.py
Normal file
19
src/gbasm/instructions/Instruction.py
Normal 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()
|
||||
1
src/gbasm/instructions/__init__.py
Executable file
1
src/gbasm/instructions/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
from .Instruction import Instruction
|
||||
52
src/gbasm/instructions/dec.py
Normal file
52
src/gbasm/instructions/dec.py
Normal 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))
|
||||
52
src/gbasm/instructions/inc.py
Normal file
52
src/gbasm/instructions/inc.py
Normal 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))
|
||||
60
src/gbasm/instructions/jr.py
Normal file
60
src/gbasm/instructions/jr.py
Normal 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)
|
||||
40
src/gbasm/instructions/ld.py
Normal file
40
src/gbasm/instructions/ld.py
Normal 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")
|
||||
24
src/gbasm/instructions/nop.py
Normal file
24
src/gbasm/instructions/nop.py
Normal 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])
|
||||
24
src/gbasm/instructions/stop.py
Normal file
24
src/gbasm/instructions/stop.py
Normal 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])
|
||||
20
test/cases/format.yaml
Normal file
20
test/cases/format.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: two_nop
|
||||
program: |
|
||||
NOP
|
||||
NOP
|
||||
expected:
|
||||
- 0x00
|
||||
- 0x00
|
||||
---
|
||||
name: unused_labels
|
||||
program: |
|
||||
start:
|
||||
NOP
|
||||
middle:
|
||||
NOP
|
||||
end:
|
||||
expected:
|
||||
- 0x00
|
||||
- 0x00
|
||||
---
|
||||
19
test/cases/format/format.yaml
Normal file
19
test/cases/format/format.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: two_nop
|
||||
program: |
|
||||
NOP
|
||||
NOP
|
||||
expected:
|
||||
- 0x00
|
||||
- 0x00
|
||||
---
|
||||
name: unused_labels
|
||||
program: |
|
||||
start:
|
||||
NOP
|
||||
middle:
|
||||
NOP
|
||||
end:
|
||||
expected:
|
||||
- 0x00
|
||||
- 0x00
|
||||
74
test/cases/instructions/dec.yaml
Normal file
74
test/cases/instructions/dec.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: dec_a
|
||||
program: |
|
||||
DEC A
|
||||
expected:
|
||||
- 0x3D
|
||||
|
||||
---
|
||||
name: dec_b
|
||||
program: |
|
||||
DEC B
|
||||
expected:
|
||||
- 0x05
|
||||
---
|
||||
name: dec_c
|
||||
program: |
|
||||
DEC C
|
||||
expected:
|
||||
- 0x0D
|
||||
---
|
||||
name: dec_d
|
||||
program: |
|
||||
DEC D
|
||||
expected:
|
||||
- 0x15
|
||||
---
|
||||
name: dec_e
|
||||
program: |
|
||||
DEC E
|
||||
expected:
|
||||
- 0x1D
|
||||
---
|
||||
name: dec_h
|
||||
program: |
|
||||
DEC H
|
||||
expected:
|
||||
- 0x25
|
||||
---
|
||||
name: dec_l
|
||||
program: |
|
||||
DEC L
|
||||
expected:
|
||||
- 0x2D
|
||||
---
|
||||
name: dec_(hl)
|
||||
program: |
|
||||
DEC (HL)
|
||||
expected:
|
||||
- 0x35
|
||||
|
||||
---
|
||||
name: dec_bc
|
||||
program: |
|
||||
DEC BC
|
||||
expected:
|
||||
- 0x0B
|
||||
---
|
||||
name: dec_de
|
||||
program: |
|
||||
DEC DE
|
||||
expected:
|
||||
- 0x1B
|
||||
---
|
||||
name: dec_hl
|
||||
program: |
|
||||
DEC HL
|
||||
expected:
|
||||
- 0x2B
|
||||
---
|
||||
name: dec_sp
|
||||
program: |
|
||||
DEC SP
|
||||
expected:
|
||||
- 0x3B
|
||||
74
test/cases/instructions/inc.yaml
Normal file
74
test/cases/instructions/inc.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: inc_a
|
||||
program: |
|
||||
INC A
|
||||
expected:
|
||||
- 0x3c
|
||||
|
||||
---
|
||||
name: inc_b
|
||||
program: |
|
||||
INC B
|
||||
expected:
|
||||
- 0x04
|
||||
---
|
||||
name: inc_c
|
||||
program: |
|
||||
INC C
|
||||
expected:
|
||||
- 0x0c
|
||||
---
|
||||
name: inc_d
|
||||
program: |
|
||||
INC D
|
||||
expected:
|
||||
- 0x14
|
||||
---
|
||||
name: inc_e
|
||||
program: |
|
||||
INC E
|
||||
expected:
|
||||
- 0x1c
|
||||
---
|
||||
name: inc_h
|
||||
program: |
|
||||
INC H
|
||||
expected:
|
||||
- 0x24
|
||||
---
|
||||
name: inc_l
|
||||
program: |
|
||||
INC L
|
||||
expected:
|
||||
- 0x2c
|
||||
---
|
||||
name: inc_(hl)
|
||||
program: |
|
||||
INC (HL)
|
||||
expected:
|
||||
- 0x34
|
||||
|
||||
---
|
||||
name: inc_bc
|
||||
program: |
|
||||
INC BC
|
||||
expected:
|
||||
- 0x03
|
||||
---
|
||||
name: inc_de
|
||||
program: |
|
||||
INC DE
|
||||
expected:
|
||||
- 0x13
|
||||
---
|
||||
name: inc_hl
|
||||
program: |
|
||||
INC HL
|
||||
expected:
|
||||
- 0x23
|
||||
---
|
||||
name: inc_sp
|
||||
program: |
|
||||
INC SP
|
||||
expected:
|
||||
- 0x33
|
||||
370
test/cases/instructions/jr.yaml
Normal file
370
test/cases/instructions/jr.yaml
Normal file
@@ -0,0 +1,370 @@
|
||||
---
|
||||
name: jr_bkwd
|
||||
program: |
|
||||
label:
|
||||
JR label
|
||||
expected:
|
||||
- 0x18
|
||||
- 0xFE
|
||||
---
|
||||
name: jr_fwd
|
||||
program: |
|
||||
JR label
|
||||
label:
|
||||
expected:
|
||||
- 0x18
|
||||
- 0x00
|
||||
|
||||
---
|
||||
name: jr_z_bkwd
|
||||
program: |
|
||||
label:
|
||||
JR Z label
|
||||
expected:
|
||||
- 0x28
|
||||
- 0xFE
|
||||
---
|
||||
name: jr_z_fwd
|
||||
program: |
|
||||
JR Z label
|
||||
label:
|
||||
expected:
|
||||
- 0x28
|
||||
- 0x00
|
||||
|
||||
---
|
||||
name: jr_nz_bkwd
|
||||
program: |
|
||||
label:
|
||||
JR NZ label
|
||||
expected:
|
||||
- 0x20
|
||||
- 0xFE
|
||||
---
|
||||
name: jr_nz_fwd
|
||||
program: |
|
||||
JR NZ label
|
||||
label:
|
||||
expected:
|
||||
- 0x20
|
||||
- 0x00
|
||||
|
||||
---
|
||||
name: jr_c_bkwd
|
||||
program: |
|
||||
label:
|
||||
JR C label
|
||||
expected:
|
||||
- 0x38
|
||||
- 0xFE
|
||||
---
|
||||
name: jr_c_fwd
|
||||
program: |
|
||||
JR C label
|
||||
label:
|
||||
expected:
|
||||
- 0x38
|
||||
- 0x00
|
||||
|
||||
---
|
||||
name: jr_nc_bkwd
|
||||
program: |
|
||||
label:
|
||||
JR NC label
|
||||
expected:
|
||||
- 0x30
|
||||
- 0xFE
|
||||
---
|
||||
name: jr_nc_fwd
|
||||
program: |
|
||||
JR NC label
|
||||
label:
|
||||
expected:
|
||||
- 0x30
|
||||
- 0x00
|
||||
|
||||
---
|
||||
# Jump backward by the maximum amount
|
||||
name: jr_far_bkwd
|
||||
program: |
|
||||
far_label:
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
JR far_label
|
||||
expected: [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x80 ]
|
||||
|
||||
---
|
||||
# Jump forward by the maximum amount
|
||||
name: jr_far_fwd
|
||||
program: |
|
||||
JR far_label
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
far_label:
|
||||
expected: [ 0x18, 0x7F,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]
|
||||
81
test/cases/instructions/ld_reg16.yaml
Normal file
81
test/cases/instructions/ld_reg16.yaml
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
name: ld_bc
|
||||
program: |
|
||||
LD BC 0
|
||||
expected:
|
||||
- 0x01
|
||||
- 0x00
|
||||
- 0x00
|
||||
---
|
||||
name: ld_de
|
||||
program: |
|
||||
LD DE 0
|
||||
expected:
|
||||
- 0x11
|
||||
- 0x00
|
||||
- 0x00
|
||||
---
|
||||
name: ld_hl
|
||||
program: |
|
||||
LD HL 0
|
||||
expected:
|
||||
- 0x21
|
||||
- 0x00
|
||||
- 0x00
|
||||
---
|
||||
name: ld_sp
|
||||
program: |
|
||||
LD SP 0
|
||||
expected:
|
||||
- 0x31
|
||||
- 0x00
|
||||
- 0x00
|
||||
|
||||
---
|
||||
name: ld_16_max
|
||||
program: |
|
||||
LD BC 65535
|
||||
expected:
|
||||
- 0x01
|
||||
- 0xFF
|
||||
- 0xFF
|
||||
---
|
||||
name: ld_16_negative
|
||||
program: |
|
||||
LD BC -1
|
||||
expected:
|
||||
- 0x01
|
||||
- 0xFF
|
||||
- 0xFF
|
||||
---
|
||||
name: ld_16_min
|
||||
program: |
|
||||
LD BC -32768
|
||||
expected:
|
||||
- 0x01
|
||||
- 0x00
|
||||
- 0x80
|
||||
---
|
||||
name: ld_16_00FF
|
||||
program: |
|
||||
LD BC 0x00FF
|
||||
expected:
|
||||
- 0x01
|
||||
- 0xFF
|
||||
- 0x00
|
||||
---
|
||||
name: ld_16_FF00
|
||||
program: |
|
||||
LD BC 0xFF00
|
||||
expected:
|
||||
- 0x01
|
||||
- 0x00
|
||||
- 0xFF
|
||||
---
|
||||
name: ld_16_100
|
||||
program: |
|
||||
LD BC 10
|
||||
expected:
|
||||
- 0x01
|
||||
- 0x0A
|
||||
- 0x00
|
||||
12
test/cases/instructions/special.yaml
Normal file
12
test/cases/instructions/special.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: nop
|
||||
program: |
|
||||
NOP
|
||||
expected:
|
||||
- 0x00
|
||||
---
|
||||
name: stop
|
||||
program: |
|
||||
STOP
|
||||
expected:
|
||||
- 0x10
|
||||
66
test/test_assemble.py
Normal file
66
test/test_assemble.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from gbasm.gbasm import assemble
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import yaml
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(format="")
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
|
||||
class AssembleCase(object):
|
||||
def __init__(self, name: str, program: str, expected: bytes):
|
||||
self.name = name
|
||||
self.program = program
|
||||
self.expected = expected
|
||||
|
||||
|
||||
def find_case_files(subdir: str):
|
||||
test_root = Path(os.path.dirname(os.path.abspath(__file__)))
|
||||
case_root = test_root / "cases" / subdir
|
||||
return case_root.glob("**/*.yaml")
|
||||
|
||||
|
||||
def get_test_cases(subdir: str):
|
||||
cases = []
|
||||
files = find_case_files(subdir)
|
||||
for f in files:
|
||||
index = 0
|
||||
with open(str(f), "r") as yaml_file:
|
||||
test_descs = yaml.safe_load_all(yaml_file)
|
||||
for desc in test_descs:
|
||||
try:
|
||||
case = AssembleCase(
|
||||
desc["name"], desc["program"], bytes(desc["expected"])
|
||||
)
|
||||
except TypeError:
|
||||
logger.exception("Failed to parse yaml: %s", desc)
|
||||
cases.append(case)
|
||||
return cases
|
||||
|
||||
|
||||
instruction_cases = get_test_cases("instructions")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"case", instruction_cases, ids=[case.name for case in instruction_cases]
|
||||
)
|
||||
def test_assemble_instruction(case):
|
||||
lines = case.program.split("\n")
|
||||
assembled = assemble(lines)
|
||||
assert assembled == case.expected
|
||||
|
||||
|
||||
format_cases = get_test_cases("format")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", format_cases, ids=[case.name for case in format_cases])
|
||||
def test_format_instruction(case):
|
||||
lines = case.program.split("\n")
|
||||
assembled = assemble(lines)
|
||||
assert assembled == case.expected
|
||||
Reference in New Issue
Block a user