Phase 4: Ereignisschleife und klassische Traps

This commit is contained in:
2026-09-04 15:56:47 +02:00
parent 060674b466
commit 5cf5a6582c
40 changed files with 4024 additions and 176 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-04

View File

@@ -0,0 +1,228 @@
# Design — Ereignisschleife und klassische Ereignis-Traps
## Context
Siehe proposal.md — Why. Technisch maßgeblich ist der Bestand aus Phase 3:
```
crossterm TerminalHost RtState Vm
--------- ------------ ------- --
Key --> next_event() --> Ereignis::Taste --> tasten[] <-- INKEY$
Resize --> next_event() --> Ereignis::Groesse -> screen.resize()
Ctrl+C --> next_event() --> Ereignis::Abbruch -> abbruch
Maus --> x verworfen (`Ok(_) => {}` im TerminalHost) <-- fehlt
Zeit --> x keine Quelle <-- fehlt
Zustellpunkte heute: Instr::Stmt (screen dirty oder alle 1024 Ticks),
vor blockierender Eingabe.
```
Zwei Randbedingungen schneiden den Entwurf zu:
1. **Builtins sehen die VM nicht.** Ihre Signatur ist
`(&mut RtState, &mut dyn Host, &mut [Value])`. `DOEVENTS` ist heute
ein Builtin und kann deshalb prinzipiell keine Prozedur des laufenden
Programms ausführen.
2. **`Vm::run()` ist nicht wiedereintrittsfähig.** Es gibt keinen
Einstiegspunkt „führe Ziel X aus und komm zurück".
## Goals / Non-Goals
**Goals:**
- Eine Zustellung, die den unterbrochenen Zustand nicht verliert und
ohne aktiven Trap nichts kostet.
- Zeitgesteuerte Programme im Korpus ohne echte Wartezeit und ohne
Flackern zwischen zwei Läufen.
- Ein Ereignismodell, das die Forms-Ereignisse des nächsten Changes
trägt, ohne dann umgebaut zu werden.
**Non-Goals:**
- Nebenläufigkeit. Es gibt weiterhin genau einen Ausführungsstrang; die
Zeitquelle ist kein Thread, sondern eine Abfrage am Zustellpunkt.
- Priorisierbare oder verschachtelte Handler.
## Decisions
### D1 — Zustellung ist ein Vorgang der VM, kein Builtin
Die Prüfung „ist etwas fällig?" sitzt in der bestehenden Tick-Prüfung an
`Instr::Stmt`. Ist etwas fällig, springt die VM das Trap-Ziel an, als
hätte das Programm dort ein `GOSUB` ausgeführt. `DOEVENTS` und `SLEEP`
werden zu Anweisungen, die denselben Weg auslösen, statt zu Builtins,
die ihn nicht auslösen können.
| Alternative | Warum nicht |
|---|---|
| `&mut Vm` an alle Builtins durchreichen | Signaturänderung an ~150 Builtins; echte Rekursion in `run()` mit unbegrenzter Stapeltiefe |
| Trampolin: Builtin liefert „bitte Ziel X rufen" zurück | Trägt die klassischen Traps, aber modales `SHOW` des nächsten Changes wird zur Zustandsmaschine — der Umbau käme dann doch |
### D2 — Der Handler läuft in einem eigenen Frame, es wird nicht abgewickelt
`ON ERROR` wickelt die Frame-Kette bis zum Handler-Frame ab. Für einen
Trap ist das falsch: das Programm muss danach weiterlaufen. Die
Zustellung **legt** deshalb einen Frame auf den Modulrumpf **auf**, mit
einer Rückmarke auf die unterbrochene Stelle; `RETURN` nimmt ihn wieder
herunter.
```
vor der Zustellung waehrend des Handlers nach RETURN
------------------ --------------------- -----------
[ SUB Rechne ] <- top [ Trap-Frame @Marke ] [ SUB Rechne ]
[ Modulrumpf ] [ SUB Rechne ] [ Modulrumpf ]
[ Modulrumpf ]
```
Der Trap-Frame teilt die Modulvariablen (sie sind Globals), hat aber
einen eigenen `GOSUB`-Stapel — damit kann ein Handler selbst `GOSUB`
verwenden, ohne den unterbrochenen Stapel zu berühren.
### D3 — Die Zeit kommt vom Host
Der `Host`-Trait bekommt eine monotone Zeitabfrage (**BREAKING**). Die
Laufzeit rechnet daraus die Fälligkeit der Zeit-Traps; der Host weiß
nichts von Traps.
Grund ist die Prüfbarkeit: der `CaptureHost` stellt die Zeit selbst und
ein Korpusprogramm mit `ON TIMER(5)` läuft in Mikrosekunden und immer
gleich. Ein direkter Uhrzugriff in der Laufzeit hätte beides verhindert
und wäre zudem die zweite Uhr neben der von `TIMER`/`TIME$` — die
bleibt, wo sie ist, denn sie liefert Ortszeit, keine monotone Dauer.
### D4 — Maskierung als Tabelle, Prüfung über das vorhandene Flagwort
Je Quelle ein Zustand (`ON`/`OFF`/`STOP`) und höchstens ein anstehendes
Ereignis. Belegt aus der Original-Hilfe (`umfang-und-form.md`): der
Eintritt in den Handler führt selbsttätig `STOP` aus, das `RETURN`
selbsttätig `ON` — außer im Handler stand ein ausdrückliches `OFF`; und
`OFF` verwirft auch ein unter `STOP` gemerktes Ereignis. `EVENT` ist
keine dritte Zustandsebene, sondern eine Klammer mit nur `ON` und `OFF`. Die Tick-Prüfung bekommt ein weiteres Bit im vorhandenen
Flagwort neben `F_STEP`/`F_BREAK`/`F_POLL`. Ohne definierten Trap ist
das Bit aus, und die heiße Schleife prüft weiterhin genau ein Wort —
das trägt die Zusicherung „ohne aktiven Trap kein messbarer Aufwand"
aus der Spec.
### D5 — Getrappte Tasten werden erst am Zustellpunkt aussortiert
Die Tastenschlange bleibt, wie sie ist. Am Zustellpunkt vergleicht die
Laufzeit den Kopf der Schlange mit den aktiven `KEY`-Traps und nimmt die
Taste nur dann heraus. Damit wirkt ein `KEY(1) OFF` sofort und auch auf
Tasten, die schon in der Schlange liegen — Filtern beim Einsortieren
hätte den Zustand zum Zeitpunkt des Tastendrucks eingefroren.
### D6 — Virtuelle Zeit im Korpus über eine Kopfzeilen-Direktive
Der Harness kennt bereits `' tb-screen:` und `' tb-tz:`. Dazu kommt
`' tb-clock: <n>ms/Zustellpunkt`: die virtuelle Uhr des `CaptureHost`
rückt an jedem Zustellpunkt um `n` Millisekunden vor. Ein Programm mit
`ON TIMER(1)` und `' tb-clock: 100ms/Zustellpunkt` feuert damit
reproduzierbar nach zehn Zustellpunkten. Fehlt die Direktive und nutzt
das Programm Zeit-Traps, bricht der Harness ab, statt auf die Systemuhr
auszuweichen.
### D7 — Maus: Quelle jetzt, Auswertung mit den Steuerelementen
Die Maus kommt als Quelle in diesem Change herein, weil die
Eingangsaufgabe der Phase die Terminal-Events in die Warteschlange
führt. Zwei Festlegungen fallen dabei an:
- **Koordinaten in Zellen, 1-basiert.** `crossterm` zählt ab 0, der
Bildschirm des Dialekts ab 1 (`LOCATE`, `CSRLIN`, `SCREEN(z, s)`).
Umgerechnet wird an der Stelle, an der die Ereignisse entstehen — in
der Darstellungsschicht —, damit es in der Laufzeit nur eine Zählweise
gibt. Die Forms-Referenz nennt Mauskoordinaten SINGLE, aber in Zellen;
die Umrechnung in Formularkoordinaten macht später das Objektmodell.
- **Ohne Verbraucher verworfen.** Bis die Steuerelemente da sind, nimmt
niemand ein Mausereignis an. Es am Zustellpunkt zu verwerfen ist die
einzige Variante, die keine unbegrenzt wachsende Warteschlange
hinterlässt; Bewegungsereignisse kommen bei gedrückter Taste in hoher
Frequenz.
### D8 — `SIGNAL` liegt auf Betriebssystemsignalen
Entscheidung des Projektinhabers vom 2026-09-04, Beleg und Begründung in
`umfang-und-form.md`. Abgebildet wird nur, was auf **allen**
Zielplattformen besteht:
| `n%` | Signal | Bemerkung |
|---|---|---|
| 1 | `SIGINT` | überall zustellbar |
| 2 | `SIGTERM` | unter Windows von außen nicht zustellbar (Abweichung) |
Der Signalhandler tut nichts weiter, als eine Marke zu setzen; der Host
liest sie beim nächsten Abholen und macht ein Ereignis daraus. Damit
läuft kein Programmcode im Signalkontext, und die Zustellung folgt
denselben Punkten wie jede andere Quelle. Liegen beide Marken an, hat
`SIGINT` Vorrang.
Ein Codepfad für alle drei Plattformen: `signal-hook` gilt sein
`flag`-Modul nicht nach Plattform ab und bildet unter Windows auf das
`signal` der C-Laufzeit ab. Die Abhängigkeit liegt ohnehin im Baum —
`crossterm` nutzt sie unter Unix für `SIGWINCH` —, sie wird für `tb-ui`
nur ausdrücklich deklariert, nicht neu hereingeholt. Damit entfällt der
sonst nötige zweite Pfad über den Konsolen-Steuerhandler von Windows.
### D9 — Umfang und Form aus den Einzelseiten der Original-Hilfe
Befund aus `phase-3-isam`: die Themenliste klärt den *Umfang*, die
Einzelseiten klären die *Form*. Für diesen Change heißt das: die
Wertebereiche von `ON KEY(n)`, die Reihenfolge gleichzeitig fälliger
Ereignisse und die Frage, ob Trapping in Prozeduren ruht, werden vor der
Implementierung aus den Einzelseiten belegt (erste Aufgabe in tasks.md),
nicht aus der Erinnerung nachgebaut. Ergibt die Hilfe nichts, gilt eine
dokumentierte Festlegung, die in der Sprachreferenz als Abweichung
steht.
### D10 — Schleifen bekommen eine Anweisungsgrenze im Rücksprung
Befund bei der Umsetzung von D1: der Codegenerator übersetzte eine
Schleife **ohne Rumpf** in reine Sprünge — in `FOR i = 1 TO 10000: NEXT`
gab es keine Anweisungsgrenze und damit keinen Zustellpunkt. Das trifft
alle vier Schleifenformen und betraf auch Breakpoints und den Abbruch,
war also schon vor diesem Change eine Lücke.
Der Rücksprung führt jetzt in jedem Fall über eine Grenze. Bei
`DO`/`WHILE` zeigt er auf die ohnehin vorhandene Grenze des
Schleifenkopfs — das kostet keine Instruktion. Nur `FOR` braucht eine
eigene, weil vor dem Schleifentest die Initialisierung steht.
## Risks / Trade-offs
- **Der Trap-Frame sieht einen halbfertigen Zustand** → Zustellung nur
an Anweisungsgrenzen; die Liste der Zustellpunkte ist in der Spec
abschließend und wird nicht „für den Komfort" erweitert.
- **Ereignisse feuern schneller, als der Handler zurückkehrt** →
höchstens ein anstehendes Ereignis je Quelle, und die eigene Quelle
ist während des Handlers gestoppt. Ein Zeit-Trap kann damit Fristen
überspringen; das entspricht dem Vorbild.
- **Tick-Kosten** → `benches/vm.rs` misst vorher und nachher; die
Zusicherung aus der Spec ist ein Messwert, keine Behauptung. Die
zusätzliche Grenze je `FOR`-Durchlauf (D10) geht in dieselbe Messung
ein.
- **`SIGNAL(2)` ist unter Windows wirkungslos** — es gibt dort kein
`kill` → als Abweichung dokumentiert, statt ein Ersatzereignis zu
erfinden, das es im Vorbild nicht gab.
- **`SIGINT` kollidiert mit dem Abbruch** → im Rohmodus liefert das
Terminal Strg+C als Taste, nicht als Signal; ein von außen gesendetes
`SIGINT` geht an den Trap, wenn einer aktiv ist, sonst bleibt es beim
Abbruch. Dieselbe Regel wie bei getrappten Tasten.
- **`Host`-Trait ist BREAKING** → betrifft `TerminalHost`, `CaptureHost`
und den Test-Host in `tb-cli`; alle drei liegen im Repo, kein externer
Verbraucher.
- **Mausereignisse ohne Verbraucher könnten stumm falsch sein** — ein
Fehler im Koordinatenmodell fiele erst im nächsten Change auf →
Unit-Tests der Umrechnung an den Rändern (Zelle 1,1 und die letzte
Zelle) und nach einer Größenänderung, nicht erst über Steuerelemente.
## Open Questions
- Ruht das Trapping in Prozeduren eines *anderen* Moduls? Mehrere Module
gibt es erst mit der Projektverwaltung in Phase 5; die Antwort ändert
hier weder Spec noch Aufgaben.
- Ob die Zeitquelle später auch Forms-`Timer`-Controls speist oder diese
eine eigene Fälligkeitsliste bekommen, entscheidet der Change zum
Objektmodell.
- Ob Mausbewegungen ohne gedrückte Taste zusammengefasst werden müssen
(`MouseMove` kommt sehr häufig), zeigt sich erst mit dem ersten
Verbraucher; das Ereignismodell bleibt davon unberührt.

