OpenSpec: phase-2-bytecode-vm archiviert, Delta-Specs in Hauptspecs gesynct

- Neue Hauptspecs: bytecode-kompilat, vm-ausfuehrung,
  vm-fehlerbehandlung, konsolen-basisbibliothek
- Erweitert: sprach-frontend (Typisiertes HIR als Sema-Ausgabe),
  kompat-testkorpus (Laufzeitvergleich, Phase-2-Korpusabdeckung)
- Change nach openspec/changes/archive/2026-09-02-phase-2-bytecode-vm
  verschoben; openspec validate --all: 9/9

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 11:33:51 +02:00
parent f7e57b0bd8
commit a6f005d023
16 changed files with 364 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
## Purpose
Das Bytecode-Kompilat ist das serialisierbare Ergebnis der Übersetzung:
Der Codegenerator senkt das typisierte HIR in einen monomorphen
Opcode-Satz ab, der `.tbc`-Container hält es als stabiles, dokumentiertes
Format, und das Instant-Compile-Budget sichert den schnellen
Edit-Run-Zyklus.
## ADDED Requirements
### Requirement: Monomorpher Opcode-Satz
Der Codegenerator SHALL jedes HIR-Konstrukt in typisierte, monomorphe
Opcodes übersetzen: Arithmetik, Vergleiche und Konvertierungen tragen den
Operandentyp im Opcode (kein Tag-Dispatch über Werte zur Laufzeit);
implizite Konvertierungen erscheinen als explizite Konvertierungs-Opcodes
an den vom HIR bestimmten Stellen. Der Opcode-Satz SHALL in
docs/tbvm-design.md vollständig dokumentiert sein.
#### Scenario: Gemischter Ausdruck wird monomorph
- **WHEN** `d# = i% + 1.5#` übersetzt wird (INTEGER-Variable, DOUBLE-Ziel)
- **THEN** enthält der Bytecode einen Konvertierungs-Opcode INTEGER→DOUBLE und eine DOUBLE-Addition, keinen generischen Additions-Opcode
#### Scenario: Namen sind zur Laufzeit aufgelöst
- **WHEN** eine Variable oder Prozedur im Bytecode referenziert wird
- **THEN** geschieht das über Slot- bzw. Tabellenindizes, nicht über Namens-Lookups
### Requirement: `.tbc`-Containerformat
Das Kompilat SHALL als `.tbc`-Datei serialisierbar und wieder ladbar sein:
Magic `TBC\0`, Formatversion, Abschnittstabelle mit den Abschnitten
CONSTS (deduplizierter Konstantenpool), TYPES (TYPE-Layouts), PROCS
(modul-qualifizierte Prozedurtabelle mit Signatur, Locals-Anzahl,
Code-Offset), CODE, DATA (`READ`/`RESTORE`-Segment) und LINES
(Zeilentabelle). Laden und erneutes Serialisieren MUST verlustfrei sein;
eine unbekannte Formatversion MUST mit einer klaren Fehlermeldung
abgewiesen werden.
#### Scenario: Roundtrip
- **WHEN** ein kompiliertes Modul als `.tbc` geschrieben und wieder geladen wird
- **THEN** ist das geladene Kompilat funktional identisch (gleiche Ausführung, gleiche Zeilenzuordnung)
#### Scenario: Unbekannte Version
- **WHEN** eine `.tbc`-Datei mit höherer Formatversion geladen wird
- **THEN** wird das Laden mit einer Meldung abgelehnt, die die Version nennt
### Requirement: Zeilentabelle für Fehlerortung
Der Bytecode SHALL jede Anweisung ihrem Ursprung (Moduldatei, Zeile)
zuordnen, sodass Laufzeitfehlermeldungen, `ERL`, Breakpoints und
Einzelschritt die Quellzeile exakt benennen können.
#### Scenario: Fehler nennt Zeile
- **WHEN** in Zeile 42 eines Programms ein Laufzeitfehler ohne Handler auftritt
- **THEN** nennt die Fehlermeldung Zeile 42
### Requirement: Instant-Compile-Budget
Die Übersetzung (Lexen bis Bytecode) SHALL ohne globale Analysepasses
auskommen; Vorwärtsreferenzen werden über Fixups aufgelöst. Ein
Benchmark MUST das Budget nachweisbar machen: ein Projekt von ~50.000
Zeilen kompiliert in unter 1 s, ein einzelnes Modul in unter 50 ms
(Release-Build, Referenzrechner); die Messwerte werden in
docs/tbvm-design.md festgehalten.
#### Scenario: Budget wird gemessen
- **WHEN** die Benchmarks in `benches/` laufen
- **THEN** wird die Kompilierzeit für das ~50k-Zeilen-Projekt und für ein Einzelmodul ausgewiesen und gegen das Budget verglichen

