#!/usr/bin/env python3
# parseTrace.py - turn a raw VICE tracepoint log from testHarden.py into an ordered access list and
# a per-address / per-page census.
#
#   python3 parseTrace.py testLogs/harden.de00.A.cold.txt [more logs ...]
#
# The monitor announces a tracepoint hit as a header line and then disassembles the instruction that
# caused it, with the CPU registers after it executed:
#
#   #1 (Trace store de03)   40/$028,  50/$32
#   .C:e567  8D 03 DE    STA $DE03      - A:1E X:00 Y:24 SP:ed ..-.....  213365345
#
# For a store, A is the byte written; for a load, A is the byte read.  Note that VICE prints the
# register values with UPPERCASE hex digits and the program counter with lowercase ones, which is
# the sort of detail that silently drops every access whose value contains A-F if the pattern only
# allows one case.
import re
import sys

TRACE_HEAD = re.compile(r"#(\d+)\s+\(Trace\s+(\w+)\s+([0-9a-f]{4})\)")
DISASM = re.compile(r"\.C:([0-9a-f]{4})\s+((?:[0-9A-F]{2} )+)\s*(\S+)\s+(\S*)\s*-\s*A:([0-9A-Fa-f]{2})")


def parse(text):
    events = []
    lines = text.splitlines()
    for index, line in enumerate(lines):
        head = TRACE_HEAD.search(line)
        if not head:
            continue
        hit = DISASM.search(lines[index + 1]) if index + 1 < len(lines) else None
        events.append({"kind": head.group(2), "addr": int(head.group(3), 16),
                       "pc": int(hit.group(1), 16) if hit else None,
                       "op": hit.group(3) if hit else "?",
                       "a": int(hit.group(5), 16) if hit else None})
    return events


def census(events):
    counts = {}
    pages = {}
    for event in events:
        counts[(event["addr"], event["kind"])] = counts.get((event["addr"], event["kind"]), 0) + 1
        page = event["addr"] & 0xFF00
        pages[page] = pages.get(page, 0) + 1
    return counts, pages


def main():
    for path in sys.argv[1:]:
        events = parse(open(path, encoding="latin-1").read())
        counts, pages = census(events)
        print(f"=== {path}: {len(events)} accesses ===")
        unpaired = [e for e in events if e["a"] is None]
        if unpaired:
            print(f"    WARNING: {len(unpaired)} headers had no disassembly line after them")
        for addr, kind in sorted(counts):
            print(f"    ${addr:04X} {kind:5s} x {counts[(addr, kind)]}")
        for page in sorted(pages):
            print(f"    page ${page:04X}: {pages[page]} accesses")
        first = events[:14]
        print("    first accesses, in order:")
        for event in first:
            print(f"      {event['kind']:5s} ${event['addr']:04X}  {event['op']} at "
                  f"${event['pc']:04X}  A=${event['a']:02X}")
        ctrl = [e for e in events if e["kind"] == "store" and (e["addr"] & 0x00FF) == 3]
        print(f"    every control-register write, in order: "
              f"{' '.join('$%02X' % e['a'] for e in ctrl)}")


if __name__ == "__main__":
    main()