View File

@@ -0,0 +1,29 @@
# Messung: Kosten der Ereigniszustellung (Aufgabe 8.1)
`cargo bench -p tb-vm --bench vm`, Release, dieselbe Maschine. „Vorher" ist
der Stand von `060674b` in einem eigenen Worktree, „nachher" der Median aus
drei Läufen dieses Changes.
| Last | vorher | nachher | Abweichung |
|---|---|---|---|
| INTEGER-Schleife (10 Mio) | 898 ms | 901 ms | +0,3 % |
| DOUBLE-Schleife (5 Mio) | 419 ms | 429 ms | +2,4 % |
| SUB-Aufrufe (1 Mio, BYREF) | 94 ms | 96 ms | +2,1 % |
| String-Funktionen (200k) | 86 ms | 83 ms | 3,5 % |
**Deutung.** Zwei Änderungen wirken hier zusammen, und nur die zweite kostet:
1. **Die Zustellprüfung** an der Anweisungsgrenze ist ein einzelner
Bool-Test (`traps.aktiv()`, ein gepflegtes Feld). Ohne definierten Trap
fällt sie sofort durch; die Uhr wird gar nicht erst gelesen. Das ist im
Rauschen nicht auffindbar.
2. **Die zusätzliche Anweisungsgrenze je `FOR`-Durchlauf** (design.md, D10)
ist eine Instruktion mehr pro Iteration. Sie erklärt die ~2 % auf den
schleifenlastigen Lasten und ist bewusst bezahlt: ohne sie ist
`FOR i = 1 TO n: NEXT` für Ereignisse, Breakpoints und Abbruch taub.
`DO`/`WHILE` zahlen nichts, dort zeigt der Rücksprung auf eine ohnehin
vorhandene Grenze.
Die Streuung zwischen den Läufen liegt bei ±1,5 % (String-Last sogar
schneller als vorher), die 2 % sind also am Rand der Auflösung dieses
Harness — als obere Schranke aber belastbar.

View File