View File

@@ -0,0 +1,27 @@
## ADDED Requirements
### Requirement: Laufzeitvergleich über den Korpus
Ein automatischer Test SHALL jede Korpusdatei `tests/compat/*.bas`
kompilieren, über die VM mit Capture-Host ausführen und die Ausgabe
byte-genau (inklusive nachgestellter Leerzeichen und LF-Zeilenenden)
gegen die zugehörige `.out`-Datei vergleichen. Bei Abweichung MUST der
Test Datei, erste abweichende Zeile sowie Soll und Ist nennen.
#### Scenario: Korpus läuft mit korrekter Ausgabe
- **WHEN** die Laufzeit-Testsuite läuft
- **THEN** stimmt die Ausgabe jeder Korpusdatei byte-genau mit ihrer `.out` überein
#### Scenario: Abweichung wird benannt
- **WHEN** ein Korpusprogramm eine abweichende Ausgabe erzeugt
- **THEN** schlägt der Test fehl und nennt Datei, Zeilennummer, Soll- und Ist-Zeile
### Requirement: Korpusabdeckung der Phase-2-Semantik
Der Korpus SHALL um Referenzprogramme mit dokumentierter Sollausgabe für
die Zahlenkonvertierungs-Matrix (Rundung, Ganzzahldivision, logische
Operatoren, Überlauf-Randfälle über Fehlerbehandlung), die
Fehlerbehandlung (`ON ERROR`, `RESUME`-Varianten, `ERR`/`ERL`) sowie
Prozeduraufrufe mit BYREF-/BYVAL-Wirkung erweitert werden.
#### Scenario: Konvertierungsmatrix als Korpustest
- **WHEN** die Laufzeit-Testsuite läuft
- **THEN** existiert ein Korpusprogramm, das die Matrix-Randfälle ausgibt, und seine Ausgabe entspricht der dokumentierten Sollausgabe

View File

