OpenSpec: phase-3-isam archiviert, Delta-Specs gesynct

Neue Capability isam-datenbank mit 9 Anforderungen und 25 Szenarien:
Tabellenbindung an eine Dateinummer, Indexverwaltung, Cursorbewegung,
Schluesselsuche, Satzoperationen, Transaktionen mit Ruecknahme,
Vergleichsreihenfolge, Pufferverwaltung und das eigene Dateiformat.

sprach-frontend um eine Anforderung erweitert: ISAM-Anweisungen und
-Funktionen in Grammatik und Signaturpruefung.

Die Delta-Spec wurde vor dem Archivieren an die Original-Hilfe
angeglichen -- Cursorlage nach SETINDEX und DELETE, Fehlercode der Suche
ueber den NULL-Index. Die Change-Notiz umfang-und-signaturen.md haelt
Umfangsabgleich, Argumentformen mit Quellenangabe je Themenseite und die
Verifikationsbefunde fest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 11:38:10 +02:00
parent 9b58e0ec43
commit e49a231a68
9 changed files with 514 additions and 62 deletions

View File

@@ -0,0 +1,193 @@
# Umfang und Argumentformen der ISAM-Elemente (Aufgaben 1.1, 1.2)
**Quelle.** `bas7advr.hlp` (BASIC 7 Advisor), Themenseiten unter
https://dos-help.soulsphere.org/bas7advr.hlp/ (abgerufen 2026-09-04).
Die Themenliste selbst ist bereits in
`openspec/changes/archive/2026-09-04-phase-3-runtime-bildschirm/rohliste-original-hilfe.md`
wortgetreu festgehalten; hier kommen die Syntaxzeilen der Einzelthemen
dazu.
## 1.1 Umfangsabgleich
Der ISAM-Abschnitt der Original-Hilfe führt genau diese Themen (Zeilen der
Rohliste in Klammern):
| Thema der Rohliste | Zeile | Elemente |
|---|---|---|
| `BEGINTRANS Statement` | 26 | `BEGINTRANS` |
| `BOF Function` | 28 | `BOF` |
| `COMMITTRANS Statement` | 47 | `COMMITTRANS` |
| `CREATEINDEX Statement` | 51 | `CREATEINDEX` |
| `DELETE Statement` | 64 | `DELETE` |
| `DELETEINDEX Statement` | 65 | `DELETEINDEX` |
| `DELETETABLE Statement` | 66 | `DELETETABLE` |
| `GETINDEX$ Function` | 99 | `GETINDEX$` |
| `INSERT Statement` | 110 | `INSERT` |
| `MOVEFIRST, MOVELAST, MOVENEXT, MOVEPREVIOUS Statements` | 145 | 4 Elemente |
| `RETRIEVE Statement` | 195 | `RETRIEVE` |
| `ROLLBACK, ROLLBACK ALL Statements` | 200 | `ROLLBACK` |
| `SAVEPOINT Function` | 206 | `SAVEPOINT` |
| `SEEKGT, SEEKGE, SEEKEQ Statements` | 211 | 3 Elemente |
| `SETINDEX Statement` | 213 | `SETINDEX` |
| `SETMEM Function` | 214 | `SETMEM` |
| `UPDATE Statement` | 259 | `UPDATE` |
Das sind **22 Elemente**. `docs/inventar.md` führt in der Gruppe `ISAM`
genau dieselben 22 Namen, alle mit Status `offen`. Der Umfang geht damit
**nicht** über die Aufzählung im Proposal hinaus — die Liste im Proposal
ist deckungsgleich.
**Kein eigenes Element:** `TEXTCOMP` kommt in der Themenliste nicht vor
(bestätigt den Befund aus `design.md`, D4). Ebenso wenig ein Thema für
Sicherungspunkte außer `SAVEPOINT` oder für Datenbankwartung.
**Elemente mit zusätzlichem ISAM-Verhalten**, die schon anderweitig
implementiert sind und deshalb keinen eigenen Inventareintrag der Gruppe
`ISAM` haben: `OPEN` (Klausel `FOR ISAM`), `CLOSE`, `EOF`, `LOF`, `LOC`.
## 1.2 Argumentformen je Element
Syntaxzeilen wortgetreu aus der jeweiligen Themenseite der Original-Hilfe.
| Element | Syntax (Original-Hilfe) | Themenseite |
|---|---|---|
| `OPEN … FOR ISAM` | `OPEN database$ FOR ISAM tabletype tablename$ AS [#]filenumber%` | `x_dot_opfior.html` |
| `CREATEINDEX` | `CREATEINDEX [#]filenumber%,indexname$,unique%,columnname$[,columnname$]` | `x_dot_CREATEINDEXr.html` |
| `DELETEINDEX` | `DELETEINDEX [#]filenumber%,indexname$` | `x_dot_DELETEINDEXr.html` |
| `SETINDEX` | `SETINDEX [#]filenumber%[,indexname$]` | `x_dot_SETINDEXr.html` |
| `GETINDEX$` | `GETINDEX$ (filenumber%)` | `x_dot_GETINDEX$r.html` |
| `INSERT` | `INSERT [#]filenumber%,recordvariable` | `x_dot_INSERTr.html` |
| `RETRIEVE` | `RETRIEVE [#]filenumber%,recordvariable` | `x_dot_RETRIEVEr.html` |
| `UPDATE` | `UPDATE [#]filenumber%,recordvariable` | `x_dot_UPDATEr.html` |
| `DELETE` | `DELETE [#]filenumber%` | `x_dot_DELETEr.html` |
| `DELETETABLE` | `DELETETABLE database$,tablename$` | `x_dot_DELETETABLEr.html` |
| `MOVEFIRST` u. a. | `MOVEFIRST [#]filenumber%` (ebenso `MOVELAST`, `MOVENEXT`, `MOVEPREVIOUS`) | `x_dot_MOVEFIRSTr.html` |
| `SEEKGT`/`SEEKGE`/`SEEKEQ` | `SEEKGT [#]filenumber% ,keyvalue [,keyvalue]…` | `x_dot_seekisamr.html` |
| `BEGINTRANS` | `BEGINTRANS` | `x_dot_BEGINTRANSr.html` |
| `COMMITTRANS` | `COMMITTRANS` | `x_dot_COMMITTRANSr.html` |
| `ROLLBACK` | `ROLLBACK [savepoint%]` bzw. `ROLLBACK ALL` | `x_dot_ROLLBACKr.html` |
| `SAVEPOINT` | `SAVEPOINT` (Funktion, liefert eine ganze Zahl) | `x_dot_SAVEPOINTr.html` |
| `SETMEM` | `SETMEM(numeric-expression&)` | `x_dot_setmemr.html` |
| `BOF` | `BOF(filenumber%)` | `x_dot_BOFr.html` |
### Befund zur offenen Frage aus design.md
Die Spaltenliste bei `CREATEINDEX` ist **keine** Zeichenkette mit
Trennzeichen, sondern eine **Folge einzelner Stringargumente**:
`columnname$[,columnname$]…`. Die Absenkung übernimmt diese Form
unverändert; `CREATEINDEX` ist damit variadisch (4 bis 12 Argumente,
also bis zu neun Indexspalten).
### Ergänzung über die Original-Hilfe hinaus: absteigende Spalten
Die Original-Hilfe kennt bei `CREATEINDEX` **keine** Angabe der
Sortierrichtung — Indizes sind dort immer aufsteigend. Die Anforderung
„Indexverwaltung" dieses Changes verlangt aber auf- **und** absteigende
Spalten. Gewählte Schreibweise: ein vorangestelltes `-` am Spaltennamen.
```basic
CREATEINDEX #1, "NachPreis", 0, "Nachname", "-Preis"
```
Sie ist eindeutig und kollisionsfrei, weil ein Feldname einer
`TYPE`-Anweisung nie mit `-` beginnen kann; Programme des Vorbilds
bleiben dadurch unverändert gültig. Festgehalten in
docs/sprachreferenz.md und docs/bibliothek.md.
### Cursor-Semantik folgt dem Vorbild
Die Delta-Spezifikation schrieb ursprünglich an zwei Stellen eine andere
Cursorlage vor, als die Original-Hilfe beschreibt. Entscheidung vom
2026-09-04: **bei Semantik und Syntax gewinnt das Vorbild, wo immer das
möglich ist.** Die Spezifikation wurde daraufhin angeglichen, nicht die
Umsetzung:
| Situation | Original-Hilfe (und jetzt auch Terminal Basic) |
|---|---|
| nach `SETINDEX` | „the current record is the first record according to that index" — der erste Satz der neuen Ordnung ist der aktuelle |
| nach `DELETE` | „the record following the deleted record becomes the current record"; war der gelöschte der letzte, steht der Cursor am Ende der Tabelle ohne aktuellen Satz |
`SETINDEX` ohne Indexnamen bzw. mit `""` wählt den NULL-Index: „If you
omit the `indexname$` argument or specify double quotes for it, the
current index is the NULL index" — er „represents the order in which
records were added to the file".
### Präfixsuche: Verhalten je Suchart
Die Original-Hilfe unterscheidet die Sucharten bei zu wenigen
Schlüsselwerten ausdrücklich; die Umsetzung übernimmt das:
- `SEEKEQ` mit unvollständigem Schlüssel **schlägt immer fehl**
(„SEEKEQ with insufficient keyvalues always fails").
- `SEEKGE` sucht mit den vorhandenen Werten als Präfix.
- `SEEKGT` mit unvollständigem Schlüssel positioniert auf dem **ersten
passenden** Satz — also wie `SEEKGE`, nicht hinter der Präfixgruppe.
### Fehler 87: Auslöser ist die Suche über den NULL-Index
Der Proposal-Abschnitt „ISAM-Fehlersemantik" nennt Code 87 („ISAM -
Invalid operation on NULL index"). Die Themenseite zur `SEEK`-Familie
schweigt zur Lage ohne aktiven Index — der Fehlerkatalog des Vorbilds
benennt sie aber genau: der NULL-Index führt keine Schlüssel, sondern nur
die Einfügereihenfolge, eine Schlüsselsuche über ihn ist deshalb die
„unzulässige Operation auf dem NULL-Index".
Die Delta-Spezifikation nannte hier zunächst Fehler 83; sie ist mit der
Entscheidung vom 2026-09-04 („bei Semantik gewinnt das Vorbild") auf 87
angeglichen. **83** bleibt dem wirklich unbekannten Indexnamen vorbehalten
und wird von `SETINDEX` und `DELETEINDEX` ausgelöst. Damit hat jeder Code
von 81 bis 89 einen Auslöser.
Bewegung über den NULL-Index bleibt zulässig (`MOVE`-Familie in
Einfügereihenfolge) — die Original-Hilfe beschreibt den NULL-Index
ausdrücklich als Reihenfolge, nicht als Fehlerzustand.
## Befunde der Verifikation (2026-09-04)
Vier Befunde aus `/opsx:verify`, alle umgesetzt.
### `CLOSE` beendet keine Transaktion
Ursprünglich schrieb `CLOSE` eine laufende Transaktion fest, um Aufgabe 4.5
(„schreibt ausstehende Änderungen fest") zu erfüllen. Das war zu weit
gegriffen und hatte zwei beobachtbare Folgen:
```basic
BEGINTRANS : INSERT #1, p : CLOSE #1 ' Satz blieb erhalten
BEGINTRANS : INSERT #1, p : END ' Satz verfiel
```
und, schlimmer, über Dateinummern hinweg:
```basic
OPEN AS #1 : OPEN AS #2
BEGINTRANS : INSERT #1, p : CLOSE #2 ' schrieb die Änderung an #1 fest
ROLLBACK ALL ' → Fehler 5, Satz überlebte
```
Aufgabe 4.5 ist auch ohne diese Festschreibung erfüllt, weil jede Operation
außerhalb einer Transaktion für sich eine Transaktion ist. `CLOSE` löst
seither nur die Bindung; über das Ende einer Transaktion entscheiden allein
`COMMITTRANS` und `ROLLBACK ALL`, und eine beim Programmende offene
Transaktion verfällt — in beiden Wegen gleich. Delta-Spec, Aufgabe 4.5 und
die Referenzen sind angeglichen; zwei Einheitentests halten es fest.
### `CREATEINDEX` ohne Obergrenze der Spaltenzahl
Die Signatur begrenzte den Index auf neun Spalten. Die Original-Hilfe nennt
keine Obergrenze, also darf die Signatur auch keine setzen: die
Spaltenargumente werden jetzt wie bei `INSTR` gesondert geprüft (ab dem
vierten Argument je ein String), die Argumentzahl ist offen.
### Kein Index-Torso nach Fehler 86
Scheiterte `CREATEINDEX` an der Eindeutigkeit, blieb innerhalb einer
Transaktion eine halb gefüllte Indextabelle zurück, die keine
Indexdefinition mehr nannte. `index_anlegen` bildet jetzt erst alle
Schlüssel und prüft die Eindeutigkeit, bevor es schreibt — dieselbe
Reihenfolge wie in `satz_schreiben`.
### Toter Klon in `RETRIEVE`
`satz_lesen` klonte das Tabellenlayout je Aufruf, ohne es zu benutzen.
Entfernt.