@@ -0,0 +1,125 @@
# Phase 4 (Eingang) — Ereignisschleife und klassische Ereignis-Traps
## Why
Phase 4 beginnt laut PLAN mit der Ereignisschleifen-Architektur. Diese
Architektur lässt sich an den **klassischen Ereignis-Traps** vollständig
bauen und beweisen, ohne eine Zeile Forms-Objektmodell: Sie brauchen
dieselbe Warteschlange, dieselben Zustellpunkte, dieselbe Maskierung und
denselben Rückruf in die VM, aber keine Formulare, keine Steuerelemente
und kein `.FRM`. Umgekehrt müsste der Entwurf der Schleife mitten im
Objektmodell blind bezahlt werden.
Phase 3 hat die halbe Maschinerie bereits nebenbei gebaut: die
`Ereignis`-Warteschlange, das Einsortieren in `RtState::pump`, die
Zustellpunkte an Anweisungsgrenzen und vor blockierender Eingabe sowie
den `CaptureHost` für Tests ohne Terminal. Es fehlen drei Dinge: eine
**Zeitquelle**, die **Maskierung** einzelner Ereignisquellen und vor
allem der **Rückruf in die VM** — heute gibt es keinen Weg, aus der
Laufzeit heraus eine Prozedur oder ein `GOSUB`-Ziel des laufenden
Programms auszuführen. `DOEVENTS()` liefert deshalb konstant 0.
Zugleich sind die zehn Ereignis-Elemente die letzte Lücke des Inventars
außerhalb von Forms: von den 15 offenen Einträgen entfallen zehn auf
diese Gruppe, drei auf die Forms-Dialoge und zwei auf Phase 5.
## What Changes
- **Ereignisquellen und Warteschlange**: Das Ereignismodell der Laufzeit
wird um eine **Zeitquelle** erweitert. Die Zeit kommt vom Host, nicht
aus einem direkten Uhrzugriff der Laufzeit — nur so kann der
`CaptureHost` virtuelle Zeit stellen und ein Korpusprogramm mit
`ON TIMER` deterministisch prüfen. **BREAKING** für den `Host`-Trait.
- **Mausereignisse als vierte Quelle**: Der `TerminalHost` verwirft die
Mausereignisse von `crossterm` heute. Sie werden aufgenommen und in
Zellenkoordinaten geführt — Tastendruck, Loslassen und Bewegung mit
Taste und Umschaltzustand. Die Eingangsaufgabe der Phase lautet
„Terminal-Events → Event-Queue → VM-Ticks"; die Maus ist ein
Terminal-Event und damit Teil der Schleife, auch wenn sie erst die
Steuerelemente auswerten.
- **Rückruf in die VM**: Ereigniszustellung wird ein Vorgang der VM, kein
Builtin. Ein zugestelltes Ereignis führt das `GOSUB`-Ziel seines Traps
aus und kehrt danach an die unterbrochene Stelle zurück. Damit
bekommt auch `DOEVENTS()` seine Wirkung.
- **Zustellpunkte**: Die vorhandenen Punkte (Anweisungsgrenze,
blockierende Eingabe) werden zu einer geschlossenen, dokumentierten
Liste ausgebaut: Anweisungsgrenze, `DOEVENTS`, `SLEEP` (wird durch ein
zugestelltes Ereignis beendet) und blockierende Eingabe. Zwischen
Zustellpunkten unterbricht nichts das Programm.
- **Klassische Ereignis-Traps** (Sprachreferenz §8, **10
Inventareinträge**): `ON TIMER(n&)`, `ON KEY(n%)`, `ON UEVENT` und
`ON SIGNAL(n%)` je mit `GOSUB label`; die Steueranweisungen `TIMER`,
`KEY(n)`, `UEVENT` und `SIGNAL(n)` je mit `ON`/`OFF`/`STOP` sowie die
Klammer `EVENT` mit `ON`/`OFF`; dazu `SetUEvent`. Wertebereiche nach
Beleg: `KEY` 0, 125, 3031; `TIMER` 186 400 Sekunden; `SIGNAL` 12.
Ihr Status im Inventar wechselt
auf `implementiert`.
- **Maskierung mit drei Zuständen**: `ON` stellt zu, `OFF` verwirft —
auch ein unter `STOP` bereits gemerktes Ereignis —, `STOP` merkt eines
und stellt es beim nächsten `ON` zu. Der Eintritt in einen Handler
führt selbsttätig `STOP` seiner Quelle aus, das `RETURN` selbsttätig
`ON`, außer im Handler stand ein ausdrückliches `OFF`.
- **`SIGNAL` auf Betriebssystemsignale** (Entscheidung 2026-09-04): Die
Quelle des Vorbilds war OS/2-gebunden (Prozessflags über
`DOSFLAGPROCESS`) und existiert nicht. Abgebildet wird die minimale
Menge, die auf allen Zielplattformen besteht: `SIGNAL(1)` auf `SIGINT`,
`SIGNAL(2)` auf `SIGTERM`. Signale erreichen die Laufzeit über den
Host, nicht im Signalkontext.
- **Getrappte Tasten verlassen den Eingabestrom**: Eine Taste, für die
ein aktiver `ON KEY`-Trap besteht, wird zugestellt und MUST NOT
zusätzlich bei `INKEY$`, `INPUT` oder `INPUT$` erscheinen.
- **Namentliche Abweisung im Frontend**: Die zehn Elemente erhalten
Grammatik und Signaturen. Heute rutscht `ON TIMER(5) GOSUB marke`
stillschweigend in das berechnete `ON ausdruck GOSUB` — genau der
Fall, den der Guiding Principle als Bug definiert.
**Non-Goals:**
- Die **Auswertung** von Mausereignissen durch Steuerelemente
(Trefferprüfung, Fokuswechsel, Ziehen) — sie setzt das Objektmodell
voraus und gehört zum Change der Steuerelemente. Quelle,
Warteschlange und Koordinatenmodell entstehen hier.
- Das Forms-Objektmodell, Steuerelemente, `.FRM` und die Dialoge
(`MSGBOX`, `INPUTBOX$`) — eigene Changes derselben Phase.
- `COM`, `PEN`, `PLAY`, `STRIG` bleiben Non-Feature (Fehler 73).
## Capabilities
### New Capabilities
- `ereignissteuerung`: Ereignisquellen (Tastatur, Zeit,
benutzerdefiniert), Warteschlange und Reihenfolge, Zustellpunkte,
Maskierung mit `ON`/`OFF`/`STOP` je Quelle und global, Zustellung als
`GOSUB` in das laufende Programm, Zusammenspiel mit Fehlerbehandlung
und blockierender Eingabe.
### Modified Capabilities
- `sprach-frontend`: Grammatik und Signaturprüfung SHALL die
Trap-Anweisungen und `SetUEvent` umfassen; `ON <quelle>(n) GOSUB`
MUST NOT als berechnetes `ON ausdruck GOSUB` behandelt werden.
- `vm-ausfuehrung`: Die VM SHALL an Anweisungsgrenzen anstehende
Ereignisse zustellen und dafür das `GOSUB`-Ziel des Traps ausführen,
ohne den unterbrochenen Zustand zu verlieren.
- `kompat-testkorpus`: Korpusabdeckung der Ereignis-Traps mit
deterministischer Zeit.
## Impact
- `crates/tb-runtime`: `Ereignis` und der `Host`-Trait um die Zeitquelle
erweitert (**BREAKING**); Trap-Zustände und Warteschlange in `RtState`;
`INKEY$`-Puffer filtert getrappte Tasten; `SLEEP` endet bei Zustellung.
- `crates/tb-vm`: Zustellung als Vorgang der Ausführung (neue
Instruktion und Frame-Behandlung); `DOEVENTS` erhält Wirkung.
- `crates/tb-frontend`: Grammatik und Signaturen der zehn Elemente;
`Unsupported`-Absenkung von `SETUEVENT` entfällt.
- `crates/tb-ui`: `TerminalHost` liefert die Zeit, reicht die
Mausereignisse von `crossterm` durch, statt sie zu verwerfen, und
meldet Betriebssystemsignale als Ereignis.
- `tests/compat`: neue Korpusprogramme für Zeit-, Tasten- und
UEvent-Traps sowie für `ON`/`OFF`/`STOP`.
- `docs/`: `sprachreferenz.md` §8 ausformuliert; `inventar.md` setzt die
zehn Einträge auf `implementiert`; `bibliothek.md` um die Elemente
erweitert.
- PLAN.md: die Eingangsaufgabe von Phase 4 und der Punkt „Klassische
Ereignis-Traps" sind damit erledigt.

View File

