Phase 3 (Kern): Laufzeitbibliothek, Bildschirm und Datei-E/A

Setzt den OpenSpec-Change phase-3-runtime-bildschirm um (77/77 Aufgaben).
Abdeckung laut Inventar: 195 implementiert, 37 offen, 53 Non-Feature.

Vollstaendigkeits-Inventar
- docs/inventar.md mit 285 Eintraegen aus den Themenlisten von
  bas7advr.hlp und qb45advr.hlp, je mit Status und Fundstelle
- crates/tb-frontend/tests/inventar.rs haelt die Tabelle in beide
  Richtungen gegen den Code; der Abdeckungsstand kann nicht veralten

Bruchschritt (Puffer, Host, Korpus)
- Cell/TextScreen ziehen ratatui-frei nach tb-runtime::screen; tb-ui
  behaelt Farbabbildung, Widget und den neuen Terminal-Host
- Host wechselt vom Zeichenstrom auf Anzeige des Zellenpuffers plus
  Ereignisse (Taste, Groessenaenderung, Abbruch)
- Korpusvergleich auf getrimmten Bildschirm-Snapshot; die acht
  bestehenden Sollausgaben blieben dabei unveraendert

Groessenunabhaengigkeit
- 80x25 ist nirgends mehr eine feste Grenze; Groessenaenderungen waehrend
  der Ausfuehrung werden zugestellt (Inhalt oben links erhalten, Cursor
  und VIEW PRINT geklemmt)
- Korpusnachweis: dasselbe Programm bei 80x25 und 120x40 mit je eigener
  Sollausgabe, plus ein Programm mit Groessenwechsel mitten im Lauf

Bibliothek
- Breite Unicode-Zeichen belegen zwei Zellen (Cursor, POS, Randumbruch)
- Bildschirm: CLS, COLOR, LOCATE, WIDTH, VIEW PRINT, SCREEN, CSRLIN, POS
- Tastatur: INKEY$, INPUT$, Funktionstasten-Makros (KEY n / LIST / ON)
- PRINT USING, LPRINT USING, FORMAT$, SetFormatCC
- Mathematik mit kompatiblem PRNG (gleiche Saat, gleiche Folge)
- Datum und Zeit mit Serienwerten ab 1899
- Finanzmathematik: FV#, PV#, Pmt#, IPmt#, PPmt#, NPer#, Rate#, NPV#,
  IRR#, MIRR#, SLN#, SYD#, DDB#
- Datei-E/A: sequenziell, RANDOM (Recordpuffer und UDT-Variablen),
  BINARY, FIELD/LSET/RSET, Statusfunktionen, Dateisystem, MK$/CV
- System: ENVIRON, FRE, CLEAR, TRON/TROFF, STACK, ERDEV, ERR-Anweisung

Altlasten aus Phase 2
- ON ERROR GOTO auf Modulebene ist aus Prozeduren ansprechbar
  (prozeduruebergreifender Fixup im Codegenerator)
- DATA behaelt seinen Rohtext (Gross-/Kleinschreibung, innerer Leerraum)
- Die vier TODO-verify-Zellen der Konvertierungsmatrix sind aufgeloest

Vom Inventar aufgedeckte Fehler
- Zwoelf Non-Features wies der Compiler entgegen der Phase-1-Spec nicht
  ab (CALLS, SSEG, POINT, VIEW, COM, PEN, STRIG, STICK, die
  String*-Routinen, LINE und OPEN "COMn:") -- geschlossen
- Drei Gruppen fehlten im urspruenglichen Umfang: Finanzmathematik,
  Record-Konvertierung, Rest der Dateisystemfunktionen
- bas7advr.hlp allein ist keine vollstaendige Quelle; das Inventar
  bildet die Vereinigung mit qb45advr.hlp

Neue Changes
- phase-3-isam: schliesst Phase 3 ab (Speicherschicht redb)
- phase-3-ortszeit: zieht die UTC-Abweichung zurueck

Neue Abhaengigkeit: unicode-width.
Dokumentiert: sprachreferenz.md und tbvm-design.md sind TODO-frei,
docs/bibliothek.md neu, PLAN.md fortgeschrieben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 06:54:19 +02:00
parent a6f005d023
commit 52ccbb5848
94 changed files with 10421 additions and 792 deletions

