## 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 81–89 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.