@@ -0,0 +1,208 @@
## Purpose
Die Ereignissteuerung bestimmt, woher Ereignisse kommen, wann ein
laufendes Programm sie zu sehen bekommt und wie es darauf reagiert:
Ereignisquellen (Tastatur, Maus, Zeit, benutzerdefiniert) und ihre
Warteschlange, die abschließende Liste der
Zustellpunkte, die Maskierung je Quelle und global, sowie die Zustellung
als `GOSUB` in das laufende Programm. Sie ist die gemeinsame Grundlage
der klassischen Ereignis-Traps und späterer Forms-Ereignisse.
## ADDED Requirements
### Requirement: Ereignisquellen mit Zeit vom Host
Die Laufzeit SHALL Ereignisse aus vier Quellen führen: Tastatur, Maus,
Zeit und benutzerdefinierte Ereignisse. Die Zeit SHALL vom Host bezogen
werden;
die Laufzeit MUST NOT für die Ereigniszustellung selbst auf die
Systemuhr zugreifen. Ein Host ohne Terminal MUST die Zeit frei stellen
können, sodass zeitgesteuerte Programme ohne Wartezeit und mit
reproduzierbarem Ergebnis prüfbar sind.
#### Scenario: Zeitgesteuerter Trap ohne echte Wartezeit
- **WHEN** ein Testhost die Zeit um 5 Sekunden vorstellt und ein Programm `ON TIMER(5) GOSUB Marke` mit `TIMER ON` aktiv hat
- **THEN** wird der Trap zugestellt, ohne dass der Test tatsächlich wartet
#### Scenario: Wiederholter Lauf liefert dasselbe Ergebnis
- **WHEN** dasselbe zeitgesteuerte Korpusprogramm zweimal mit demselben Zeitverlauf ausgeführt wird
- **THEN** ist die Ausgabe beide Male identisch
### Requirement: Mausereignisse in Zellenkoordinaten
Die Darstellungsschicht SHALL Mausereignisse der Ausführungsumgebung an
die Laufzeit weitergeben: Drücken, Loslassen und Bewegung, jeweils mit
gedrückter Taste, Umschaltzustand und Position. Die Position SHALL in
Zellen des Textbildschirms angegeben werden, gezählt wie bei `LOCATE`,
also 1-basiert. Ereignisse außerhalb der aktuellen Bildschirmfläche MUST
verworfen werden. Die Reihenfolge zwischen Maus- und Tastenereignissen
SHALL der Reihenfolge ihres Eintreffens entsprechen. Solange kein
Verbraucher ein Mausereignis annimmt, MUST es am Zustellpunkt verworfen
werden, damit die Warteschlange nicht unbegrenzt wächst.
#### Scenario: Position als Zelle
- **WHEN** in der linken oberen Ecke der Darstellungsfläche die linke Maustaste gedrückt wird
- **THEN** trägt das Ereignis Zeile 1 und Spalte 1
#### Scenario: Reihenfolge bleibt erhalten
- **WHEN** eine Taste gedrückt und danach die Maus geklickt wird
- **THEN** liefert die Warteschlange erst das Tasten-, dann das Mausereignis
#### Scenario: Ereignis außerhalb der Fläche
- **WHEN** ein Mausereignis eine Position außerhalb der aktuellen Bildschirmgröße nennt
- **THEN** wird es verworfen und erreicht die Warteschlange nicht
#### Scenario: Kein Verbraucher, keine Anhäufung
- **WHEN** ein Programm ohne Verbraucher für Mausereignisse läuft und die Maus dauerhaft bewegt wird
- **THEN** wächst die Warteschlange nicht über ihre Grenze und das Programm läuft unverändert weiter
### Requirement: Abschließende Liste der Zustellpunkte
Ereignisse SHALL ausschließlich an folgenden Punkten zugestellt werden
— das Vorbild prüft die Ereignismarke „before executing the next
statement":
an einer Anweisungsgrenze, bei `DOEVENTS`, während `SLEEP` und vor einer
blockierenden Eingabe. Zwischen zwei Zustellpunkten MUST NOT ein
Trap-Handler beginnen; insbesondere MUST NOT eine Anweisung in ihrer
Mitte unterbrochen werden. Ein zugestelltes Ereignis SHALL ein laufendes
`SLEEP` beenden.
#### Scenario: Keine Zustellung innerhalb einer Anweisung
- **WHEN** ein Ereignis fällig wird, während eine mehrteilige Anweisung ausgewertet wird
- **THEN** läuft die Anweisung zu Ende und der Handler beginnt erst an der folgenden Anweisungsgrenze
#### Scenario: SLEEP endet durch Ereignis
- **WHEN** ein Programm `SLEEP 60` ausführt und nach 2 Sekunden ein aktiver Trap fällig wird
- **THEN** wird der Handler ausgeführt und `SLEEP` kehrt danach zurück, ohne die vollen 60 Sekunden abzuwarten
#### Scenario: DOEVENTS stellt zu
- **WHEN** ein anstehendes Ereignis vorliegt und `DOEVENTS` ausgewertet wird
- **THEN** läuft der zugehörige Handler, bevor `DOEVENTS` einen Wert liefert
### Requirement: Maskierung je Quelle mit drei Zuständen
Jede Ereignisquelle SHALL drei Zustände kennen. `ON` stellt Ereignisse
zu. `OFF` verwirft sie; ein während `OFF` eingetretenes Ereignis MUST
NOT nachträglich zugestellt werden, und ein zuvor unter `STOP` gemerktes
Ereignis MUST von `OFF` verworfen werden. `STOP` merkt höchstens ein
anstehendes Ereignis und stellt es beim nächsten `ON` zu. Ohne
vorangehende `ON …`-Trap-Definition MUST eine Steueranweisung wirkungslos
bleiben, aber keinen Fehler auslösen.
#### Scenario: OFF verwirft
- **WHEN** `TIMER OFF` gilt, die Frist verstreicht und danach `TIMER ON` ausgeführt wird
- **THEN** wird kein Handler ausgeführt
#### Scenario: STOP merkt genau eines
- **WHEN** `KEY(1) STOP` gilt, F1 dreimal gedrückt wird und danach `KEY(1) ON` ausgeführt wird
- **THEN** läuft der Handler genau einmal
#### Scenario: OFF verwirft das unter STOP Gemerkte
- **WHEN** `TIMER STOP` gilt, die Frist verstreicht, danach `TIMER OFF` und dann `TIMER ON` ausgeführt werden
- **THEN** läuft kein Handler
### Requirement: EVENT klammert Abschnitte ohne Ereignisprüfung
`EVENT OFF` SHALL die Ereignisprüfung für den folgenden Abschnitt
abschalten, `EVENT ON` sie wieder einschalten; die beiden klammern einen
Abschnitt, in dem keine Ereignisse erkannt werden müssen. Ein dritter
Zustand MUST NOT bestehen: `EVENT STOP` ist im Vorbild nicht
dokumentiert und MUST namentlich abgewiesen werden. Der Schalter SHALL
die Zustände der einzelnen Quellen überlagern, ohne sie zu verändern:
nach `EVENT ON` gilt für jede Quelle wieder ihr eigener Zustand.
Ereignisse, die während `EVENT OFF` eintreten, MUST NOT nachträglich
zugestellt werden.
#### Scenario: Klammer überlagert Einzelzustand
- **WHEN** `TIMER ON` gilt und danach `EVENT OFF` ausgeführt wird
- **THEN** wird kein Zeit-Trap zugestellt, und nach `EVENT ON` wird wieder zugestellt, ohne dass `TIMER ON` erneut nötig ist
#### Scenario: EVENT STOP gibt es nicht
- **WHEN** ein Modul `EVENT STOP` enthält
- **THEN** wird es namentlich abgewiesen
### Requirement: Zustellung als GOSUB ohne Wiedereintritt
Ein zugestelltes Ereignis SHALL das `GOSUB`-Ziel seines Traps ausführen
und danach an die unterbrochene Stelle zurückkehren; der unterbrochene
Zustand (aufrufende Prozedur, Schleifen, Variablen) MUST erhalten
bleiben. Beim Eintritt in den Handler SHALL für seine eigene Quelle
selbsttätig `STOP` wirken, sodass keine rekursiven Traps entstehen; das
`RETURN` SHALL für sie selbsttätig `ON` ausführen — es sei denn, im
Handler wurde ausdrücklich `OFF` für diese Quelle ausgeführt, dann bleibt
sie aus. Ein `RETURN label` aus einem Handler MUST erlaubt sein.
#### Scenario: Rückkehr in die unterbrochene Schleife
- **WHEN** ein Trap während einer `FOR`-Schleife zugestellt wird
- **THEN** läuft die Schleife nach `RETURN` mit unverändertem Zählerstand weiter
#### Scenario: Kein Wiedereintritt
- **WHEN** ein Zeit-Trap läuft und die nächste Frist während des Handlers verstreicht
- **THEN** wird der Handler nicht erneut betreten, sondern das Ereignis nach `RETURN` einmal zugestellt
#### Scenario: OFF im Handler überlebt das RETURN
- **WHEN** ein Handler `TIMER OFF` ausführt und danach `RETURN` erreicht
- **THEN** bleibt die Quelle aus und kein weiterer Zeit-Trap wird zugestellt
### Requirement: Getrappte Tasten verlassen den Eingabestrom
Eine Taste, für die ein aktiver `ON KEY`-Trap besteht, SHALL dem Trap
zugestellt werden und MUST NOT zusätzlich über `INKEY$`, `INPUT`,
`LINE INPUT` oder `INPUT$` sichtbar werden. Bei `OFF` oder `STOP` der
Quelle SHALL die Taste dem normalen Eingabestrom erhalten bleiben.
#### Scenario: Getrappte Taste erscheint nicht bei INKEY$
- **WHEN** `ON KEY(1) GOSUB Marke` mit `KEY(1) ON` gilt und F1 gedrückt wird
- **THEN** läuft der Handler und ein anschließendes `INKEY$` liefert den leeren String
#### Scenario: Nicht getrappte Taste bleibt im Strom
- **WHEN** derselbe Trap mit `KEY(1) OFF` gilt und F1 gedrückt wird
- **THEN** liefert `INKEY$` die Sondertastenfolge für F1
### Requirement: Benutzerdefiniertes Ereignis
`SetUEvent` SHALL ein benutzerdefiniertes Ereignis auslösen, das über
`ON UEVENT GOSUB` und die Steueranweisung `UEVENT` denselben Regeln für
Maskierung und Zustellung unterliegt wie die übrigen Quellen.
#### Scenario: SetUEvent löst den Handler aus
- **WHEN** `ON UEVENT GOSUB Marke` mit `UEVENT ON` gilt und `SetUEvent` aufgerufen wird
- **THEN** läuft der Handler am nächsten Zustellpunkt genau einmal
### Requirement: Reihenfolge bei mehreren fälligen Ereignissen
Sind an einem Zustellpunkt mehrere Ereignisse fällig, SHALL genau eines
zugestellt werden; die übrigen bleiben anstehend und werden an
folgenden Zustellpunkten zugestellt. Die Reihenfolge SHALL festgelegt
und dokumentiert sein, sodass sie über Läufe hinweg gleich bleibt.
#### Scenario: Zwei gleichzeitig fällige Traps
- **WHEN** an einem Zustellpunkt ein Zeit- und ein Tasten-Trap zugleich fällig sind
- **THEN** läuft zuerst genau ein Handler und der andere am nächsten Zustellpunkt
### Requirement: Fehler in einem Handler
Ein Laufzeitfehler in einem Trap-Handler SHALL der geltenden
Fehlerbehandlung unterliegen. `RESUME` und `RESUME NEXT` SHALL sich auf
die Anweisung im Handler beziehen, nicht auf die unterbrochene
Anweisung.
#### Scenario: Fehler im Handler erreicht den Handler des Moduls
- **WHEN** ein Trap-Handler eine Anweisung mit Laufzeitfehler ausführt und ein modulweiter `ON ERROR GOTO`-Handler gesetzt ist
- **THEN** wird dieser Handler betreten und `ERL` nennt die Zeile im Trap-Handler
### Requirement: Signal-Trap auf Betriebssystemsignalen
`ON SIGNAL(n%) GOSUB` und die Steueranweisung `SIGNAL(n%)` SHALL
denselben Regeln für Maskierung, Zustellung und Wiedereintritt
unterliegen wie die übrigen Quellen. `n%` SHALL auf
Betriebssystemsignale abgebildet werden, und zwar ausschließlich auf die
Menge, die auf allen Zielplattformen besteht: 1 auf den
Unterbrechungswunsch (`SIGINT`), 2 auf den Beendigungswunsch
(`SIGTERM`). Ein anderer Wert MUST namentlich abgewiesen werden — zur
Übersetzungszeit, wenn er konstant ist, sonst mit Laufzeitfehler 5. Ein
Signal MUST NOT im Signalkontext verarbeitet werden; es SHALL über den
Host als Ereignis in die Warteschlange gelangen. Die Abweichung zur
OS/2-gebundenen Quelle des Vorbilds SHALL in der Sprachreferenz
ausgewiesen sein.
#### Scenario: Signal-Trap folgt der Maskierung
- **WHEN** `ON SIGNAL(1) GOSUB Marke` mit `SIGNAL(1) STOP` gilt und ein `SIGINT` eintrifft
- **THEN** läuft der Handler erst nach `SIGNAL(1) ON`, und zwar genau einmal
#### Scenario: Unzulässige Signalnummer
- **WHEN** ein Modul `ON SIGNAL(7) GOSUB Marke` enthält
- **THEN** wird es namentlich abgewiesen und die Meldung nennt den zulässigen Bereich
#### Scenario: Signal ohne Trap bleibt Abbruch
- **WHEN** ein `SIGINT` eintrifft, ohne dass ein `SIGNAL(1)`-Trap aktiv ist
- **THEN** bleibt es beim bisherigen Abbruchverhalten

