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,210 @@
# ereignissteuerung Specification
## 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.
## 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

@@ -110,3 +110,21 @@ durchlaufen und danach die neuen Grenzen ausnutzen.
#### Scenario: Größenänderung mitten im Programm
- **WHEN** der Test-Host während der Ausführung von 80×25 auf 120×40 wechselt
- **THEN** bleibt der bisherige Inhalt oben links erhalten und die anschließende Ausgabe nutzt die neuen Grenzen
### 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

@@ -199,3 +199,51 @@ Bezeichner oder mit einem generischen Syntaxfehler abgewiesen werden
#### Scenario: Typprüfung des Satzarguments
- **WHEN** `RETRIEVE #1, x$` mit einem String statt einer Variablen des Tabellentyps geprüft wird
- **THEN** wird „Type mismatch" mit Quelltextposition gemeldet
### 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

@@ -114,3 +114,44 @@ Programmbestandteil.
#### Scenario: STOP im Runner
- **WHEN** ein per `tbc run` gestartetes Programm `STOP` in Zeile 7 ausführt
- **THEN** endet der Prozess mit Exit-Code ungleich 0 und die Meldung nennt Zeile 7
### 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