Files
TerminalBasic/openspec/changes/phase-3-isam/design.md
Chili Palmer 52ccbb5848 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>
2026-09-04 06:54:19 +02:00

9.3 KiB
Raw Blame History

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.