View File

@@ -0,0 +1,19 @@
## ADDED Requirements
### Requirement: Korpusabdeckung der Ereignis-Traps
Der Testkorpus SHALL die Ereignissteuerung mit Programmen abdecken, die
ohne echte Wartezeit und mit reproduzierbarer Ausgabe laufen: Zeit-Trap,
Tasten-Trap einschließlich der Frage, ob die Taste im Eingabestrom
verbleibt, benutzerdefiniertes Ereignis, die drei Maskierungszustände je
Quelle, der Hauptschalter `EVENT` sowie Rückkehr und Nicht-Wiedereintritt
eines Handlers. Ein Korpusprogramm mit Zeitbezug SHALL seinen
Zeitverlauf im Programmkopf deklarieren; ohne Deklaration MUST der
Harness den Lauf abweisen statt auf die Systemuhr auszuweichen.
#### Scenario: Zeit-Trap im Korpus
- **WHEN** ein Korpusprogramm mit deklariertem Zeitverlauf und `ON TIMER` ausgeführt wird
- **THEN** entspricht der Bildschirminhalt byte-genau der Sollausgabe und der Lauf benötigt keine messbare Wartezeit
#### Scenario: Fehlende Zeitdeklaration
- **WHEN** ein Korpusprogramm zeitgesteuerte Traps nutzt, aber keinen Zeitverlauf deklariert
- **THEN** schlägt der Harness mit einer Meldung fehl, die die fehlende Deklaration nennt

View File

@@ -0,0 +1,49 @@
## ADDED Requirements
### Requirement: Ereignis-Traps in Grammatik und Signaturprüfung
Der Parser SHALL die Trap-Anweisungen des Vorbilds als eigene Konstrukte
erkennen: `ON TIMER(n) GOSUB ziel`, `ON KEY(n) GOSUB ziel`,
`ON UEVENT GOSUB ziel` und `ON SIGNAL(n) GOSUB ziel`. Diese Formen MUST
NOT als berechnetes `ON ausdruck GOSUB liste` behandelt werden. Die
Steueranweisungen `TIMER`, `KEY(n)`, `UEVENT` und `SIGNAL(n)` mit `ON`,
`OFF` oder `STOP` SHALL erkannt und nur für diese Quellen zugelassen
werden; `EVENT` SHALL nur `ON` und `OFF` zulassen, weil das Vorbild
keinen dritten Zustand kennt. `COM`, `PEN`, `PLAY` und `STRIG` MUST als
Non-Feature namentlich abgewiesen werden. `SetUEvent` SHALL eine
Signatur besitzen. Die Wertebereiche SHALL geprüft werden: `ON KEY(n%)`
mit `n%` aus 0, 125 oder 3031, `ON TIMER(n&)` mit `n&` aus 186 400
und `ON SIGNAL(n%)` mit `n%` aus 12. Eine fehlerhafte Verwendung MUST
eine Diagnose erzeugen, die das Element namentlich nennt.
#### Scenario: ON TIMER ist kein berechnetes GOSUB
- **WHEN** ein Modul `ON TIMER(5) GOSUB Marke` enthält
- **THEN** wird die Anweisung als Zeit-Trap erfasst und nicht als `ON ausdruck GOSUB` mit einer einelementigen Zielliste
#### Scenario: Ungültige Kennung wird namentlich gemeldet
- **WHEN** ein Modul `ON KEY(99) GOSUB Marke` enthält
- **THEN** nennt die Diagnose `ON KEY` und den zulässigen Wertebereich
#### Scenario: EVENT kennt kein STOP
- **WHEN** ein Modul `EVENT STOP` enthält
- **THEN** nennt die Diagnose `EVENT` und die zulässigen Formen `ON` und `OFF`
#### Scenario: Zeitintervall außerhalb des Bereichs
- **WHEN** ein Modul `ON TIMER(90000) GOSUB Marke` enthält
- **THEN** nennt die Diagnose `ON TIMER` und den Bereich 1 bis 86400
#### Scenario: Non-Feature-Quelle wird abgewiesen
- **WHEN** ein Modul `ON PEN GOSUB Marke` enthält
- **THEN** meldet die Übersetzung „Feature unavailable" an der Fundstelle der Anweisung — wie bei jedem anderen Non-Feature — und nicht einen generischen Syntaxfehler
#### Scenario: SetUEvent besitzt eine Signatur
- **WHEN** ein Modul `CALL SetUEvent` enthält
- **THEN** entsteht keine Diagnose „Subprogram not defined"
### Requirement: Trap-Ziele sind Sprungziele des Moduls
Das Ziel eines Ereignis-Traps SHALL wie ein `GOSUB`-Ziel aufgelöst
werden. Ein unbekanntes Ziel MUST zur Übersetzungszeit gemeldet werden
und nennt dabei das Label.
#### Scenario: Unbekanntes Trap-Ziel
- **WHEN** ein Modul `ON TIMER(1) GOSUB Fehlt` ohne Label `Fehlt` enthält
- **THEN** meldet die Übersetzung ein undefiniertes Sprungziel und nennt `Fehlt`

View File

