"""The desk's world and its clock, and a deterministic simulation driver. Shared by the capabilities host and the sans-io host, so that the two produce byte-identical output from the same script: patrons and books are tables, loans a dict, the clock virtual — a patron visit takes five days, `Sleep(d)` wakes at `now + d` — and machines run as events in day order, ties in submission order. """ from __future__ import annotations import heapq from collections.abc import Callable from dataclasses import dataclass, field VISIT_DAYS = 5 PATRONS = { "ada": ("Ada Lovelace", False), "grace": ("Grace Hopper", False), "linus": ("Linus Torvalds", True), } BOOKS = { "111": ("SICP", True), "222": ("TAPL", True), "333": ("Dragon Book", False), } SCRIPT = [ "checkout ada 111", "checkout grace 222", "status ada", "renew ada 111", "renew ada 111", "checkin ada 111", "checkout linus 111", "checkout ada 333", "status grace", "checkin grace 222", "checkin grace 222", "quit", ] @dataclass class World: day: int = 0 counter: int = 0 loans: dict[str, int] = field(default_factory=dict) extended: set[str] = field(default_factory=set) lines: list[str] = field(default_factory=lambda: list(SCRIPT)) out: list[str] = field(default_factory=list) def read_line(self) -> str: return self.lines.pop(0) if self.lines else "quit" def patron(self, id: str) -> tuple[str, bool, int]: name, blocked = PATRONS.get(id, (f"<{id}>", True)) on_loan = sum(1 for k in self.loans if k.startswith(f"loan/{id}/")) return name, blocked, on_loan def book(self, isbn: str) -> tuple[str, bool]: title, available = BOOKS.get(isbn, ("", False)) return title, available and not any(k.endswith(f"/{isbn}") for k in self.loans) def list(self, prefix: str) -> list[tuple[str, int]]: return sorted((k, v) for k, v in self.loans.items() if k.startswith(prefix)) def extend(self, key: str, days: int) -> bool: if key in self.extended or key not in self.loans: return False self.extended.add(key) self.loans[key] += days return True def count(self) -> int: self.counter += 1 return self.counter def write(self, who: str, text: str) -> None: self.out.append(f"[day {self.day:>2} {who:<8}] {text}") class Sim: """Events keyed by (day, seq): a step to run for a machine.""" def __init__(self, world: World) -> None: self.world = world self._q: list[tuple[int, int, Callable[[], None]]] = [] self._seq = 0 def at(self, day: int, step: Callable[[], None]) -> None: self._seq += 1 heapq.heappush(self._q, (day, self._seq, step)) def now(self, step: Callable[[], None]) -> None: self.at(self.world.day, step) def run(self) -> None: while self._q: day, _, step = heapq.heappop(self._q) self.world.day = max(self.world.day, day) step()