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,2 @@
schema: spec-driven
created: 2026-09-03

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.

View File

@@ -0,0 +1,104 @@
# Phase 3 (Abschluss) — ISAM-Datenbankunterstützung
## Why
Die Leitplanke „Vollständigkeit ist das Soll" und die Entscheidung vom
2026-09-02 legen fest: ISAM wird implementiert, es ist kein Non-Feature.
Heute existiert davon nur eine Parser-Klausel — `OPEN … FOR ISAM typname
tabellenname AS #n` wird erkannt und auf den „nicht verfügbar"-Marker
abgesenkt. Die eigentlichen ISAM-Anweisungen und -Funktionen
(`CREATEINDEX`, `SETINDEX`, `INSERT`, `RETRIEVE`, `UPDATE`, `DELETE`, die
`MOVE`- und `SEEK`-Familien, die Transaktionsanweisungen) besitzen im
Frontend **keine Signatur**. Sie enden damit in „unbekannter Bezeichner"
bzw. einem generischen Syntaxfehler — genau der Fall, den der Guiding
Principle als Bug definiert: ein dokumentiertes Feature wird still und
ohne Namensnennung abgewiesen.
Die Laufzeitfehler 8189 des ISAM-Bereichs sind bereits als
`tb_runtime::errors` implementiert und warten auf ihre Auslöser.
Dieser Change schließt Phase 3 ab. Der vorausgehende Change
`phase-3-runtime-bildschirm` liefert die Datei-E/A-Grundlage
(Dateitabelle, Recordpuffer, UTF-32-Festtextfelder), auf der ISAM
aufsetzt, sowie das Inventar, in dem die ISAM-Elemente ihren Status
führen.
## What Changes
- **ISAM-Elemente erhalten Frontend-Signaturen**: Sämtliche in der
Original-Hilfe dokumentierten ISAM-Anweisungen und -Funktionen werden
in Grammatik und Signaturprüfung aufgenommen, damit ihre Verwendung
namentlich diagnostiziert statt als unbekannter Bezeichner behandelt
wird. Den maßgeblichen Umfang bestimmt das Sprachinventar aus
`phase-3-runtime-bildschirm`; nach heutigem Stand umfasst er
mindestens `CREATEINDEX`, `DELETEINDEX`, `SETINDEX`, `GETINDEX$`,
`INSERT`, `RETRIEVE`, `UPDATE`, `DELETE`, `DELETETABLE`, `MOVEFIRST`,
`MOVELAST`, `MOVENEXT`, `MOVEPREVIOUS`, `SEEKEQ`, `SEEKGT`, `SEEKGE`,
`BEGINTRANS`, `COMMITTRANS`, `ROLLBACK`/`ROLLBACK ALL`, `SAVEPOINT`
(Funktion), `SETMEM` (Funktion), `BOF` sowie das ISAM-Verhalten von
`EOF`, `DELETE`, `LOF` und `LOC`.
- **ISAM-Datenbanken und Tabellen**: `OPEN … FOR ISAM typname
tabellenname AS #n` bindet eine Tabelle einer Datenbankdatei an eine
Dateinummer; das Satzlayout ergibt sich aus dem benannten
benutzerdefinierten Typ. Tabellen entstehen beim ersten Öffnen,
`DELETETABLE` entfernt sie.
- **Indizes**: benannte Indizes über eine oder mehrere Spalten, eindeutig
oder mehrdeutig, mit auf- und absteigender Ordnung; `SETINDEX` wählt
den aktiven Index und bestimmt damit die Satzreihenfolge, `GETINDEX$`
liefert ihn zurück.
- **Satzoperationen und Cursor**: `INSERT`, `RETRIEVE`, `UPDATE`,
`DELETE` arbeiten auf dem aktuellen Satz; die `MOVE`-Familie bewegt den
Cursor in der Ordnung des aktiven Index, die `SEEK`-Familie positioniert
über Schlüsselvergleiche (gleich, größer, größer-gleich). `EOF` und
`BOF` melden das Überlaufen des Cursors am Ende bzw. am Anfang.
- **Transaktionen**: `BEGINTRANS`, `COMMITTRANS`, `ROLLBACK` und
`ROLLBACK ALL` mit Rücknahme aller Änderungen seit Transaktionsbeginn
bzw. seit einem Sicherungspunkt; `SAVEPOINT` ist eine **Funktion** und
liefert die Kennung des gesetzten Sicherungspunkts.
- **Vergleichsreihenfolge**: Die Sortierordnung der Indizes wird unter
Unicode festgelegt und dokumentiert.
- **ISAM-Fehlersemantik**: Die vorhandenen Fehlercodes 8189 werden von
den entsprechenden Situationen ausgelöst (ungültiger Name, Tabelle
nicht gefunden, Index nicht gefunden, ungültige Spalte, kein aktueller
Satz, Verstoß gegen einen eindeutigen Index, ungültige Operation auf
NULL-Index, inkonsistente Datenbank, zu wenig Puffer).
- **Eigenes Datenbankdateiformat**: Die Datenbankdatei ist bewusst nicht
binärkompatibel zum Vorbild — dieselbe Linie wie bei den UTF-32-
Records. Das Format wird in `docs/dateiformate.md` beschrieben.
**Non-Goals:** Ein Konvertierungswerkzeug für Datenbankdateien des
Vorbilds; die eingebettete SQL-Erweiterung samt `WHILE SELECT`
(Stufe 2, Ideenspeicher); Mehrprozess-Zugriff über
Betriebssystemgrenzen hinweg jenseits der Satzsperren aus `datei-eio`.
## Capabilities
### New Capabilities
- `isam-datenbank`: indizierter Satzzugriff — Datenbank- und
Tabellenbindung an Dateinummern, Indexverwaltung, Cursorbewegung und
Schlüsselsuche, Satzoperationen, Transaktionen, Vergleichsreihenfolge
und die ISAM-Fehlersemantik (Codes 8189).
### Modified Capabilities
- `sprach-frontend`: Die Grammatik und die Signaturprüfung SHALL die
ISAM-Anweisungen und -Funktionen umfassen, sodass ihre Verwendung
namentlich diagnostiziert wird statt als unbekannter Bezeichner.
## Impact
- `crates/tb-frontend`: neue Anweisungsgrammatik und Signaturen für die
ISAM-Elemente; die bisherige `Unsupported`-Absenkung von `OPEN … FOR
ISAM` entfällt.
- `crates/tb-runtime`: neues ISAM-Modul (Speicher, Indizes, Cursor,
Transaktionen) auf der Dateitabelle und den Recordpuffern aus
`phase-3-runtime-bildschirm`.
- `crates/tb-vm`: neue Builtin-Indizes für die ISAM-Anweisungen.
- `tests/compat`: neue Korpusprogramme für Indexordnung, Schlüsselsuche,
Eindeutigkeitsverletzung, Cursorenden und Transaktionsrücknahme.
- `docs/`: `docs/dateiformate.md` um das Datenbankdateiformat erweitert;
`docs/bibliothek.md` um die ISAM-Elemente; `docs/inventar.md` wechselt
deren Status auf `implementiert`; `docs/sprachreferenz.md` hält die
Sortierordnung und die Inkompatibilität des Dateiformats fest.
- PLAN.md: Phase 3 gilt mit diesem Change als abgeschlossen.