@@ -0,0 +1,42 @@
## ADDED Requirements
### Requirement: Ereigniszustellung an Anweisungsgrenzen
Die VM SHALL an Anweisungsgrenzen prüfen, ob ein Ereignis zuzustellen
ist, und in diesem Fall das `GOSUB`-Ziel des zugehörigen Traps
ausführen. Der unterbrochene Zustand — Aufrufkette, Schleifen, Locals,
`GOSUB`-Stapel und Fehlerzustand — MUST dabei erhalten bleiben; mit
`RETURN` SHALL die Ausführung an der unterbrochenen Stelle fortfahren.
Die Prüfung MUST NOT pro Opcode erfolgen; solange kein Trap definiert ist,
MUST sie auf einen einzelnen Test hinauslaufen und MUST NOT die Uhr lesen.
#### Scenario: Trap aus einer Prozedur heraus
- **WHEN** ein Ereignis zugestellt wird, während eine `SUB` läuft, und das Trap-Ziel im Modulrumpf liegt
- **THEN** läuft der Handler und nach `RETURN` setzt die `SUB` an der unterbrochenen Anweisung fort
#### Scenario: Ohne aktiven Trap wird die Uhr nicht gelesen
- **WHEN** ein Programm ohne jede `ON …`-Trap-Definition ausgeführt wird
- **THEN** fragt die Ausführung an keiner Anweisungsgrenze die Zeit ab, und der Durchsatz bleibt im Rahmen der Messgenauigkeit unverändert
### Requirement: Jede Schleife trägt eine Anweisungsgrenze im Kreis
Der zurückspringende Zweig jeder Schleife (`FOR`/`NEXT`, `WHILE`/`WEND`,
`DO`/`LOOP` in allen Bedingungsformen) MUST über mindestens eine
Anweisungsgrenze führen, auch wenn der Schleifenrumpf leer ist. Andernfalls
gäbe es in einer Warteschleife keinen Zustellpunkt: kein Ereignis, kein
Breakpoint und kein Abbruch würden dort wirken.
#### Scenario: Leere Warteschleife
- **WHEN** `FOR i% = 1 TO 10000` unmittelbar von `NEXT` gefolgt wird
- **THEN** führt der Rücksprung über eine Anweisungsgrenze und ein aktiver Zeit-Trap wird darin zugestellt
#### Scenario: Leere Schleife in jeder Form
- **WHEN** eine `WHILE`/`WEND`-, `DO WHILE`/`LOOP`- oder `DO`/`LOOP UNTIL`-Schleife ohne Rumpf übersetzt wird
- **THEN** liegt zwischen Rücksprungziel und Rücksprung eine Anweisungsgrenze
### Requirement: DOEVENTS gibt die Kontrolle ab
`DOEVENTS` SHALL anstehende Ereignisse zustellen und danach einen Wert
liefern. Solange keine Formulare geladen werden können, SHALL der Wert 0
sein.
#### Scenario: DOEVENTS in einer Warteschleife
- **WHEN** ein Programm in einer Schleife `DOEVENTS` aufruft und ein Trap fällig wird
- **THEN** läuft der Handler innerhalb dieser Schleife

View File

