# datei-eio Specification

## Purpose
Die Datei-E/A stellt die Dateiverwaltung des Dialekts bereit: sequenzieller,
wahlfreier und binärer Zugriff über Dateinummern, Record-Puffer mit
Festtextfeldern, Positions- und Statusfunktionen sowie die
Dateisystem-Anweisungen — plattformübergreifend und mit den
Laufzeitfehlern des Vorbilds.

## Requirements

### Requirement: Dateinummern und Öffnen in drei Zugriffsarten
`OPEN` SHALL in beiden Syntaxen des Vorbilds (Klausel- und Kurzform) die
Zugriffsarten sequenziell (`INPUT`, `OUTPUT`, `APPEND`), wahlfrei
(`RANDOM`) und binär (`BINARY`) unterstützen und die Datei an eine
Dateinummer binden. `FREEFILE` SHALL die kleinste freie Dateinummer
liefern. `CLOSE` ohne Argument SHALL alle offenen Dateien schließen,
`RESET` ebenso. Eine bereits belegte Dateinummer MUST Laufzeitfehler 55
„File already open" auslösen, eine nicht geöffnete Nummer Fehler 52
„Bad file name or number", eine fehlende Datei bei `INPUT` Fehler 53
„File not found".

FREEFILE SHALL die gemeinsame Belegung durch sequenzielle, wahlfreie, binäre und ISAM-Bindungen berücksichtigen.

#### Scenario: Sequenzielles Schreiben und Lesen
- **WHEN** eine Datei mit `OPEN "d.txt" FOR OUTPUT AS #1` beschrieben, geschlossen und mit `FOR INPUT` wieder geöffnet wird
- **THEN** liefert das Lesen genau die geschriebenen Zeilen

#### Scenario: Doppeltes Öffnen derselben Nummer
- **WHEN** `OPEN` eine bereits offene Dateinummer verwendet
- **THEN** tritt Laufzeitfehler 55 auf

#### Scenario: Freie Dateinummer
- **WHEN** `#1` offen ist und `FREEFILE` aufgerufen wird
- **THEN** liefert `FREEFILE` eine Nummer, die noch nicht belegt ist

#### Scenario: ISAM belegt eine Dateinummer
- **WHEN** eine ISAM-Tabelle auf #1 und sonst keine Datei offen ist
- **THEN** liefert FREEFILE den Wert 2

### Requirement: Sequenzielle Datenübertragung
`PRINT #` SHALL wie `PRINT` formatieren (Druckzonen, Zahlendarstellung mit
Vorzeichenspalte), `WRITE #` SHALL Elemente komma-getrennt mit Strings in
Anführungszeichen schreiben. `INPUT #` SHALL komma-getrennte Felder lesen
und dabei Anführungszeichen auswerten, `LINE INPUT #` die ganze Zeile ohne
Zeilenende. `EOF` SHALL das Erreichen des Dateiendes melden; Lesen über das
Dateiende hinaus MUST Laufzeitfehler 62 „Input past end of file" auslösen.

INPUT # SHALL nicht konsumierte Felder einer Zeile für spätere Aufrufe bewahren und bei Bedarf über Zeilen hinweg weiterlesen. Fehlende geforderte Felder am Dateiende MUST Fehler 62 auslösen statt als leere Ersatzwerte zu gelten.

#### Scenario: WRITE # und INPUT # sind gegenläufig
- **WHEN** `WRITE #1, "a,b", 5` geschrieben und die Zeile mit `INPUT #1, s$, n` gelesen wird
- **THEN** enthält `s$` den Text `a,b` und `n` den Wert 5

#### Scenario: Lesen über das Dateiende
- **WHEN** `INPUT #1, x` nach dem letzten Datensatz ausgeführt wird
- **THEN** tritt Laufzeitfehler 62 auf

#### Scenario: Felder über zwei Aufrufe
- **WHEN** WRITE #1, "a", "b" geschrieben und nach Wiederöffnung zweimal INPUT #1 mit je einer Stringvariablen ausgeführt wird
- **THEN** liefern die Aufrufe a und b

#### Scenario: Zu wenige Felder
- **WHEN** nur ein Feld vorhanden ist und INPUT #1 zwei Felder verlangt
- **THEN** tritt Fehler 62 auf