View File

@@ -0,0 +1,187 @@
## Purpose
Die ISAM-Unterstützung bietet indizierten Satzzugriff auf Tabellen einer
Datenbankdatei: Sätze werden über benannte Indizes geordnet durchlaufen
und über Schlüsselvergleiche gesucht, Änderungen laufen in Transaktionen
und Verstöße melden die ISAM-Fehlercodes des Vorbilds.
## ADDED Requirements
### Requirement: Datenbank- und Tabellenbindung an eine Dateinummer
`OPEN datenbank$ FOR ISAM typname tabellenname AS #n` SHALL eine Tabelle
einer Datenbankdatei an eine Dateinummer binden. Das Satzlayout SHALL
sich aus dem benannten benutzerdefinierten Typ ergeben; dessen Felder
sind die Spalten der Tabelle. Existiert die Tabelle noch nicht, SHALL sie
mit diesem Layout angelegt werden; existiert sie mit abweichendem Layout,
MUST Laufzeitfehler 88 „ISAM - Database inconsistent" auftreten. Ein
ungültiger Tabellen- oder Datenbankname MUST Fehler 81 „ISAM - Invalid
name" auslösen. `CLOSE` SHALL die Bindung lösen und ausstehende
Änderungen festschreiben. `DELETETABLE` SHALL eine Tabelle samt ihren
Indizes entfernen; eine nicht vorhandene Tabelle MUST Fehler 82 „ISAM -
Table not found" auslösen.
#### Scenario: Tabelle wird beim ersten Öffnen angelegt
- **WHEN** eine Datenbank ohne die Tabelle `Kunden` mit `OPEN "db" FOR ISAM KundeTyp "Kunden" AS #1` geöffnet wird
- **THEN** entsteht die Tabelle mit den Feldern des Typs und die Dateinummer ist nutzbar
#### Scenario: Abweichendes Satzlayout
- **WHEN** eine bestehende Tabelle mit einem Typ geöffnet wird, dessen Felder nicht zu ihren Spalten passen
- **THEN** tritt Laufzeitfehler 88 auf
#### Scenario: Entfernte Tabelle
- **WHEN** `DELETETABLE` für eine nicht vorhandene Tabelle aufgerufen wird
- **THEN** tritt Laufzeitfehler 82 auf
### Requirement: Indexverwaltung
`CREATEINDEX` SHALL einen benannten Index über eine oder mehrere Spalten
anlegen, wahlweise eindeutig oder mehrdeutig und je Spalte auf- oder
absteigend geordnet. `DELETEINDEX` SHALL einen Index entfernen.
`SETINDEX` SHALL den aktiven Index einer Dateinummer setzen und damit die
Reihenfolge bestimmen, in der Sätze durchlaufen werden; `GETINDEX$` SHALL
den Namen des aktiven Index liefern. Ein unbekannter Indexname MUST
Laufzeitfehler 83 „ISAM - Index not found" auslösen, ein unbekannter
Spaltenname Fehler 84 „ISAM - Invalid column". Nach `SETINDEX` MUST der
Cursor als unpositioniert gelten.
#### Scenario: Aktiver Index bestimmt die Reihenfolge
- **WHEN** ein Index über die Spalte `Name` gesetzt ist und die Sätze mit `MOVEFIRST` und `MOVENEXT` durchlaufen werden
- **THEN** erscheinen sie in aufsteigender Reihenfolge der Spalte `Name`
#### Scenario: Unbekannter Index
- **WHEN** `SETINDEX #1, "GibtsNicht"` ausgeführt wird
- **THEN** tritt Laufzeitfehler 83 auf
#### Scenario: Index über unbekannte Spalte
- **WHEN** `CREATEINDEX` eine Spalte nennt, die der Satztyp nicht führt
- **THEN** tritt Laufzeitfehler 84 auf
### Requirement: Cursorbewegung in der Ordnung des aktiven Index
`MOVEFIRST`, `MOVELAST`, `MOVENEXT` und `MOVEPREVIOUS` SHALL den Cursor
in der Ordnung des aktiven Index bewegen. Läuft der Cursor über das Ende
hinaus, SHALL `EOF` für diese Dateinummer wahr werden; läuft er über den
Anfang hinaus, SHALL `BOF` wahr werden. In beiden Fällen SHALL der Cursor
als unpositioniert gelten. Eine Bewegung ohne aktiven Index SHALL der
Einfügereihenfolge folgen.
#### Scenario: Cursor läuft über das Ende hinaus
- **WHEN** nach dem letzten Satz `MOVENEXT` ausgeführt wird
- **THEN** liefert `EOF` für diese Dateinummer wahr
#### Scenario: Rückwärts über den Anfang hinaus
- **WHEN** auf dem ersten Satz `MOVEPREVIOUS` ausgeführt wird
- **THEN** liefert `BOF` für diese Dateinummer wahr
### Requirement: Schlüsselsuche
`SEEKEQ`, `SEEKGT` und `SEEKGE` SHALL den Cursor auf den ersten Satz
setzen, dessen Schlüssel im aktiven Index gleich, größer bzw.
größer-gleich dem angegebenen Wert ist. Die Zahl der Schlüsselwerte MUST
höchstens der Spaltenzahl des aktiven Index entsprechen; ein
mehrspaltiger Index SHALL mit einem Präfix der Spalten durchsucht werden
können. Findet die Suche keinen Satz, SHALL `EOF` wahr werden und der
Cursor unpositioniert bleiben. Eine Suche ohne aktiven Index MUST
Laufzeitfehler 83 auslösen.
#### Scenario: Suche nach größerem Schlüssel
- **WHEN** Sätze mit den Schlüsseln 10, 20 und 30 vorliegen und `SEEKGT #1, 20` ausgeführt wird
- **THEN** steht der Cursor auf dem Satz mit Schlüssel 30
#### Scenario: Erfolglose Suche
- **WHEN** `SEEKGT #1, 30` bei größtem Schlüssel 30 ausgeführt wird
- **THEN** liefert `EOF` wahr
#### Scenario: Suche über Präfix eines mehrspaltigen Index
- **WHEN** der aktive Index über `Nachname` und `Vorname` geht und mit nur einem Wert gesucht wird
- **THEN** positioniert die Suche auf dem ersten Satz mit passendem `Nachname`
### Requirement: Satzoperationen auf dem aktuellen Satz
`INSERT` SHALL einen Satz aus einer Variablen des Tabellentyps einfügen,
`RETRIEVE` den aktuellen Satz in eine solche Variable lesen, `UPDATE` ihn
aus einer solchen Variablen überschreiben und `DELETE` ihn entfernen.
`RETRIEVE`, `UPDATE` und `DELETE` ohne positionierten Cursor MUST
Laufzeitfehler 85 „ISAM - No current record" auslösen. Ein `INSERT` oder
`UPDATE`, das einen als eindeutig angelegten Index verletzen würde, MUST
Laufzeitfehler 86 „ISAM - Duplicate value for unique index" auslösen und
den Satzbestand unverändert lassen. Nach `DELETE` SHALL der Cursor als
unpositioniert gelten. Alle Indizes der Tabelle SHALL nach jeder
Satzoperation die neue Ordnung widerspiegeln.
#### Scenario: Einfügen und Zurücklesen
- **WHEN** ein Satz eingefügt, per `SEEKEQ` gesucht und mit `RETRIEVE` gelesen wird
- **THEN** stimmen alle Feldwerte mit den eingefügten überein
#### Scenario: Operation ohne aktuellen Satz
- **WHEN** nach `SETINDEX` ohne Positionierung `RETRIEVE #1, k` ausgeführt wird
- **THEN** tritt Laufzeitfehler 85 auf
#### Scenario: Verstoß gegen eindeutigen Index
- **WHEN** ein Satz eingefügt wird, dessen Schlüssel in einem eindeutigen Index bereits vorkommt
- **THEN** tritt Laufzeitfehler 86 auf und der Satzbestand bleibt unverändert
#### Scenario: Index folgt der Änderung
- **WHEN** die indizierte Spalte eines Satzes per `UPDATE` geändert wird
- **THEN** erscheint der Satz beim nächsten Durchlauf an seiner neuen Position in der Indexordnung
### Requirement: Transaktionen mit Rücknahme
`BEGINTRANS` SHALL eine Transaktion eröffnen, `COMMITTRANS` alle
Änderungen seit ihrem Beginn festschreiben und `ROLLBACK` sie
zurücknehmen. `SAVEPOINT` SHALL als **Funktion** einen Sicherungspunkt
setzen und dessen Kennung liefern; `ROLLBACK` mit einer solchen Kennung
SHALL auf diesen Punkt zurücksetzen, ohne die Transaktion aufzugeben.
`ROLLBACK ALL` SHALL alle Änderungen seit `BEGINTRANS` zurücknehmen und
die Transaktion beenden. Nach jeder Rücknahme MUST der Satzbestand
einschließlich aller Indizes dem Zustand zum Bezugspunkt entsprechen und
der Cursor als unpositioniert gelten.
#### Scenario: Rücknahme stellt den Bestand wieder her
- **WHEN** nach `BEGINTRANS` Sätze eingefügt und gelöscht werden und dann `ROLLBACK` folgt
- **THEN** entspricht der Bestand beim erneuten Durchlauf dem Zustand vor `BEGINTRANS`
#### Scenario: Rücknahme auf einen Sicherungspunkt
- **WHEN** innerhalb einer Transaktion `s = SAVEPOINT` ausgewertet, danach geändert und mit `ROLLBACK s` zurückgesetzt wird
- **THEN** bleiben die Änderungen vor dem Sicherungspunkt erhalten und die danach sind zurückgenommen
#### Scenario: ROLLBACK ALL beendet die Transaktion
- **WHEN** nach mehreren Sicherungspunkten `ROLLBACK ALL` ausgeführt wird
- **THEN** entspricht der Bestand dem Zustand vor `BEGINTRANS` und die Transaktion ist beendet
### Requirement: Vergleichsreihenfolge für Indizes
Textspalten SHALL in Indizes nach einer festgelegten, dokumentierten
Sortierordnung geordnet werden. Diese Ordnung MUST dieselbe sein, die der
Vergleichsoperator des Dialekts auf Strings anwendet, damit ein Programm
die Satzreihenfolge vorhersagen kann. Die gewählte Ordnung MUST in
docs/sprachreferenz.md festgehalten sein, einschließlich der Behandlung
von Groß- und Kleinschreibung und von Zeichen jenseits von ASCII.
#### Scenario: Indexordnung entspricht dem Stringvergleich
- **WHEN** zwei Texte als Schlüssel indiziert und dieselben Werte mit `<` verglichen werden
- **THEN** entspricht ihre Reihenfolge im Index dem Ergebnis des Vergleichs
### Requirement: Pufferverwaltung mit benanntem Fehler
`SETMEM` SHALL als **Funktion** die für ISAM verfügbare Puffergröße um
den übergebenen Betrag ändern und die danach verfügbare Größe liefern.
Reicht
der verfügbare Puffer für eine Operation nicht aus, MUST Laufzeitfehler 89
„ISAM - Insufficient ISAM buffers" auftreten statt eines generischen
Fehlers oder eines stillen Abbruchs. Weicht die Bedeutung von `SETMEM`
mangels DOS-Speichermodell vom Vorbild ab, MUST die Abweichung in
docs/sprachreferenz.md dokumentiert sein.
#### Scenario: Pufferfehler wird benannt
- **WHEN** eine Operation den verfügbaren ISAM-Puffer überschreitet
- **THEN** tritt Laufzeitfehler 89 auf
### Requirement: Eigenes Datenbankdateiformat
Die Datenbankdatei SHALL ein eigenes, in docs/dateiformate.md
beschriebenes Format verwenden und ist bewusst nicht binärkompatibel zu
Datenbankdateien des Vorbilds — dieselbe Linie wie bei den
UTF-32-Festtextfeldern. Das Format SHALL eine Formatversion tragen; eine
unbekannte Version MUST mit einem benannten Fehler abgewiesen werden
statt fehlinterpretiert zu werden. Eine strukturell beschädigte Datei
MUST Laufzeitfehler 88 auslösen.
#### Scenario: Unbekannte Formatversion
- **WHEN** eine Datenbankdatei mit einer höheren Formatversion geöffnet wird
- **THEN** wird sie mit einem benannten Fehler abgewiesen und nicht gelesen
#### Scenario: Beschädigte Datei
- **WHEN** eine strukturell beschädigte Datenbankdatei geöffnet wird
- **THEN** tritt Laufzeitfehler 88 auf

