164 lines
7.9 KiB
Markdown
164 lines
7.9 KiB
Markdown
# vm-ausfuehrung Specification
|
||
|
||
## Purpose
|
||
Die TBVM führt das Bytecode-Kompilat mit der exakten Semantik des
|
||
Dialekts aus: Ausdrücke mit der Zahlenkonvertierungs-Matrix des Vorbilds,
|
||
Kontrollfluss, Prozeduraufrufe mit BYREF, `GOSUB`, unterbrechbare
|
||
`step()`-Ausführung als Grundlage für Debugger und Ereignisschleife.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Zahlenkonvertierungs-Matrix
|
||
Die VM SHALL implizite und explizite Zahlenkonvertierungen nach der in
|
||
docs/tbvm-design.md dokumentierten Matrix ausführen, insbesondere:
|
||
Gleitkomma→Ganzzahl rundet kaufmännisch zur nächsten geraden Zahl
|
||
(Banker's Rounding); Bereichsüberschreitung löst Fehler 6 „Overflow"
|
||
aus; `/` liefert stets Gleitkomma, `\` und `MOD` konvertieren ihre
|
||
Operanden vorab zu Ganzzahlen; logische Operatoren (`AND`, `OR`, `XOR`,
|
||
`EQV`, `IMP`) arbeiten bitweise auf zu Ganzzahlen konvertierten
|
||
Operanden; CURRENCY rechnet als Festkomma ×10 000. Die Matrix SHALL
|
||
durch ein Korpusprogramm mit dokumentierter Sollausgabe abgedeckt sein.
|
||
|
||
#### Scenario: Banker's Rounding
|
||
- **WHEN** `PRINT CINT(0.5); CINT(1.5); CINT(2.5)` ausgeführt wird
|
||
- **THEN** lautet die Ausgabe ` 0 2 2 `
|
||
|
||
#### Scenario: Überlauf bei Konvertierung
|
||
- **WHEN** einer INTEGER-Variablen der Wert 40000 zugewiesen wird
|
||
- **THEN** tritt Laufzeitfehler 6 „Overflow" auf
|
||
|
||
#### Scenario: Ganzzahldivision rundet Operanden vor
|
||
- **WHEN** `PRINT 7.5 \ 2` ausgeführt wird
|
||
- **THEN** wird 7.5 zuerst zu 8 gerundet und die Ausgabe ist ` 4 `
|
||
|
||
### Requirement: Kontrollfluss
|
||
Die VM SHALL `IF`/`ELSEIF`/`ELSE`, `SELECT CASE` (Werte, Bereiche,
|
||
`IS`-Vergleiche, `CASE ELSE`), `FOR`/`NEXT` (inkl. `STEP`, null
|
||
Durchläufen bei unpassender Grenze), `DO`/`LOOP` (alle vier
|
||
Bedingungsformen), `WHILE`/`WEND`, `GOTO`, `ON expr GOTO/GOSUB` und
|
||
`EXIT FOR/DO` mit der Semantik des Vorbilds ausführen.
|
||
|
||
#### Scenario: FOR ohne Durchlauf
|
||
- **WHEN** `FOR i% = 3 TO 1: PRINT i%: NEXT` ausgeführt wird
|
||
- **THEN** wird der Schleifenkörper nicht betreten
|
||
|
||
#### Scenario: Korpus Kontrollfluss
|
||
- **WHEN** `tests/compat/kontrollfluss.bas` ausgeführt wird
|
||
- **THEN** entspricht die Ausgabe byte-genau `kontrollfluss.out`
|
||
|
||
### Requirement: GOSUB/RETURN neben Prozeduraufrufen
|
||
`GOSUB` SHALL die Rücksprungadresse auf einem GOSUB-Stack im aktuellen
|
||
Frame ablegen (kein eigener Frame); `RETURN` kehrt dorthin zurück,
|
||
`RETURN label` an das benannte Ziel. Ein `RETURN` ohne anhängigen
|
||
`GOSUB` MUST Fehler 3 „RETURN without GOSUB" auslösen.
|
||
|
||
#### Scenario: RETURN ohne GOSUB
|
||
- **WHEN** ein Programm `RETURN` ohne vorangehendes `GOSUB` ausführt
|
||
- **THEN** tritt Laufzeitfehler 3 auf
|
||
|
||
### Requirement: Prozeduraufrufe mit BYREF-Semantik
|
||
SUB-/FUNCTION-Aufrufe SHALL eigene Frames mit Locals-Slots verwenden.
|
||
Variablen, Arrayelemente und UDT-Felder werden BYREF übergeben
|
||
(Änderungen im Rumpf wirken auf das Argument zurück); ein geklammertes
|
||
Argument oder ein Ausdruck wird als temporärer Wert übergeben (BYVAL-
|
||
Wirkung). `STATIC`-Prozeduren behalten ihre Locals zwischen Aufrufen;
|
||
rekursive Aufrufe nicht-statischer Prozeduren erhalten frische Locals.
|
||
`DEF FN`-Funktionen werten im Modulkontext aus (Zugriff auf
|
||
Modulvariablen), ihre Parameter wirken lokal.
|
||
|
||
#### Scenario: BYREF wirkt zurück
|
||
- **WHEN** `SUB Inc(x%) : x% = x% + 1 : END SUB` mit `Inc n%` aufgerufen wird
|
||
- **THEN** ist `n%` danach um 1 erhöht
|
||
|
||
#### Scenario: Klammern erzwingen Wertübergabe
|
||
- **WHEN** dieselbe SUB mit `Inc (n%)` aufgerufen wird
|
||
- **THEN** bleibt `n%` unverändert
|
||
|
||
### Requirement: DATA/READ/RESTORE
|
||
`READ` SHALL die `DATA`-Konstanten des Moduls in Deklarationsreihenfolge
|
||
konsumieren und in den Zieltyp konvertieren; `RESTORE [label]` setzt den
|
||
Lesezeiger auf den Anfang bzw. auf die erste `DATA`-Anweisung ab dem
|
||
Label. Lesen hinter dem letzten Element MUST Fehler 4 „Out of DATA"
|
||
auslösen; eine nicht konvertierbare Konstante MUST Fehler 13 „Type
|
||
mismatch" auslösen.
|
||
|
||
#### Scenario: Out of DATA
|
||
- **WHEN** ein Programm mehr Werte liest als `DATA` bereitstellt
|
||
- **THEN** tritt Laufzeitfehler 4 auf
|
||
|
||
### Requirement: Unterbrechbare step()-Ausführung
|
||
Die VM SHALL als zustandsbehaftete, vom Einbetter getriebene Struktur
|
||
ausführbar sein: An Anweisungsgrenzen prüft sie ein Flag-Wort und kann
|
||
anhalten (Breakpoint auf (Modul, Zeile), Einzelschritt, Abbruchwunsch).
|
||
Im angehaltenen Zustand MUST der Einbetter Variableninhalte (Skalare,
|
||
Arrayelemente, UDT-Felder) inspizieren und die Ausführung fortsetzen
|
||
können. Die Tick-Prüfung erfolgt nur an Anweisungsgrenzen, nicht pro
|
||
Opcode.
|
||
|
||
#### Scenario: Breakpoint hält an
|
||
- **WHEN** ein Breakpoint auf Zeile 10 gesetzt ist und die Ausführung Zeile 10 erreicht
|
||
- **THEN** hält die VM vor der Anweisung an und meldet Modul und Zeile
|
||
|
||
#### Scenario: Inspektion im Halt
|
||
- **WHEN** die VM an einem Breakpoint steht
|
||
- **THEN** liefert die Inspektions-API den aktuellen Wert einer benannten Variablen
|
||
|
||
### Requirement: Programmende im Runner
|
||
Außerhalb der IDE SHALL `END` (und das Erreichen des Programmendes) den
|
||
Prozess mit Exit-Code 0 beenden; `STOP` SHALL mit der Meldung
|
||
`STOP in line n` (bzw. Modul/Zeile) und einem Exit-Code ungleich 0
|
||
terminieren (Projektentscheidung 2026-09-02). `SYSTEM` verhält sich wie
|
||
`END`. `CONT` existiert nur als IDE-Konzept (Phase 5) und ist kein
|
||
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.
|
||
|
||
Ohne klassische Traps, aktive Forms-Timer oder zeitabhängige Mausauswertung SHALL auch die Forms-Integration keine Hostzeit abfragen.
|
||
|
||
#### 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
|
||
|
||
#### Scenario: Einfaches Programm liest keine Ereigniszeit
|
||
- **WHEN** ein Programm nur Zuweisungen und END ausführt und ein Testhost Zeitabfragen zählt
|
||
- **THEN** ist der Zähler am Ende 0
|
||
|
||
### 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
|