@@ -0,0 +1,60 @@
## 1. Belege aus der Original-Hilfe
- [x] 1.1 Einzelseiten zu `ON TIMER`, `ON KEY`, `ON UEVENT`, `ON SIGNAL`, `TIMER`, `KEY(n)`, `UEVENT`, `SIGNAL(n)`, `EVENT` und `SetUEvent` erfassen und in `umfang-und-form.md` dieses Changes wortgetreu festhalten (Syntaxzeilen, Wertebereiche von `n`, Reihenfolge gleichzeitig fälliger Ereignisse, Verhalten in Prozeduren); verifiziert durch die vorhandene Datei mit Quellenangabe je Element
- [x] 1.2 Aus 1.1 die Festlegungen ableiten, die die Hilfe **nicht** hergibt, und sie als Abweichung in `docs/sprachreferenz.md` §8 eintragen — insbesondere die Quellenzuordnung von `SIGNAL(n)`; verifiziert durch den Abschnitt „Abweichungen" mit je einem Satz Begründung
- [x] 1.3 Delta-Specs an die Belege aus 1.1 angleichen (Leitplanke „Referenzverhalten schlägt Eleganz"): `EVENT` ohne `STOP`, automatischer `STOP`/`ON` um den Handler, `OFF` verwirft Gemerktes, Wertebereiche von `KEY`/`TIMER`/`SIGNAL`; verifiziert durch `openspec validate --strict` und die geänderten Requirements
## 2. Zeit- und Mausquelle im Host (BREAKING)
- [x] 2.1 `Host`-Trait um eine monotone Zeitabfrage erweitern und `TerminalHost` daran anschließen; verifiziert durch `cargo build`, das alle drei Host-Implementierungen ohne Warnung übersetzt
- [x] 2.2 `CaptureHost` um eine stellbare virtuelle Uhr erweitern (setzen und vorrücken); verifiziert durch einen Unit-Test, der die Uhr vorrückt und den gelesenen Wert prüft
- [x] 2.3 Signalquelle: Marke im Signalhandler setzen, Übersetzung in ein Ereignis beim Abholen (D8), `SIGINT` und `SIGTERM` plattformübergreifend über `signal-hook`; verifiziert durch einen Unit-Test, der eine gesetzte Marke als Ereignis wiederfindet
- [x] 2.4 Mausereignis in das Ereignismodell aufnehmen (Drücken, Loslassen, Bewegung, Taste, Umschaltzustand, Position) und `TerminalHost` die `crossterm`-Mausereignisse durchreichen lassen, statt sie zu verwerfen; verifiziert durch Unit-Tests der Abbildung je Ereignisart
- [x] 2.5 Umrechnung auf 1-basierte Zellenkoordinaten in der Darstellungsschicht, Ereignisse außerhalb der Fläche verwerfen (D7); verifiziert durch Unit-Tests an den Rändern — Zelle 1,1, letzte Zelle, Position außerhalb, und dieselben Fälle nach einer Größenänderung
- [x] 2.6 `CaptureHost` kann Mausereignisse einspeisen und die Reihenfolge gegenüber Tastenereignissen bleibt erhalten; verifiziert durch einen Unit-Test mit gemischter Folge
## 3. Trap-Zustände in der Laufzeit
- [x] 3.1 Trap-Tabelle in `RtState`: je Quelle Zustand `ON`/`OFF`/`STOP`, höchstens ein anstehendes Ereignis, Zielkennung; verifiziert durch Unit-Tests für die drei Zustandsübergänge inklusive „STOP merkt genau eines"
- [x] 3.2 Hauptschalter `EVENT` als Überlagerung der Einzelzustände; verifiziert durch einen Unit-Test, der nach `EVENT ON` den vorherigen Einzelzustand wiederfindet
- [x] 3.3 Fälligkeit der Zeit-Traps aus der Host-Zeit berechnen; verifiziert durch einen Unit-Test mit virtueller Uhr über mehrere Fristen inklusive übersprungener Frist
- [x] 3.4 `SetUEvent` als Auslöser des benutzerdefinierten Ereignisses; verifiziert durch einen Unit-Test, der genau eine Zustellung sieht
- [x] 3.5 Getrappte Tasten am Zustellpunkt aus der Tastenschlange nehmen (D5); verifiziert durch Unit-Tests für beide Richtungen — getrappte Taste erscheint nicht bei `INKEY$`, nicht getrappte bleibt erhalten
- [x] 3.7 Umschaltzustand im Tastenereignis mitführen und `KEY n, CHR$(flag)+CHR$(scan)` für n = 1525 als Erklärung einer Trap-Taste (statt Makrozuweisung); verifiziert durch Unit-Tests für Strg+A mit und ohne Strg sowie durch die Scancode-Tabelle aus der Original-Hilfe
- [x] 3.6 Mausereignisse ohne Verbraucher am Zustellpunkt verwerfen und die Warteschlange begrenzen (D7); verifiziert durch einen Unit-Test, der viele Bewegungsereignisse einspeist und die Warteschlangenlänge prüft
## 4. Zustellung in der VM
- [x] 4.1 Zustellbit im Flagwort und Fälligkeitsprüfung an `Instr::Stmt`; verifiziert durch einen VM-Test, der ohne aktiven Trap keine Zustellung auslöst
- [x] 4.2 Trap-Frame auf den Modulrumpf legen und mit `RETURN` abräumen (D2), inklusive eigenem `GOSUB`-Stapel und `RETURN label`; verifiziert durch VM-Tests für Zustellung aus dem Modulrumpf und aus einer `SUB` heraus, jeweils mit unverändertem Zustand danach
- [x] 4.3 Eigene Quelle während des Handlers stoppen und mit `RETURN` freigeben; verifiziert durch einen VM-Test, der den Handler nicht erneut betritt
- [x] 4.7 Anweisungsgrenze in den Rücksprung jeder Schleife legen — ohne sie ist `FOR i = 1 TO n: NEXT` für Ereignisse, Breakpoints und Abbruch taub (Befund bei 4.2); verifiziert durch einen Test, der für alle vier Schleifenformen eine Grenze im Kreis nachweist, und dadurch, dass die Trap-Tests mit leerem Schleifenrumpf laufen
- [x] 4.4 Reihenfolge bei mehreren fälligen Ereignissen nach Beleg aus 1.1 festlegen; verifiziert durch einen VM-Test mit zwei gleichzeitig fälligen Quellen
- [x] 4.5 `DOEVENTS` und `SLEEP` von Builtins auf Anweisungen mit Zustellung umstellen; verifiziert durch VM-Tests — Handler läuft innerhalb einer `DOEVENTS`-Schleife, und `SLEEP` endet vorzeitig durch eine Zustellung
- [x] 4.8 Befund bei 4.6: `ERL` meldete die Zeile des Fehlerhandlers statt der Fehlerzeile, sobald der Handler selbst nummeriert war — laufende Zeilennummer und `ERL` getrennt; verifiziert durch einen Regressionstest mit nummeriertem Handler
- [x] 4.6 Fehler in einem Handler an die geltende Fehlerbehandlung geben, `RESUME` bezieht sich auf den Handler; verifiziert durch einen VM-Test, dessen `ERL` die Zeile im Handler nennt
## 5. Frontend
- [x] 5.1 Grammatik für `ON TIMER(n)/KEY(n)/UEVENT/SIGNAL(n) GOSUB ziel` als eigene Konstrukte; verifiziert durch einen Parser-Test, der zeigt, dass daraus kein berechnetes `ON ausdruck GOSUB` entsteht
- [x] 5.2 Signaturen und Wertebereichsprüfung der Steueranweisungen und von `SetUEvent`; verifiziert durch Sema-Tests, deren Diagnosen das Element namentlich nennen (`ON KEY(99)`, `ON PEN`, `CALL SetUEvent`)
- [x] 5.3 Trap-Ziele wie `GOSUB`-Ziele auflösen; verifiziert durch einen Sema-Test, der ein fehlendes Label namentlich meldet
- [x] 5.4 Absenkung ins HIR/Bytecode, `Unsupported`-Absenkung von `SETUEVENT` entfernen; verifiziert durch `cargo test -p tb-frontend`, inklusive des Inventartests
## 6. Korpus und Harness
- [x] 6.1 Kopfzeilen-Direktive `' tb-clock: <n>ms/Zustellpunkt` im Harness auswerten und ohne Direktive den Lauf mit zeitgesteuerten Traps abweisen (D6); verifiziert durch einen Harness-Test für beide Fälle
- [x] 6.2 Korpusprogramme mit Sollausgabe anlegen: Zeit-Trap, Tasten-Trap mit Eingabestrom-Nachweis, `SetUEvent`, die drei Maskierungszustände, `EVENT`-Hauptschalter, Rückkehr in eine unterbrochene Schleife; verifiziert durch `cargo test -p tb-cli`, byte-genau gegen die `.out`
- [x] 6.3 Denselben Korpuslauf zweimal ausführen und auf identische Ausgabe prüfen; verifiziert durch einen Test, der die Reproduzierbarkeit sicherstellt
## 7. Dokumentation und Inventar
- [x] 7.1 `docs/sprachreferenz.md` §8 ausformulieren: Zustellpunkte, Maskierung, Wiedereintritt, Wertebereiche; verifiziert durch den Abschnitt, der jede Spec-Zusicherung wiederfindet
- [x] 7.2 `docs/bibliothek.md` um die zehn Elemente erweitern; verifiziert durch je einen Eintrag mit Syntax und Wirkung
- [x] 7.3 `docs/inventar.md`: die zehn Einträge auf `implementiert` mit Fundstelle setzen; verifiziert durch `inventar_stimmt_mit_code_ueberein`
- [x] 7.4 PLAN.md: Eingangsaufgabe und Punkt „Klassische Ereignis-Traps" der Phase 4 abhaken, Befunde festhalten; verifiziert durch die aktualisierte Phase-4-Liste
## 8. Abnahme
- [x] 8.1 `benches/vm.rs` vor und nach der Änderung messen und das Ergebnis in der Änderungsnotiz festhalten; verifiziert durch die Messwerte zur Zusicherung „ohne aktiven Trap kein messbarer Aufwand"
- [x] 8.2 `cargo test` über alle Crates grün und `openspec validate phase-4-ereignisschleife --strict` ohne Befund; verifiziert durch beide Kommandos

View File

@@ -0,0 +1,261 @@
# Umfang und Form der Ereignis-Elemente (Aufgaben 1.1, 1.2)
**Quelle.** `bas7advr.hlp` (BASIC 7 Advisor), Einzelseiten unter
https://dos-help.soulsphere.org/bas7advr.hlp/ (abgerufen 2026-09-04);
Gegenprobe `qb45advr.hlp` unter derselben Domain. Zitate wörtlich aus den
Einzelseiten, Übersetzung nur in den Kommentarzeilen.
## 1.1 Einzelseiten
| Element | Seite |
|---|---|
| `ON TIMER` | `x_dot_ontimerrr.html` |
| `ON KEY` | `x_dot_onkeyr.html` |
| `ON UEVENT` | `x_dot_onur.html` |
| `ON SIGNAL` | `x_dot_onsigbar.html` |
| `TIMER` (Anweisung) | `x_dot_timersr.html` |
| `KEY(n)` (Ereignis) | `x_dot_ketvr.html` |
| `UEVENT` | `x_dot_uevvvr.html` |
| `SIGNAL` | `x_dot_signalr.html` |
| `EVENT` | `x_dot_evntonr.html` |
| `SetUEvent` | `x_dot_SETUEVr.html` |
### ON TIMER
> `ON TIMER(n&) GOSUB {linenumber | linelabel}`
>
> „The maximum number of seconds that can be specified for the time
> interval, n&, is the number of seconds in a 24-hour day (86,400)."
>
> „A linenumber value of 0 disables event trapping and does not specify
> line 0 as the start of the routine."
Der Zeitwert ist **LONG** (`n&`), Bereich 186 400 Sekunden.
`GOSUB 0` schaltet den Trap ab.
### TIMER ON/OFF/STOP
> „TIMER ON enables timer-event trapping."
>
> „TIMER OFF disables timer-event trapping. … Events occurring while
> trapping is off are ignored."
>
> „TIMER STOP suspends timer-event trapping. … Events occurring while
> trapping is off are remembered and processed when the next TIMER ON
> statement is executed."
**Wiedereintritt — wörtlich belegt:**
> „an automatic TIMER STOP is executed so that recursive traps cannot
> take place."
>
> „The RETURN from the trapping routine automatically performs a TIMER ON
> statement unless an explicit TIMER OFF was performed inside the
> routine."
Das ist genauer als „die Quelle gilt während des Handlers als gestoppt":
`RETURN` schaltet die Quelle **auf ON**, außer im Handler stand ein
ausdrückliches `OFF`.
### KEY(n) — Wertebereich
> ```
> KEY(n%) ON
> KEY(n%) OFF
> KEY(n%) STOP
> ```
| n% | Taste |
|---|---|
| 0 | alle Tasten dieser Tabelle |
| 110 | F1F10 |
| 11 | Pfeil hoch |
| 12 | Pfeil links |
| 13 | Pfeil rechts |
| 14 | Pfeil runter |
| 1525 | benutzerdefinierte Tasten |
| 3031 | F11F12 (101-Tasten-Tastatur) |
`ON KEY` selbst: `ON KEY(n%) GOSUB {linenumber | linelabel}`. Zur Frage,
ob eine getrappte Taste im Eingabestrom bleibt, sagt die Einzelseite nur:
> „After a key is trapped, the information on which key was trapped is no
> longer available."
Die Gegenprobe in `qb45advr.hlp` (`x_dot_onkeyba.html`, `x_dot_ketv.html`)
enthält dazu **nichts**. → Festlegung nötig, siehe 1.2.
### Benutzerdefinierte Tasten 1525
Die Einzelseite zu `KEY n, zeichenkette$` (`x_dot_setfkr.html`) behandelt
**nur** die Makrozuweisung für 110 und 3031; die Erklärungsform für
1525 steht in keiner der beiden Hilfen, ebenso wenig die Bedeutung der
Bits des Tastaturflagbytes. Belegt ist dagegen die **Scancode-Tabelle**
(`qb45advr.hlp`, `x_dot_kbsct.html`), aus der die Zuordnung Zeichen →
Scancode stammt:
> ESC 01 · „! or 1" 02 … „) or 0" 0B · „_ or -" 0C · „+ or =" 0D ·
> TAB 0F · Q 10 … P 19 · ENTER 1C · A 1E … L 26 · Z 2C … M 32 ·
> SPACEBAR 39 · F1 3B … F10 44 · HOME 47 · UP 48 · LEFT 4B · RIGHT 4D ·
> END 4F · DOWN 50 · INS 52 · DEL 53
→ Festlegung nötig für das Flagbyte, siehe 1.2.
### UEVENT und SetUEvent
> `ON UEVENT GOSUB {linenumber | linelabel}`
>
> „This event typically originates from a hardware interrupt but can also
> be triggered by software."
>
> „SetUEvent … sets a flag that BASIC checks before executing the next
> statement."
Bestätigt die Zustellung **an der Anweisungsgrenze**.
> ```
> UEVENT ON
> UEVENT OFF
> UEVENT STOP
> ```
>
> UEVENT STOP „preserves events in memory for processing once UEVENT ON
> resumes, though executing UEVENT OFF afterward will discard these queued
> events."
`OFF` verwirft also auch ein unter `STOP` gemerktes Ereignis.
`SetUEvent` ist eine **Routine** ohne dokumentierte Argumentliste und ohne
dokumentierten Rückgabewert:
> „It sets the BASIC entry point that causes a user-defined event."
### SIGNAL
> ```
> SIGNAL(n%) ON
> SIGNAL(n%) OFF
> SIGNAL(n%) STOP
> ```
>
> `ON SIGNAL(n%) GOSUB {linenumber | linelabel}`
>
> „A linenumber value of 0 disables event trapping."
Quelle der Signale — wörtlich:
> „OS/2 protected-mode signal"
>
> „Process flags A, B, and C are used for communicating between
> processes."
>
> aktiviert durch die OS/2-Funktion `DOSFLAGPROCESS` aus einem anderen
> Prozess.
Ein **Wertebereich für `n%` ist nicht dokumentiert**; die Einzelseite
verweist auf „Chapter 9, Event Handling'" des Programmer's Guide, der
nicht vorliegt.
### EVENT
> ```
> EVENT ON
> EVENT OFF
> ```
>
> „EVENT OFF … equivalent to compiling a program from the BC command line
> without using /V or /W."
>
> „EVENT OFF and EVENT ON can be used to bracket sections of code where
> events do not need to be detected and trapped, and fast performance is
> required."
**`EVENT STOP` ist nicht dokumentiert.** `EVENT` ist im Vorbild kein
Laufzeit-Hauptschalter mit drei Zuständen, sondern eine Klammer, die die
Erzeugung der Ereignisprüfungen im Kompilat ein- und ausschaltet.
### Reihenfolge und Trapping in Prozeduren
Zu beidem geben die Einzelseiten **nichts** her; sie verweisen auf
Kapitel 9 des Programmer's Guide. → Festlegung nötig, siehe 1.2.
## 1.2 Abweichungen der Delta-Spec vom Beleg
Nach der Leitplanke „Referenzverhalten schlägt Eleganz … das gilt auch
gegenüber den eigenen Spezifikationen" ist bei jeder Abweichung die
**Spec** anzugleichen.
| Nr. | Delta-Spec sagt | Beleg sagt | Folge |
|---|---|---|---|
| A | `EVENT` hat `ON`/`OFF`/`STOP` und überlagert die Einzelzustände | nur `ON`/`OFF`; Klammer um die Erzeugung der Prüfungen | Spec angleichen; `EVENT STOP` wird namentlich abgewiesen |
| B | „während des Handlers gilt die Quelle als gestoppt; mit `RETURN` gilt wieder ihr Zustand" | Eintritt = automatisches `STOP`; `RETURN` = automatisches `ON`, außer im Handler stand ein ausdrückliches `OFF` | Spec präzisieren |
| C | `OFF` verwirft künftige Ereignisse | `OFF` verwirft **auch** ein unter `STOP` gemerktes | Spec ergänzen |
| D | `ON KEY(n)` mit Wertebereich | 0, 125, 3031 mit fester Tastenzuordnung | Spec unverändert, Bereich jetzt belegt |
| E | `ON TIMER(n)` | `n&` ist LONG, 186 400; `GOSUB 0` schaltet ab | Spec unverändert, Form jetzt belegt |
**Ohne Beleg — unsere Festlegungen (gehören nach `sprachreferenz.md`
unter „Abweichungen"):**
1. **Getrappte Tasten verlassen den Eingabestrom.** Von keiner der beiden
Hilfen belegt. Wir legen fest: eine Taste mit aktivem Trap erscheint
nicht zusätzlich bei `INKEY$`/`INPUT`. Begründung: andernfalls sähe ein
Programm dieselbe Taste zweimal, und `KEY(n) OFF` wäre die einzige Art,
eine Taste überhaupt normal zu lesen.
2. **Reihenfolge gleichzeitig fälliger Ereignisse.** Unbelegt. Wir legen
fest: `KEY` (aufsteigend nach `n`), dann `TIMER`, dann `UEVENT`, dann
`SIGNAL` — Tasten zuerst, weil sie an einer Benutzereingabe hängen.
3. **Trapping in Prozeduren.** Unbelegt. Wir legen fest: die Zustellung
erfolgt an jeder Anweisungsgrenze, auch innerhalb von `SUB`/`FUNCTION`;
das Ziel liegt im Modulrumpf. Das entspricht dem bereits umgesetzten
Verhalten des modulweiten `ON ERROR`.
4. **Bits des Tastaturflagbytes** in `KEY n, CHR$(flag)+CHR$(scan)`.
Unbelegt. Wir legen die Belegung des BIOS-Flagbytes zugrunde:
1 rechte Umschalttaste, 2 linke Umschalttaste, 4 Strg, 8 Alt. Im
Terminal sind linke und rechte Umschalttaste nicht unterscheidbar,
beide Bits gelten deshalb gleich; NumLock und CapsLock spielen für
einen Trap keine Rolle und werden ignoriert.
5. **`SIGNAL`-Quelle.** Die belegte Quelle ist OS/2-gebunden
(`DOSFLAGPROCESS`, Prozessflags A/B/C) und existiert auf keiner
Zielplattform; ein Wertebereich ist nicht dokumentiert. → **Entscheidung
des Projektinhabers ausstehend**, siehe unten.
## Entscheidung zu `SIGNAL` (Projektinhaber, 2026-09-04)
`SIGNAL` wird auf **Betriebssystemsignale** abgebildet, und zwar nur auf
die minimale Menge, die auf allen Zielplattformen existiert.
Der C-Standard garantiert überall — Linux, macOS **und** Windows —
`SIGABRT`, `SIGFPE`, `SIGILL`, `SIGINT`, `SIGSEGV`, `SIGTERM`. Von diesen
sind nur zwei asynchron, von außen ausgelöst und fortsetzbar; die vier
Fault-Signale entstehen aus dem eigenen Programmzustand, und ein
`RETURN` aus ihrem Handler führte in denselben Fehler zurück. Abgebildet
werden deshalb:
| `n%` | Signal | Bedeutung |
|---|---|---|
| 1 | `SIGINT` | Unterbrechungswunsch von außen |
| 2 | `SIGTERM` | Beendigungswunsch von außen |
Ein anderer Wert als 1 oder 2 wird namentlich abgewiesen — zur
Übersetzungszeit, wenn er konstant ist, sonst mit Laufzeitfehler 5.
**Zustellung.** Signale erreichen die Laufzeit wie die Zeit über den
Host; nur er berührt das Betriebssystem. Ein Signal wird nicht im
Signalkontext verarbeitet, sondern setzt eine Marke, die der Host beim
nächsten Abholen in ein Ereignis übersetzt.
**Verhältnis zum Abbruch.** Im Rohmodus liefert das Terminal Strg+C als
Taste, nicht als Signal; daran ändert sich nichts. Trifft `SIGINT`
dagegen von außen ein (`kill -INT`), gilt: mit aktivem Trap läuft der
Handler, ohne aktiven Trap bleibt es beim bisherigen Abbruch. Dieselbe
Regel wie bei getrappten Tasten.
**Abweichungen, die zu dokumentieren sind:**
1. Die belegte Quelle des Vorbilds (OS/2-Prozessflags A/B/C über
`DOSFLAGPROCESS`) existiert nicht; `n%` bedeutet bei uns etwas
anderes als im Vorbild. Ein Wertebereich war im Vorbild ohnehin nicht
dokumentiert.
2. `SIGTERM` ist unter Windows von außen nicht zustellbar (es gibt kein
`kill`); `SIGNAL(2)` bleibt dort wirkungslos. `SIGNAL(1)` wird dort
über den Konsolen-Steuerhandler (`CTRL_C_EVENT`) bedient.