@@ -0,0 +1,66 @@
## Purpose
Die Konsolen-Basisbibliothek ist die in Phase 2 vorgezogene Scheibe der
Laufzeitbibliothek: die Builtin-Aufrufschnittstelle der VM, ein
`Host`-Abstraktion für Konsolen-E/A sowie PRINT-Formatierung und die vom
Testkorpus benötigten Stringfunktionen.
## ADDED Requirements
### Requirement: Builtin-Aufrufschnittstelle
Die VM SHALL Bibliotheksfunktionen und -anweisungen über eine indizierte
Dispatch-Tabelle aufrufen (Argumente auf dem Operandenstack, Index im
Opcode); die Tabelle lebt in der Laufzeitbibliothek und ist ohne
Änderung am Opcode-Satz erweiterbar (Phase 3 füllt sie auf).
#### Scenario: Builtin über Tabelle
- **WHEN** `PRINT LEN("abc")` ausgeführt wird
- **THEN** wird `LEN` über seinen Tabellenindex aufgerufen und liefert 3
### Requirement: Host-Abstraktion für Konsolen-E/A
Alle Konsolenwirkungen der VM SHALL über eine austauschbare
Host-Schnittstelle laufen (Textausgabe, zeilenweises Lesen für `INPUT`,
Abbruchprüfung). Host-Aufrufe dürfen blockieren; die Abbruchprüfung
(Strg+Untbr) obliegt dem Host. `tbc run` verwendet einen Konsolen-Host
(stdout/stdin); Tests MUST einen Capture-Host verwenden können, der die
Ausgabe byte-genau aufzeichnet.
#### Scenario: Capture-Host im Test
- **WHEN** ein Korpusprogramm im Test-Harness läuft
- **THEN** entsteht die komplette Ausgabe im Capture-Host und ist byte-genau vergleichbar
### Requirement: PRINT-Formatierung
`PRINT` SHALL Zahlen im Format des Vorbilds ausgeben: führendes
Leerzeichen für nicht-negative bzw. `-` für negative Werte,
nachgestelltes Leerzeichen (auch am Zeilenende); Zahlendarstellung je
Typ wie beim Vorbild (u. a. kein Dezimalpunkt bei ganzzahligen Werten,
signifikante Stellen je nach SINGLE/DOUBLE). `;` schließt direkt an,
`,` springt zur nächsten 14-Zeichen-Druckzone (belegt ein Element die
Zone ganz, zur übernächsten); ohne abschließendes `;`/`,` folgt ein
Zeilenumbruch.
#### Scenario: Zahlen mit Vorzeichenspalte
- **WHEN** `PRINT 1; -2; 3.5` ausgeführt wird
- **THEN** lautet die Ausgabe ` 1 -2 3.5 ` mit Zeilenumbruch
#### Scenario: Druckzonen
- **WHEN** `PRINT "12345678901234", "x"` ausgeführt wird
- **THEN** beginnt `x` in Spalte 29 (Zone nach der belegten übersprungen)
### Requirement: Korpus-Stringfunktionen
Die Bibliothek SHALL mindestens folgende Funktionen mit der Semantik des
Vorbilds bereitstellen (Zeichen = Unicode-Codepoints, dokumentierte
UTF-8-Abweichung): `LEN`, `LEFT$`, `RIGHT$`, `MID$` (Funktion),
`INSTR`, `UCASE$`, `LCASE$`, `STRING$`, `SPACE$`, `CHR$`, `ASC`,
`STR$`, `VAL`. Randfälle folgen dem Vorbild, u. a.: `VAL` liest das
führende Zahlenpräfix und ignoriert Leerraum; `STR$` stellt
nicht-negativen Zahlen ein Leerzeichen voran; `ASC("")` löst Fehler 5
„Illegal function call" aus.
#### Scenario: Korpusdatei strings.bas
- **WHEN** `tests/compat/strings.bas` ausgeführt wird
- **THEN** entspricht die Ausgabe byte-genau `strings.out`
#### Scenario: VAL mit Restzeichen
- **WHEN** `VAL(" 12.5abc")` ausgewertet wird
- **THEN** ist das Ergebnis 12.5

View File

@@ -0,0 +1,19 @@
## ADDED Requirements
### Requirement: Typisiertes HIR als Sema-Ausgabe
Die semantische Analyse SHALL neben den Diagnosen einen typisierten,
abgesenkten Zwischenbaum (HIR) liefern, der die Codegen-Eingabe bildet:
Namen sind zu Slot-/Tabellenindizes aufgelöst, jeder Ausdrucksknoten
trägt seinen Ergebnistyp, `CONST`-Werte sind gefaltet, `DEFtype`-Regeln
angewandt und implizite Konvertierungen als explizite
Konvertierungsknoten materialisiert. Bei Diagnosen mit Fehlercharakter
darf kein HIR für das betroffene Konstrukt garantiert werden; ein
diagnose-freies Modul MUST ein vollständiges HIR ergeben.
#### Scenario: Konvertierung wird materialisiert
- **WHEN** `d# = i% + 1.5#` semantisch analysiert wird
- **THEN** enthält das HIR einen expliziten Konvertierungsknoten INTEGER→DOUBLE unter dem Additionsknoten und die Zuweisung trägt den Zieltyp DOUBLE
#### Scenario: Diagnose-freies Modul liefert HIR
- **WHEN** ein Korpusmodul ohne Diagnosen analysiert wird
- **THEN** liefert das Frontend ein HIR, aus dem der Codegen ohne erneute Namens- oder Typauflösung Bytecode erzeugen kann

View File

@@ -0,0 +1,115 @@
## 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.
## ADDED 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

View File

