2026-06-18 22:25:42 +02:00

96 lines
2.7 KiB
Python

"""PETSCII -> display-string conversion for the debug trace.
Data bytes the C64 sends to a printer are PETSCII, not ASCII. For the upper-
case/graphics character set (the C64 power-on default) the printable
sub-ranges line up with ASCII for letters, digits and most punctuation, with a
handful of Commodore-specific glyphs (``£ ↑ ←``). Control codes (RETURN, RVS,
colour changes, cursor moves) are rendered with short symbolic names so the
trace stays readable.
This is deliberately a *display* mapping for logging, not a font renderer. The
goal (PLAN.md §8) is: printable PETSCII -> its glyph; control code -> ``<NAME>``;
anything else (graphics characters) -> ``.``.
"""
from __future__ import annotations
from dataclasses import dataclass
# Symbolic names for the C64 control codes that actually turn up in a printer
# byte stream. Anything not in here renders as ``<$xx>``.
_CONTROL_NAMES = {
0x03: "STOP",
0x05: "WHT",
0x08: "SHIFT-DIS",
0x09: "SHIFT-EN",
0x0A: "LF",
0x0D: "CR",
0x0E: "LOWER",
0x11: "DOWN",
0x12: "RVS-ON",
0x13: "HOME",
0x14: "DEL",
0x1C: "RED",
0x1D: "RIGHT",
0x1E: "GRN",
0x1F: "BLU",
0x81: "ORG",
0x8D: "SHIFT-CR",
0x90: "BLK",
0x91: "UP",
0x92: "RVS-OFF",
0x93: "CLR",
0x94: "INST",
0x95: "BRN",
0x99: "GRY",
0x9D: "LEFT",
0x9E: "YEL",
0x9F: "CYN",
}
# Printable glyphs that differ from plain ASCII in the upper-case/graphics set.
_GLYPH_OVERRIDES = {
0x5C: "£", # £
0x5E: "", # ↑
0x5F: "", # ←
0xA0: " ", # shifted space
}
CAT_PRINTABLE = "printable"
CAT_CONTROL = "control"
CAT_GRAPHIC = "graphic"
@dataclass(frozen=True)
class Glyph:
"""The rendered form of one PETSCII byte."""
text: str # what to show in the trace
category: str # CAT_PRINTABLE | CAT_CONTROL | CAT_GRAPHIC
@property
def printable(self) -> bool:
return self.category == CAT_PRINTABLE
def to_glyph(byte: int) -> Glyph:
"""Map one PETSCII byte to a :class:`Glyph` for the debug trace."""
if not 0 <= byte <= 0xFF:
raise ValueError(f"byte out of range: {byte!r}")
if byte in _GLYPH_OVERRIDES:
return Glyph(_GLYPH_OVERRIDES[byte], CAT_PRINTABLE)
# Space through '_' (minus the overrides above) matches ASCII directly.
if 0x20 <= byte <= 0x5F:
return Glyph(chr(byte), CAT_PRINTABLE)
# Control ranges: $00-$1F and $80-$9F.
if byte <= 0x1F or 0x80 <= byte <= 0x9F:
name = _CONTROL_NAMES.get(byte, f"${byte:02X}")
return Glyph(f"<{name}>", CAT_CONTROL)
# Everything else ($60-$7F, $A0-$FF) is graphics / shifted glyphs in the
# upper-case set -- not meaningful as printer text, render as '.'.
return Glyph(".", CAT_GRAPHIC)