96 lines
2.7 KiB
Python
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)
|