### Requirement: Wahlfreier Zugriff mit Record-Puffern
`OPEN … FOR RANDOM … LEN = n` SHALL Datensätze fester Länge bereitstellen.
`GET` und `PUT` SHALL einen Datensatz an einer 1-basierten Recordnummer
lesen bzw. schreiben; ohne Nummer gilt der Satz nach der letzten Operation.
`FIELD` SHALL den Recordpuffer in benannte Festtextfelder aufteilen,
`LSET`/`RSET` SHALL links- bzw. rechtsbündig in ein Feld schreiben und
überlange Werte abschneiden. Alternativ SHALL `GET`/`PUT` mit einer
Variablen eines benutzerdefinierten Typs arbeiten.

#### Scenario: Datensatz schreiben und an gleicher Stelle lesen
- **WHEN** ein Record an Position 3 geschrieben und anschließend von Position 3 gelesen wird
- **THEN** stimmen alle Feldwerte mit den geschriebenen überein

#### Scenario: RSET schneidet ab
- **WHEN** in ein Feld der Länge 5 mit `RSET` der Text `ABCDEFG` geschrieben wird
- **THEN** enthält das Feld genau 5 Zeichen

### Requirement: Record-Konvertierungsfunktionen
Die Bibliothek SHALL die Konvertierungsfunktionen für Recordpuffer
bereitstellen: `MKI$`, `MKL$`, `MKS$`, `MKD$` und `MKC$` wandeln einen
numerischen Wert in seine Byte-Darstellung als String, `CVI`, `CVL`,
`CVS`, `CVD` und `CVC` wandeln zurück. Jedes Paar MUST gegenläufig sein.
Ein Argument, dessen Länge nicht zur Zielbreite passt, MUST
Laufzeitfehler 5 „Illegal function call" auslösen. `MKSMBF$`, `MKDMBF$`,
`CVSMBF` und `CVDMBF` SHALL dieselbe Rolle für das
Microsoft-Binärformat älterer BASIC-Versionen erfüllen; ist dieses
Format ein deklariertes Non-Feature, MUST die Ablehnung namentlich
erfolgen und in der Sprachreferenz unter „Abweichungen" stehen.

Die Argumentlänge der CV-Funktionen SHALL exakt ihrer Zielbreite entsprechen, nicht nur einer Mindestbreite.

#### Scenario: MK und CV sind gegenläufig
- **WHEN** `CVL(MKL$(123456))` ausgewertet wird
- **THEN** ist das Ergebnis 123456

#### Scenario: Falsche Argumentlänge
- **WHEN** `CVI("x")` mit einem ein Zeichen langen String ausgewertet wird
- **THEN** tritt Laufzeitfehler 5 auf

#### Scenario: Zu langes CVI-Argument
- **WHEN** CVI("abc") ausgewertet wird
- **THEN** tritt Laufzeitfehler 5 auf

### Requirement: Festtextfelder als UTF-32 mit fester Recordlänge
Feste Strings (`STRING * n`) in Recordpuffern SHALL als UTF-32 gespeichert
werden — 4 Bytes je Zeichen, damit `STRING * n` stets genau `4 * n` Bytes
belegt und die Recordlänge unabhängig vom Zeicheninhalt konstant bleibt.
Die daraus folgende Binär-Inkompatibilität zu Dateien des Vorbilds ist
beschlossen (2026-09-02) und MUST in der Sprachreferenz unter
„Abweichungen" dokumentiert sein.

#### Scenario: Recordlänge unabhängig vom Inhalt
- **WHEN** in ein Feld `STRING * 10` einmal `abc` und einmal `äöüßÄÖÜ` geschrieben wird
- **THEN** belegt der Datensatz in beiden Fällen dieselbe Byteanzahl und der Folgesatz beginnt an derselben Position

### Requirement: Binärzugriff auf Byteebene
`OPEN … FOR BINARY` SHALL byteweise Positionierung erlauben. `GET`/`PUT`
SHALL an einer 1-basierten Byteposition lesen bzw. schreiben, wobei die
übertragene Länge der Größe der Zielvariablen entspricht. `SEEK` als
Anweisung SHALL die Position setzen, `SEEK` als Funktion sie liefern.

#### Scenario: Positioniertes Byte überschreiben
- **WHEN** in einer Binärdatei an Position 4 ein Byte geschrieben und anschließend gelesen wird
- **THEN** liefert das Lesen den geschriebenen Wert und die übrigen Bytes bleiben unverändert

### Requirement: Positions- und Statusfunktionen
`LOF` SHALL die Dateigröße in Bytes liefern, `LOC` die aktuelle Position
in der Einheit der Zugriffsart (Datensatz bei `RANDOM`, Byte bei
`BINARY`), `SEEK` die nächste Lese-/Schreibposition und `FILEATTR` den
Modus bzw. das Betriebssystem-Handle. Alle Funktionen MUST bei einer nicht
geöffneten Dateinummer Laufzeitfehler 52 auslösen.