View File

@@ -0,0 +1,209 @@
## Context
Siehe proposal.md — Why. Technisch relevant ist der Zustand nach
`phase-3-runtime-bildschirm`:
- Die Dateitabelle, die Recordpuffer und die UTF-32-Serialisierung fester
Textfelder existieren (Capability `datei-eio`) und sind die Grundlage,
auf der ISAM die Sätze ablegt.
- Die Laufzeitfehler 8189 sind in `tb_runtime::errors` bereits
vorhanden; dieser Change liefert ihre Auslöser.
- Vom ISAM-Sprachumfang existiert heute nur die Parser-Klausel
`OPEN … FOR ISAM` (`parser.rs`). Die ISAM-Anweisungen haben weder
Grammatik noch Signatur — sie enden in „unbekannter Bezeichner".
- Das Sprachinventar (`docs/inventar.md`) führt die ISAM-Elemente mit
Status `offen` und ist der maßgebliche Umfangsnachweis.
## Goals / Non-Goals
**Goals:**
- Der volle dokumentierte ISAM-Sprachumfang läuft, nicht eine Auswahl.
- Die Cursor-Semantik des Vorbilds ist exakt abgebildet — sie ist der
Teil, den keine Fremdbibliothek liefert.
- Speicher, Indizes und Transaktionen kommen aus einer erprobten
Bibliothek statt aus Eigenbau.
- Phase 3 ist mit diesem Change abgeschlossen.
**Non-Goals:**
- Leistungsoptimierung des Satzzugriffs — ISAM-Programme des Vorbilds
arbeiten auf Datenmengen, die jede heutige Speicherschicht mühelos
trägt.
- Nebenläufiger Zugriff mehrerer Prozesse auf dieselbe Datenbankdatei
über die Satzsperren aus `datei-eio` hinaus.
- Lesen von Datenbankdateien des Vorbilds.
## Decisions
### D1: `redb` als Speicherschicht
Die Datenbankdatei ist eine `redb`-Datei. Sätze und Indexeinträge liegen
darin als Key-Value-Paare; `redb` liefert B-Baum, Transaktionen und
Crash-Sicherheit.
*Warum nicht Eigenbau:* Seitenverwaltung, B-Baum und crash-sichere
Transaktionen sind gelöste Probleme mit langen Fehlerkatalogen. Der
projektspezifische Teil ist die ISAM-Semantik, nicht der Baum darunter.
*Warum nicht SQLite:* Zöge eine C-Bibliothek in jedes von `tbc build
--exe` erzeugte Binary. Der eigentliche Grund ist aber semantisch: ISAM
hat einen *stabilen, benannten Cursor*, der Änderungen überlebt und sich
vorwärts wie rückwärts bewegt — ein SQL-Cursor ist ein
Ergebnismengen-Iterator. Die Abbildung wäre aufwendiger als der Gewinn.
Die für Stufe 2 geplante SQL-Erweiterung bleibt davon unberührt; sie ist
eine eigene Sprachebene und kann später ihren eigenen Speicher wählen.
*Was `redb` nicht liefert und wir bauen:* Sekundärindizes,
Savepoints innerhalb einer Transaktion, die Cursor-Semantik.
### D2: Datenmodell — Sätze und Indizes als getrennte Tabellen
Je Tabelle eine Satztabelle `satz-id → Satzbytes` und je Index eine
Tabelle `Schlüsselbytes → satz-id`. Satz-IDs sind monoton vergebene
Zahlen und werden nie wiederverwendet.
*Warum getrennt:* `SETINDEX` wechselt die Durchlaufordnung zur Laufzeit;
mit Indextabellen ist das ein Wechsel der zu lesenden Tabelle, ohne die
Sätze anzufassen. `MOVEPREVIOUS` und die `SEEK`-Familie sind dann
Bereichsabfragen auf einer geordneten Tabelle.
*Warum IDs nicht wiederverwendet werden:* Der Cursor merkt sich eine
Satz-ID. Würde sie nach `DELETE` neu vergeben, könnte ein Cursor still
auf einen fremden Satz zeigen.
### D3: Schlüsselkodierung muss ordnungserhaltend sein
Indexschlüssel werden so kodiert, dass die Byte-Reihenfolge der Kodierung
der fachlichen Reihenfolge entspricht — Zahlen mit vorzeichenrichtiger
Kodierung fester Breite, Texte nach der in D4 festgelegten Ordnung,
mehrspaltige Schlüssel als Verkettung mit eindeutigem Trenner.
Absteigende Spalten werden durch Invertierung der Bytes abgebildet.
*Warum das die kritische Stelle ist:* `redb` ordnet nach Bytes. Jede
Ordnungsverletzung in der Kodierung äußert sich als falsche
Satzreihenfolge — ein Fehler, der erst bei bestimmten Datenwerten
sichtbar wird. Ein Eigenschaftstest über zufällige Wertepaare
(Kodierungsvergleich = fachlicher Vergleich) gehört deshalb zur
Umsetzung.
*Mehrspaltige Präfixsuche* fällt aus dieser Kodierung ab: Suche mit
weniger Werten als Spalten ist eine Bereichsabfrage über das
Schlüsselpräfix.
### D4: Sortierordnung explizit festlegen
Texte werden nach Unicode-Codepoints geordnet, Groß- und Kleinschreibung
unterschieden. Es ist dieselbe Ordnung, die der Vergleichsoperator des
Dialekts auf Strings anwendet.
`TEXTCOMP` gibt es in der Original-Hilfe nicht (Befund aus Aufgabe 1.1,
2026-09-03); die Ordnung braucht deshalb keine eigene Vergleichsfunktion,
sondern muss nur mit dem Stringvergleich der Sprache übereinstimmen.
*Warum nicht sprachabhängige Sortierung:* Sie machte die Reihenfolge
plattform- und locale-abhängig und damit die Korpustests unreproduzierbar.
Das Vorbild ordnete nach Zeichencodes einer festen Codepage; Codepoints
sind die direkte Entsprechung unter der bereits getroffenen
Unicode-Entscheidung. Die Ordnung wird in docs/sprachreferenz.md
festgehalten, weil sie beobachtbar ist.
### D5: Cursor als Wert, nicht als Bibliotheks-Iterator
Der Cursor je Dateinummer ist ein eigener Zustand: aktiver Index,
zuletzt besuchte Satz-ID und ein Kennzeichen „unpositioniert". Jede
Bewegung ist eine frische Bereichsabfrage ab dem gemerkten Schlüssel.
*Warum keine gehaltenen Iteratoren:* Der ISAM-Cursor überlebt
Satzänderungen und Transaktionsgrenzen; ein gehaltener Iterator über eine
Transaktion tut das nicht. Ein Cursor als Wert bildet außerdem
„unpositioniert" (Fehler 85) direkt ab, statt es aus einem
Iteratorzustand ableiten zu müssen.
*Preis:* Jede Bewegung kostet eine Suche statt eines Schritts. Bei den
Datenmengen dieser Programme irrelevant; siehe Non-Goals.
### D6: Savepoints als Rücknahmeprotokoll
`BEGINTRANS` öffnet eine Schreibtransaktion. Innerhalb davon führt die
Laufzeit ein Protokoll der Änderungen; die Funktion `SAVEPOINT` merkt
sich eine Position darin und liefert deren Kennung, `ROLLBACK` mit dieser
Kennung spielt das Protokoll bis dorthin rückwärts ab. `ROLLBACK ALL` und
`COMMITTRANS` nutzen die Transaktion der Bibliothek direkt.
*Warum ein eigenes Protokoll:* Verschachtelte Rücknahmepunkte gehören
nicht zum Funktionsumfang von `redb`. Das Protokoll ist der kleinste
Weg dorthin und bleibt auf den Umfang einer laufenden Transaktion
begrenzt.
### D7: Fehler 89 und `SETMEM` bekommen eine echte Grenze
`SETMEM` ist eine Funktion: sie ändert die Obergrenze des ISAM-Puffers um
den übergebenen Betrag und liefert die danach verfügbare Größe.
Überschreiten der Grenze löst Fehler 89 aus. Ohne Grenze wäre der Code nie
erreichbar und die Anweisung eine Attrappe.
*Abweichung:* Die Bedeutung in Bytes entspricht nicht dem
DOS-Speichermodell des Vorbilds und wird in docs/sprachreferenz.md
dokumentiert.
### D8: Reihenfolge der Umsetzung
```
1. Frontend: Grammatik + Signaturen -> Elemente werden benannt
2. Speicher: Datei, Satztabelle, Schluesselkodierung (+ Eigenschaftstest)
3. Indizes: CREATEINDEX/SETINDEX/GETINDEX$/DELETEINDEX
4. Cursor: MOVE-Familie, SEEK-Familie, EOF
5. Saetze: INSERT/RETRIEVE/UPDATE/DELETE, Eindeutigkeit
6. Transaktionen: BEGINTRANS/COMMITTRANS/ROLLBACK[ ALL]/SAVEPOINT
7. Rest: SETMEM, DELETETABLE, BOF, Formatversion
```
Schritt 1 zuerst, weil er allein schon den Guiding-Principle-Verstoß
behebt: ISAM-Programme werden ab da namentlich abgewiesen statt mit
„unbekannter Bezeichner". Schritt 2 vor allem anderen, weil die
Schlüsselkodierung jede spätere Ordnung bestimmt.
## Risks / Trade-offs
- **Fehlerhafte Schlüsselkodierung fällt erst bei bestimmten Daten auf**
→ Eigenschaftstest über zufällige Wertepaare je Spaltentyp:
Byte-Vergleich der Kodierung MUSS dem fachlichen Vergleich entsprechen.
Läuft vor allem Weiteren.
- **Der genaue ISAM-Sprachumfang stammt aus der Original-Hilfe und ist
bislang nur aus zweiter Hand belegt** → Das Inventar aus
`phase-3-runtime-bildschirm` ist die maßgebliche Liste; findet die
Erhebung dort weitere Elemente, gehören sie in diesen Change. Die
Aufzählung im Proposal ist ein Mindestumfang, keine Obergrenze.
- **Cursorverhalten an den Rändern ist die häufigste Fehlerquelle**
(`EOF` nach `MOVENEXT` am Ende, Cursor nach `DELETE`, nach `SETINDEX`,
nach `ROLLBACK`) → je ein Korpustest pro Randfall, nicht ein
Sammeltest.
- **Neue Abhängigkeit in `tb-runtime`, die bisher keine hatte** →
bewusst; die Alternative wäre ein eigener transaktionaler B-Baum.
Die Abhängigkeit bleibt auf das ISAM-Modul beschränkt.
- **Das Rücknahmeprotokoll kann bei langen Transaktionen wachsen** →
begrenzt auf die laufende Transaktion; `COMMITTRANS` beendet sie. Falls
eine Grenze nötig wird, ist Fehler 89 der dafür vorgesehene Ausgang.
- **Eigenes Dateiformat heißt: keine Vorbild-Datenbanken lesbar** →
bewusst entschieden, dieselbe Linie wie UTF-32-Records; steht in
docs/dateiformate.md und in der Sprachreferenz.
## Migration Plan
Rein additiv — es gibt heute keine ISAM-Funktionalität und keine
Datenbankdateien im Feld. Die einzige sichtbare Änderung an bestehendem
Verhalten: `OPEN … FOR ISAM` löst nicht mehr Laufzeitfehler 73 aus,
sondern arbeitet.
Die Datenbankdatei trägt von der ersten Fassung an eine Formatversion,
damit spätere Änderungen abgewiesen statt fehlinterpretiert werden.
## Open Questions
- Genaue Argumentformen einzelner ISAM-Anweisungen (etwa die Schreibweise
der Spaltenliste bei `CREATEINDEX`): bei der Umsetzung aus der
Original-Hilfe zu übernehmen. Berührt weder Datenmodell noch
Aufgabenschnitt.
- Konkreter Vorgabewert der `SETMEM`-Grenze: bei der Umsetzung zu wählen
und zu dokumentieren.