Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/outfmt/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ class BinaryEmitter(CodeEmitter):

def emit(
self,
output_filename,
program_name,
loader_bytes,
entry_point,
program_bytes,
aux_bin_blocks,
aux_headless_bin_blocks,
output_filename: str,
program_name: str,
loader_bytes: bytearray | None,
entry_point: int,
program_bytes: bytearray | bytes | list[int],
aux_bin_blocks: list[tuple[str, list[int]]],
aux_headless_bin_blocks: list[list[int]],
):
"""Emits resulting binary file."""
"""Emits the resulting binary file."""
with open(output_filename, "wb") as f:
f.write(bytearray(program_bytes))
12 changes: 6 additions & 6 deletions src/outfmt/codeemitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ def emit(
self,
output_filename: str,
program_name: str,
loader_bytes: bytearray,
entry_point,
program_bytes,
aux_bin_blocks,
aux_headless_bin_blocks,
):
loader_bytes: bytearray | None,
entry_point: int,
program_bytes: bytearray | bytes | list[int],
aux_bin_blocks: list[tuple[str, list[int]]],
aux_headless_bin_blocks: list[list[int]],
) -> None:
pass
13 changes: 6 additions & 7 deletions src/outfmt/gensnapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ def patchAddr(self, addr: int, data: bytes):

def __init__(
self,
loader_bytes,
clear_addr,
mc_addr,
mc_bytes,
loader_bytes: bytearray | None,
clear_addr: int,
mc_addr: int,
mc_bytes: bytearray,
):
"""
Creates a snapshot object ready to run a BASIC program as if RUN was just executed.
Expand Down Expand Up @@ -69,9 +69,8 @@ def __init__(
eilast: Whether the last instruction prevents an interrupt
"""

self.A = self.A2 = self.B = self.B2 = self.C = self.C2 = self.D = self.D2 = self.E = self.E2 = self.H = (
self.H2
) = self.L = self.L2 = self.F = self.F2 = self.R = self.IXL = self.IXH = 0
self.A = self.A2 = self.B = self.B2 = self.C = self.C2 = self.D = self.D2 = self.E = self.E2 = self.H = 0
self.H2 = self.L = self.L2 = self.F = self.F2 = self.R = self.IXL = self.IXH = 0

self.IYH = 0x5C
self.IYL = 0x3A # 0x5C3A is the normal value of IY for ROM use
Expand Down
28 changes: 14 additions & 14 deletions src/outfmt/sna.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ class SnaEmitter(CodeEmitter):