#### Scenario: Größe nach dem Schreiben
- **WHEN** 100 Bytes geschrieben wurden und `LOF(1)` aufgerufen wird
- **THEN** liefert `LOF(1)` den Wert 100

#### Scenario: Statusfunktion auf geschlossener Datei
- **WHEN** `LOF(9)` ohne offene Datei 9 aufgerufen wird
- **THEN** tritt Laufzeitfehler 52 auf

### Requirement: Satzsperren
`LOCK` und `UNLOCK` SHALL einen Datensatz-, Byte- oder den gesamten
Dateibereich sperren und wieder freigeben. Auf Plattformen ohne
entsprechende Sperrmechanik MUST das Verhalten in der Sprachreferenz unter
„Abweichungen" dokumentiert sein statt still abzuweichen.

#### Scenario: Sperren und Freigeben
- **WHEN** ein Datensatzbereich gesperrt und anschließend mit denselben Grenzen freigegeben wird
- **THEN** läuft die Folgeoperation auf diesem Bereich ohne Fehler

### Requirement: Dateisystem-Anweisungen mit plattformübergreifender Pfadsemantik
`KILL`, `NAME … AS`, `FILES`, `CHDIR`, `CHDRIVE`, `MKDIR`, `RMDIR`,
`CURDIR$`, `DIR$` sowie `SHELL` als Anweisung **und** als Funktion SHALL
verfügbar sein. `CURDIR$` SHALL das aktuelle Verzeichnis liefern, `DIR$`
den ersten bzw. — bei leerem Argument — den nächsten Treffer eines
Suchmusters und den leeren String, wenn keiner mehr folgt. Die
`SHELL`-Funktion SHALL den Beendigungsstatus des aufgerufenen Prozesses
liefern. `CHDRIVE` SHALL auf Plattformen ohne Laufwerksbuchstaben
namentlich abgewiesen oder als Non-Feature dokumentiert werden. Pfade SHALL sowohl `\` als auch `/` als Trennzeichen
akzeptieren und auf die Konvention der laufenden Plattform abgebildet
werden; Laufwerksbuchstaben SHALL auf Nicht-Windows-Plattformen als
gewöhnlicher Namensbestandteil behandelt oder mit benannter Ablehnung
abgewiesen werden. Fehlende Dateien MUST Fehler 53 auslösen, fehlende
Pfade Fehler 76 „Path not found".

#### Scenario: Rückwärts-Schrägstrich auf Unix
- **WHEN** `OPEN "unter\d.txt" FOR OUTPUT AS #1` auf einer Unix-Plattform ausgeführt wird
- **THEN** entsteht die Datei `d.txt` im Verzeichnis `unter`

#### Scenario: Verzeichnis durchlaufen
- **WHEN** `DIR$("*.txt")` und danach `DIR$("")` wiederholt ausgewertet werden
- **THEN** liefern sie nacheinander alle passenden Namen und zuletzt den leeren String

#### Scenario: Fehlender Pfad
- **WHEN** `MKDIR` in einem nicht existierenden Elternverzeichnis aufgerufen wird
- **THEN** tritt Laufzeitfehler 76 auf

#### Scenario: OPEN mit fehlendem Elternpfad
- **WHEN** OPEN FOR INPUT einen Dateinamen in einem nicht vorhandenen Elternverzeichnis verwendet
- **THEN** tritt Fehler 76 auf; fehlt nur die Datei in einem vorhandenen Verzeichnis, tritt Fehler 53 auf

### Requirement: LPRINT als Druckausgabe
`LPRINT` und `LPRINT USING` SHALL wie `PRINT` formatieren und in einen
Druckerkanal schreiben. `LPOS` SHALL die aktuelle Spalte im
Druckerpuffer liefern. Existiert auf der Plattform kein Druckerkanal,
MUST das Ziel (Datei, Standardausgabe oder Ablehnung) in der
Sprachreferenz unter „Abweichungen" dokumentiert sein.

#### Scenario: LPRINT formatiert wie PRINT
- **WHEN** `LPRINT 1; 2` ausgeführt wird
- **THEN** entspricht der erzeugte Text der Ausgabe von `PRINT 1; 2`

#### Scenario: Druckerspalte
- **WHEN** nach `LPRINT "abc";` die Funktion `LPOS(0)` ausgewertet wird
- **THEN** liefert sie die Spalte hinter dem ausgegebenen Text
