117 lines
3.8 KiB
Python
117 lines
3.8 KiB
Python
"""Command decoding, PETSCII glyphs, and data-byte rendering (PLAN.md Phase 0)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from iecpoc import decode, device
|
|
from iecpoc.petscii import CAT_CONTROL, CAT_GRAPHIC, CAT_PRINTABLE, to_glyph
|
|
|
|
|
|
# --- command decoding -------------------------------------------------------
|
|
@pytest.mark.parametrize(
|
|
"value, mnemonic, primary, secondary",
|
|
[
|
|
(0x24, decode.LISTEN, 4, None), # LISTEN 4
|
|
(0x20, decode.LISTEN, 0, None), # LISTEN 0
|
|
(0x3E, decode.LISTEN, 30, None), # LISTEN 30 (top of range)
|
|
(0x3F, decode.UNLISTEN, None, None),
|
|
(0x48, decode.TALK, 8, None), # TALK 8
|
|
(0x5F, decode.UNTALK, None, None),
|
|
(0x60, decode.SECOND, None, 0), # DATA/reopen SA=0
|
|
(0x6F, decode.SECOND, None, 15),
|
|
(0xE0, decode.CLOSE, None, 0),
|
|
(0xEF, decode.CLOSE, None, 15),
|
|
(0xF0, decode.OPEN, None, 0),
|
|
(0xFF, decode.OPEN, None, 15),
|
|
],
|
|
)
|
|
def test_decode_command(value, mnemonic, primary, secondary):
|
|
cmd = decode.decode_command(value)
|
|
assert cmd.value == value
|
|
assert cmd.mnemonic == mnemonic
|
|
assert cmd.primary == primary
|
|
assert cmd.secondary == secondary
|
|
|
|
|
|
def test_targets_address():
|
|
assert decode.decode_command(0x24).targets(4) is True
|
|
assert decode.decode_command(0x24).targets(8) is False
|
|
# UNLISTEN has no primary address, targets nobody specifically
|
|
assert decode.decode_command(0x3F).targets(4) is False
|
|
|
|
|
|
def test_describe():
|
|
assert decode.decode_command(0x24).describe() == "LISTEN 4"
|
|
assert decode.decode_command(0x3F).describe() == "UNLISTEN"
|
|
assert decode.decode_command(0xF0).describe() == "OPEN SA=0"
|
|
assert decode.decode_command(0xE0).describe() == "CLOSE SA=0"
|
|
|
|
|
|
def test_decode_command_range_check():
|
|
with pytest.raises(ValueError):
|
|
decode.decode_command(0x100)
|
|
|
|
|
|
# --- PETSCII ----------------------------------------------------------------
|
|
def test_petscii_letters_and_digits():
|
|
for ch in "HELLO WORLD0123456789":
|
|
g = to_glyph(ord(ch))
|
|
assert g.category == CAT_PRINTABLE
|
|
assert g.text == ch
|
|
|
|
|
|
def test_petscii_commodore_glyphs():
|
|
assert to_glyph(0x5C).text == "£"
|
|
assert to_glyph(0x5E).text == "↑"
|
|
assert to_glyph(0x5F).text == "←"
|
|
|
|
|
|
def test_petscii_control_codes():
|
|
assert to_glyph(0x0D) == to_glyph(0x0D)
|
|
cr = to_glyph(0x0D)
|
|
assert cr.category == CAT_CONTROL
|
|
assert cr.text == "<CR>"
|
|
assert to_glyph(0x12).text == "<RVS-ON>"
|
|
# unknown control code falls back to the hex form
|
|
assert to_glyph(0x07).category == CAT_CONTROL
|
|
assert to_glyph(0x07).text == "<$07>"
|
|
|
|
|
|
def test_petscii_graphics_render_as_dot():
|
|
g = to_glyph(0x70) # graphics range in the upper-case set
|
|
assert g.category == CAT_GRAPHIC
|
|
assert g.text == "."
|
|
assert not g.printable
|
|
|
|
|
|
def test_petscii_range_check():
|
|
with pytest.raises(ValueError):
|
|
to_glyph(256)
|
|
|
|
|
|
# --- data-byte rendering ----------------------------------------------------
|
|
def test_describe_data_printable():
|
|
assert decode.describe_data(0x48) == "$48 'H'"
|
|
|
|
|
|
def test_describe_data_control():
|
|
assert decode.describe_data(0x0D) == "$0D <CR>"
|
|
|
|
|
|
# --- event description ------------------------------------------------------
|
|
def test_describe_event_atn_released_addressed():
|
|
rec = device.IecRecord(device.KIND_EVENT, device.EV_ATN_RELEASED,
|
|
device.FLAG_ADDRESSED, 0)
|
|
assert decode.describe_event(rec) == "released -> LISTENER"
|
|
|
|
|
|
def test_describe_event_atn_released_not_addressed():
|
|
rec = device.IecRecord(device.KIND_EVENT, device.EV_ATN_RELEASED, 0, 0)
|
|
assert decode.describe_event(rec) == "released -> not addressed -> IDLE"
|
|
|
|
|
|
def test_describe_event_idle():
|
|
rec = device.IecRecord(device.KIND_EVENT, device.EV_IDLE, 0, 0)
|
|
assert decode.describe_event(rec) == "waiting for ATN"
|