def generate(
self,
loader_bytes,
clear_addr,
entry_point,
program_bytes,
):
loader_bytes: bytearray | None,
clear_addr: int,
entry_point: int,
program_bytes: bytearray,
) -> bytearray:
"""
Format of .SNA file:

Expand Down Expand Up @@ -90,17 +90,17 @@ def generate(

def emit(
self,
output_filename,
program_name,
loader_bytes,
entry_point,
program_bytes,
aux_bin_blocks,
aux_headless_bin_blocks,
):
output_filename: str,
program_name: str,
loader_bytes: bytearray | None,
entry_point: int,
program_bytes: bytearray | bytes | list[int],
aux_bin_blocks: list[tuple[str, list[int]]],
aux_headless_bin_blocks: list[list[int]],
) -> None:
"""Emit a .SNA file with the compiled bytes; ignores loader_bytes"""

sna_data = self.generate(None, entry_point - 1, entry_point, program_bytes)
sna_data = self.generate(None, entry_point - 1, entry_point, bytearray(program_bytes))

# Write output file
with open(output_filename, "wb") as f:
Expand Down
27 changes: 14 additions & 13 deletions src/outfmt/tzx.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class TZX(CodeEmitter):
HEADER_TYPE_CODE = 3

def __init__(self):
"""Initializes the object with standard header"""
"""Initializes the object with a standard header"""
self.output = bytearray(b"ZXTape!")
self.out(0x1A)
self.out([self.VERSION_MAJOR, self.VERSION_MINOR])
Expand All @@ -44,14 +44,14 @@ def LH(self, value):

return [valueL, valueH]

def out(self, l):
def out(self, l: int | list[int]) -> None:
"""Adds a list of bytes to the output string"""
if not isinstance(l, list):
l = [l]

self.output.extend([int(i) & 0xFF for i in l])

def standard_block(self, _bytes):
def standard_block(self, _bytes: bytearray | bytes | list[int]) -> None:
"""Adds a standard block of bytes"""
self.out(self.BLOCK_STANDARD) # Standard block ID
self.out(self.LH(1000)) # 1000 ms standard pause
Expand All @@ -64,7 +64,7 @@ def standard_block(self, _bytes):

self.out(checksum)

def dump(self, fname):
def dump(self, fname: str) -> None:
"""Saves TZX file to fname"""
with open(fname, "wb") as f:
f.write(self.output)
Expand Down Expand Up @@ -122,21 +122,22 @@ def save_program(self, title, bytes, line=32768):

def emit(
self,
output_filename,
program_name,
loader_bytes,
entry_point,
program_bytes,
aux_bin_blocks,
aux_headless_bin_blocks,
):
"""Emits resulting tape file."""
output_filename: str,
program_name: str,
loader_bytes: bytearray | None,
entry_point: int,
program_bytes: bytearray | bytes | list[int],
aux_bin_blocks: list[tuple[str, list[int]]],
aux_headless_bin_blocks: list[list[int]],
) -> None:
"""Emits the resulting tape file."""
if loader_bytes is not None:
self.save_program("loader", loader_bytes, line=1) # Put line 0 to protect against MERGE

self.save_code(program_name, entry_point, program_bytes)
for name, block in aux_bin_blocks:
self.save_code(name, 0, block)

for block in aux_headless_bin_blocks:
self.standard_block(block)

Expand Down
14 changes: 7 additions & 7 deletions src/outfmt/z80.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,13 @@ def generate(

def emit(
self,
output_filename,
program_name,
loader_bytes,
entry_point,
program_bytes,
aux_bin_blocks,
aux_headless_bin_blocks,
output_filename: str,
program_name: str,
loader_bytes: bytearray | None,
entry_point: int,
program_bytes: bytearray | bytes | list[int],
aux_bin_blocks: list[tuple[str, list[int]]],
aux_headless_bin_blocks: list[list[int]],
):
"""Save a .Z80 file with the compiled bytes; ignores loader_bytes"""

Expand Down
Empty file added tests/outfmt/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions tests/outfmt/test_binary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# --------------------------------------------------------------------
# SPDX-License-Identifier: AGPL-3.0-or-later
# © Copyright 2008-2024 José Manuel Rodríguez de la Rosa and contributors.
# See the file CONTRIBUTORS.md for copyright details.
# See https://www.gnu.org/licenses/agpl-3.0.html for details.
# --------------------------------------------------------------------

from src.outfmt.binary import BinaryEmitter


def test_binary_emitter(tmp_path):
output_file = tmp_path / "test.bin"
emitter = BinaryEmitter()
program_bytes = b"\x01\x02\x03\x04"

emitter.emit(
output_filename=str(output_file),
program_name="test",
loader_bytes=None,
entry_point=16384,
program_bytes=program_bytes,
aux_bin_blocks=[],
aux_headless_bin_blocks=[],
)

assert output_file.exists()
assert output_file.read_bytes() == program_bytes
57 changes: 57 additions & 0 deletions tests/outfmt/test_sna.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# --------------------------------------------------------------------
# SPDX-License-Identifier: AGPL-3.0-or-later
# © Copyright 2008-2024 José Manuel Rodríguez de la Rosa and contributors.
# See the file CONTRIBUTORS.md for copyright details.
# See https://www.gnu.org/licenses/agpl-3.0.html for details.
# --------------------------------------------------------------------

from src.outfmt.sna import SnaEmitter


def test_sna_emitter_generate():
emitter = SnaEmitter()
program_bytes = b"\x00" * 100
entry_point = 0x8000

# generate(self, loader_bytes, clear_addr, entry_point, program_bytes)
# SnaEmitter.emit calls generate(None, entry_point - 1, entry_point, program_bytes)
sna_data = emitter.generate(None, entry_point - 1, entry_point, program_bytes)

assert len(sna_data) == 27 + 49152

# Check some header values
# snapshot.I is initialized to 0x3F in GenSnapshot
assert sna_data[0] == 0x3F

# Border color (snapshot.outFE & 7)
# GenSnapshot.outFE = 0x0F, so 0x0F & 7 = 7
assert sna_data[26] == 7

# Check SP
# GenSnapshot: SP = clear_addr - 3 = (0x8000 - 1) - 3 = 0x7FFC = 32764
# SnaEmitter: SP = snapshot.SP - 2 = 32764 - 2 = 32762 (0x7FFA)
# sna_data[23] = 0xFA, sna_data[24] = 0x7F
assert sna_data[23] == 0xFA
assert sna_data[24] == 0x7F

# Check PC patched on stack
# snapshot.PCL = 0x9E, snapshot.PCH = 0x1B
# Index in sna_data = 27 + 32762 - 16384 = 16405
assert sna_data[27 + 32762 - 16384] == 0x9E
assert sna_data[27 + 32762 - 16384 + 1] == 0x1B


def test_sna_emitter_emit(tmp_path):
output_file = tmp_path / "test.sna"
emitter = SnaEmitter()
emitter.emit(
output_filename=str(output_file),
program_name="test",
loader_bytes=None,
entry_point=0x8000,
program_bytes=b"\x00" * 100,
aux_bin_blocks=[],
aux_headless_bin_blocks=[],
)
assert output_file.exists()
assert len(output_file.read_bytes()) == 27 + 49152
63 changes: 63 additions & 0 deletions tests/outfmt/test_tap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# --------------------------------------------------------------------
# SPDX-License-Identifier: AGPL-3.0-or-later
# © Copyright 2008-2024 José Manuel Rodríguez de la Rosa and contributors.
# See the file CONTRIBUTORS.md for copyright details.
# See https://www.gnu.org/licenses/agpl-3.0.html for details.
# --------------------------------------------------------------------

from src.outfmt.tap import TAP


def test_tap_init():
tap = TAP()
assert tap.output == b""


def test_tap_standard_block():
tap = TAP()
tap.standard_block(b"\x01\x02")
# Length (3 -> [0x03, 0x00])
# Data: \x01\x02
# Checksum: 0x01 ^ 0x02 = 0x03
assert tap.output == b"\x03\x00\x01\x02\x03"


def test_tap_emit(tmp_path):
output_file = tmp_path / "test.tap"
tap = TAP()
program_bytes = b"\x00\x01"
tap.emit(
output_filename=str(output_file),
program_name="test",
loader_bytes=None,
entry_point=16384,
program_bytes=program_bytes,
aux_bin_blocks=[],
aux_headless_bin_blocks=[],
)
assert output_file.exists()
content = output_file.read_bytes()

# Header block
# 0x13 0x00 (Length)
# 0x00 (BLOCK_TYPE_HEADER)
# 0x03 (HEADER_TYPE_CODE)
# "test " (10 bytes)
# 0x02 0x00 (Length 2)
# 0x00 0x40 (Address 16384)
# 0x00 0x80 (32768)
# Checksum
expected_header_content = b"\x00\x03test \x02\x00\x00\x40\x00\x80"
checksum = 0
for b in expected_header_content:
checksum ^= b
expected_header = b"\x13\x00" + expected_header_content + bytes([checksum])
assert content.startswith(expected_header)

# Data block
# 0x04 0x00 (Length)
# 0xFF (BLOCK_TYPE_DATA)
# 0x00 0x01 (data)
# Checksum: 0xFF ^ 0x00 ^ 0x01 = 0xFE
expected_data = b"\x04\x00\xff\x00\x01\xfe"
assert expected_data in content
Loading
Loading