View File

@@ -0,0 +1,27 @@
## ADDED Requirements
### Requirement: ISAM-Anweisungen und -Funktionen in Grammatik und Signaturprüfung
Der Parser SHALL die ISAM-Anweisungen und -Funktionen des Vorbilds als
eigene Konstrukte erkennen, und die Semantik SHALL für jede eine Signatur
führen (Argumentanzahl und -typen). Der maßgebliche Umfang ergibt sich
aus dem Sprachinventar; er umfasst mindestens `CREATEINDEX`,
`DELETEINDEX`, `SETINDEX`, `GETINDEX$`, `INSERT`, `RETRIEVE`, `UPDATE`,
`DELETE`, `DELETETABLE`, `MOVEFIRST`, `MOVELAST`, `MOVENEXT`,
`MOVEPREVIOUS`, `SEEKEQ`, `SEEKGT`, `SEEKGE`, `BEGINTRANS`,
`COMMITTRANS`, `ROLLBACK`, `ROLLBACK ALL`, `SAVEPOINT`, `SETMEM` und
`BOF`. Eine fehlerhafte Verwendung MUST eine Diagnose erzeugen, die
das Element namentlich nennt; ein ISAM-Element MUST NOT als unbekannter
Bezeichner oder mit einem generischen Syntaxfehler abgewiesen werden
(Guiding Principle).
#### Scenario: ISAM-Anweisung wird als solche erkannt
- **WHEN** ein Modul `SETINDEX #1, "NachName"` enthält
- **THEN** entsteht keine Diagnose „unbekannter Bezeichner" und die Anweisung wird als ISAM-Anweisung erfasst
#### Scenario: Falsche Argumentanzahl wird namentlich gemeldet
- **WHEN** ein Modul `SEEKGT #1` ohne Schlüsselwert enthält
- **THEN** nennt die Diagnose `SEEKGT` und die erwartete Argumentanzahl
#### 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