@@ -0,0 +1,71 @@
## Purpose
Die BASIC-Fehlersemantik (`ON ERROR GOTO`, `ON LOCAL ERROR`, `RESUME`,
`ERR`/`ERL`) ist Teil des Kompatibilitätsvertrags: Programme des Vorbilds
verlassen sich auf Handler-Scoping, Propagation und Wiederaufnahme exakt
wie dokumentiert.
## ADDED Requirements
### Requirement: Handler-Scoping nach Vorbild
`ON ERROR GOTO label` SHALL einen modulweiten Handler aktivieren (auch
wenn die Anweisung in einer Prozedur steht); `ON LOCAL ERROR GOTO label`
SHALL einen prozedurlokalen Handler aktivieren, der den modulweiten für
die Dauer des Prozedurlaufs verdeckt. `ON [LOCAL] ERROR GOTO 0`
deaktiviert den jeweiligen Handler. Tritt ein Fehler in einer Prozedur
ohne zuständigen Handler auf, SHALL er die Aufrufkette hochpropagieren,
bis ein aktiver Handler gefunden ist; ohne Handler bricht das Programm
mit der Fehlermeldung und der Quellzeile ab.
#### Scenario: Modulweiter Handler fängt Prozedurfehler
- **WHEN** das Hauptprogramm `ON ERROR GOTO Fehler` gesetzt hat und eine gerufene SUB Fehler 6 auslöst
- **THEN** springt die Ausführung in den Handler `Fehler` und `ERR` ist 6
#### Scenario: Lokaler Handler verdeckt modulweiten
- **WHEN** eine Prozedur `ON LOCAL ERROR GOTO L` gesetzt hat und in ihr ein Fehler auftritt
- **THEN** behandelt `L` den Fehler, nicht der modulweite Handler
#### Scenario: Ohne Handler bricht das Programm ab
- **WHEN** Fehler 9 ohne aktiven Handler auftritt
- **THEN** endet das Programm mit der Meldung „Subscript out of range" und der Quellzeile
### Requirement: ERR und ERL
Beim Eintritt in einen Handler SHALL `ERR` den Fehlercode liefern und
`ERL` die zuletzt vor dem Fehler ausgeführte **numerische** Zeilennummer
(0, wenn keine numerischen Zeilennummern durchlaufen wurden —
alphanumerische Labels zählen nicht). `ERROR n` SHALL Fehler `n`
künstlich auslösen und dieselbe Maschinerie durchlaufen.
#### Scenario: ERL bei Label-Programm
- **WHEN** ein Programm ohne numerische Zeilennummern einen Fehler behandelt
- **THEN** liefert `ERL` 0
#### Scenario: ERROR-Anweisung
- **WHEN** `ERROR 53` bei aktivem Handler ausgeführt wird
- **THEN** wird der Handler mit `ERR` = 53 betreten
### Requirement: RESUME-Semantik
Im Handler SHALL `RESUME` (bzw. `RESUME 0`) die fehlerauslösende
Anweisung wiederholen, `RESUME NEXT` mit der Anweisung danach
fortfahren und `RESUME label` an das Ziel springen. `RESUME` außerhalb
einer aktiven Fehlerbehandlung MUST Fehler 20 „RESUME without error"
auslösen. Nach `RESUME` gilt die Fehlerbehandlung als abgeschlossen
(`ERR` wird zurückgesetzt).
#### Scenario: RESUME NEXT
- **WHEN** ein Handler nach Fehler 11 (`Division by zero`) `RESUME NEXT` ausführt
- **THEN** wird die auf die Division folgende Anweisung ausgeführt
#### Scenario: RESUME ohne Fehler
- **WHEN** `RESUME` außerhalb eines Handlers ausgeführt wird
- **THEN** tritt Laufzeitfehler 20 auf
### Requirement: Fehler im Handler ist fatal
Tritt ein Fehler auf, während die Fehlerbehandlung aktiv ist (zwischen
Handler-Eintritt und `RESUME`), SHALL das Programm ohne erneute
Handler-Suche mit der Fehlermeldung abbrechen (keine Fehlerkaskaden,
Verhalten des Vorbilds).
#### Scenario: Kein Kaskadieren
- **WHEN** im aktiven Handler selbst Fehler 6 auftritt
- **THEN** bricht das Programm mit „Overflow" ab, auch wenn ein Handler registriert ist