View File

@@ -0,0 +1,78 @@
## 1. Umfang festlegen
- [ ] 1.1 ISAM-Abschnitt der Original-Hilfe auswerten und alle Anweisungen, Funktionen und Klauseln gegen die ISAM-Einträge in `docs/inventar.md` abgleichen; verifiziert dadurch, dass jedes gefundene Element im Inventar mit Status `offen` steht und im Change notiert ist, falls es über die Aufzählung im Proposal hinausgeht
- [ ] 1.2 Argumentformen je Element aus der Original-Hilfe festhalten (insbesondere die Schreibweise der Spaltenliste bei `CREATEINDEX`); verifiziert durch die Notiz im Change, die jede Signatur mit ihrer Quelle nennt
## 2. Frontend: Grammatik und Signaturen
- [ ] 2.1 ISAM-Anweisungen in die Parser-Grammatik aufnehmen; verifiziert durch Frontend-Test, dass `SETINDEX #1, "Name"` diagnose-frei parst
- [ ] 2.2 Signaturen (Argumentanzahl und -typen) für alle ISAM-Elemente in der Semantik führen; verifiziert durch Frontend-Test, dass `SEEKGT #1` ohne Schlüsselwert eine Diagnose erzeugt, die `SEEKGT` namentlich nennt
- [ ] 2.3 Satzargumente gegen den Tabellentyp prüfen; verifiziert durch Frontend-Test, dass `RETRIEVE #1, x$` „Type mismatch" meldet
- [ ] 2.4 `Unsupported`-Absenkung von `OPEN … FOR ISAM` entfernen und durch die reguläre Absenkung ersetzen; verifiziert dadurch, dass ein ISAM-Programm nicht mehr mit Laufzeitfehler 73 endet
- [ ] 2.5 Prüfen, dass kein ISAM-Element mehr als unbekannter Bezeichner behandelt wird; verifiziert durch Frontend-Test über ein Programm, das jedes Element einmal verwendet, ohne Diagnose „unbekannter Bezeichner"
## 3. Speicherschicht
- [ ] 3.1 `redb` als Abhängigkeit aufnehmen, auf das ISAM-Modul in `tb-runtime` beschränkt; verifiziert dadurch, dass `cargo build -p tb-runtime` durchläuft
- [ ] 3.2 Datenbankdatei mit Formatversion anlegen und öffnen; verifiziert durch Test, dass eine Datei mit höherer Formatversion mit benanntem Fehler abgewiesen und eine strukturell beschädigte Datei mit Laufzeitfehler 88 abgelehnt wird
- [ ] 3.3 Satztabelle `satz-id → Satzbytes` mit monoton vergebenen, nie wiederverwendeten IDs; verifiziert durch Test, dass eine nach `DELETE` neu vergebene ID nicht vorkommt
- [ ] 3.4 Satzserialisierung auf den Recordpuffern und der UTF-32-Festtextkodierung aus `datei-eio` aufsetzen; verifiziert durch Roundtrip-Test über alle Feldtypen des Satztyps
- [ ] 3.5 Ordnungserhaltende Schlüsselkodierung je Spaltentyp (Zahlen vorzeichenrichtig fester Breite, Text nach Codepoints, mehrspaltig verkettet mit eindeutigem Trenner, absteigend durch Byte-Invertierung); verifiziert durch Eigenschaftstest über zufällige Wertepaare, dass der Byte-Vergleich der Kodierung dem fachlichen Vergleich entspricht
- [ ] 3.6 Sortierordnung (Unicode-Codepoints, Groß-/Kleinschreibung unterschieden) in docs/sprachreferenz.md festhalten; verifiziert durch den Abschnittseintrag
- [ ] 3.7 Datenbankdateiformat in docs/dateiformate.md beschreiben inklusive Formatversion und bewusster Inkompatibilität zum Vorbild; verifiziert durch den Abschnittseintrag
## 4. Tabellen
- [ ] 4.1 `OPEN … FOR ISAM typname tabellenname AS #n` an die Dateitabelle binden, Satzlayout aus dem benannten Typ ableiten; verifiziert durch Korpustest, dass die Dateinummer danach nutzbar ist
- [ ] 4.2 Tabelle beim ersten Öffnen anlegen; verifiziert durch Korpustest gegen eine Datenbank ohne diese Tabelle
- [ ] 4.3 Layoutabgleich bei bestehender Tabelle mit Fehler 88 bei Abweichung; verifiziert durch Korpustest mit unpassendem Typ
- [ ] 4.4 Ungültige Datenbank- und Tabellennamen mit Fehler 81 abweisen; verifiziert durch Korpustest
- [ ] 4.5 `CLOSE` löst die Bindung und schreibt ausstehende Änderungen fest; verifiziert durch Korpustest, dass die Sätze nach erneutem Öffnen vorhanden sind
- [ ] 4.6 `DELETETABLE` entfernt Tabelle samt Indizes, Fehler 82 bei fehlender Tabelle; verifiziert durch Korpustest für beide Fälle
## 5. Indizes
- [ ] 5.1 Indextabellen `Schlüsselbytes → satz-id` je Index anlegen; verifiziert durch Test, dass die Einträge in Schlüsselreihenfolge stehen
- [ ] 5.2 `CREATEINDEX` mit einer und mit mehreren Spalten, eindeutig und mehrdeutig, auf- und absteigend; verifiziert durch Korpustest je Variante
- [ ] 5.3 `DELETEINDEX`; verifiziert durch Korpustest, dass der Index danach nicht mehr gesetzt werden kann
- [ ] 5.4 `SETINDEX` setzt den aktiven Index und den Cursor auf unpositioniert, `GETINDEX$` liefert ihn zurück; verifiziert durch Korpustest
- [ ] 5.5 Fehler 83 bei unbekanntem Index und Fehler 84 bei unbekannter Spalte; verifiziert durch Korpustest für beide Fälle
## 6. Cursor
- [ ] 6.1 Cursor je Dateinummer als Wert führen (aktiver Index, letzte Satz-ID, Kennzeichen unpositioniert); verifiziert durch Unit-Test, dass der Cursor eine Satzänderung überlebt
- [ ] 6.2 `MOVEFIRST`, `MOVELAST`, `MOVENEXT`, `MOVEPREVIOUS` in der Ordnung des aktiven Index; verifiziert durch Korpustest, dass ein Durchlauf die Sätze in Indexreihenfolge liefert
- [ ] 6.3 Bewegung ohne aktiven Index folgt der Einfügereihenfolge; verifiziert durch Korpustest
- [ ] 6.4 `EOF` am Ende und `BOF` am Anfang, Cursor danach jeweils unpositioniert; verifiziert durch je einen Korpustest für `MOVENEXT` am Ende und `MOVEPREVIOUS` am Anfang
- [ ] 6.5 `SEEKEQ`, `SEEKGT`, `SEEKGE` inklusive Präfixsuche über einen mehrspaltigen Index; verifiziert durch Korpustest mit den Schlüsseln 10, 20, 30 und einer Präfixsuche
- [ ] 6.6 Erfolglose Suche setzt `EOF` und lässt den Cursor unpositioniert; verifiziert durch Korpustest
- [ ] 6.7 Suche ohne aktiven Index löst Fehler 83 aus; verifiziert durch Korpustest
## 7. Satzoperationen
- [ ] 7.1 `INSERT` und `RETRIEVE`; verifiziert durch Korpustest, dass ein eingefügter Satz per `SEEKEQ` gefunden und feldgleich gelesen wird
- [ ] 7.2 `UPDATE` und `DELETE`, Cursor nach `DELETE` unpositioniert; verifiziert durch Korpustest
- [ ] 7.3 Fehler 85 bei `RETRIEVE`/`UPDATE`/`DELETE` ohne positionierten Cursor; verifiziert durch je einen Korpustest
- [ ] 7.4 Eindeutigkeitsprüfung mit Fehler 86 und unverändertem Satzbestand; verifiziert durch Korpustest, der nach dem Fehler den Bestand nachzählt
- [ ] 7.5 Alle Indizes nach jeder Satzoperation nachführen; verifiziert durch Korpustest, dass ein Satz nach Änderung der indizierten Spalte an neuer Position im Durchlauf erscheint
## 8. Transaktionen
- [ ] 8.1 `BEGINTRANS`/`COMMITTRANS` auf einer Schreibtransaktion; verifiziert durch Korpustest, dass die Änderungen nach erneutem Öffnen vorhanden sind
- [ ] 8.2 Rücknahmeprotokoll der Änderungen innerhalb einer Transaktion; verifiziert durch Unit-Test, dass jede Satzoperation einen Protokolleintrag erzeugt
- [ ] 8.3 `ROLLBACK ALL` stellt Sätze und Indizes wieder her, beendet die Transaktion und setzt den Cursor auf unpositioniert; verifiziert durch Korpustest mit Einfügen, Löschen und anschließendem Durchlauf
- [ ] 8.4 `SAVEPOINT` als Funktion (liefert Kennung) und `ROLLBACK kennung`; verifiziert durch Korpustest, dass Änderungen vor dem Punkt erhalten bleiben
## 9. Übrige Elemente
- [ ] 9.2 `SETMEM` als Funktion mit echter Puffergrenze und Fehler 89 beim Überschreiten; verifiziert durch Korpustest, der die Grenze niedrig setzt und den Fehler auslöst
- [ ] 9.3 Vorgabewert und Bedeutung von `SETMEM` sowie die Abweichung vom DOS-Speichermodell in docs/sprachreferenz.md festhalten; verifiziert durch den Abschnittseintrag
- [ ] 9.4 Indexordnung gegen den Stringvergleich des Dialekts prüfen; verifiziert durch Korpustest, dass Indexreihenfolge und `<`-Vergleich übereinstimmen
## 10. Abschluss
- [ ] 10.1 ISAM-Einträge in `docs/inventar.md` auf `implementiert` setzen; verifiziert dadurch, dass der Abgleichstest aus `phase-3-runtime-bildschirm` grün bleibt
- [ ] 10.2 `docs/bibliothek.md` um die ISAM-Elemente erweitern; verifiziert dadurch, dass jedes umgesetzte Element dort einen Eintrag hat
- [ ] 10.3 Korpus-ISAM-Programme in einem temporären Arbeitsverzeichnis ausführen; verifiziert dadurch, dass nach `cargo test` `git status` sauber ist
- [ ] 10.4 Vollständige Suite auf allen Zielplattformen ausführen; verifiziert dadurch, dass `cargo test --workspace` grün ist
- [ ] 10.5 PLAN.md Phase 3 als abgeschlossen markieren; verifiziert durch den aktualisierten Planabschnitt