# Dateiformate

Terminal Basic liest und schreibt die Textformate des Vorbilds, durchgängig
in UTF-8 (Abweichung: das Vorbild nutzte die DOS-Codepage). Binäre
„Fast-Load"-Formulare des Vorbilds werden nur durch den Konverter gelesen,
aber nie geschrieben.

## Quelltext: `.BAS`

Reiner Text, eine Anweisung(sfolge) pro Zeile. Optionale Kopfzeilen der IDE
(`DECLARE`-Prototypen) werden beim Speichern erzeugt/aktualisiert.
Metabefehle in Kommentaren: `'$INCLUDE: 'datei.bi'`, `'$STATIC`, `'$DYNAMIC`.

## Formular: `.FRM`

Das Vorbild kannte zwei Speicherformate: **binär** („Fast load and save",
Standard) und **Text** („Readable by other programs"). Terminal Basic schreibt
das Textformat und liest das Binärformat ausschließlich zur Konvertierung.

Textformat, zwei Abschnitte: Formular-Beschreibung, dann Code. Schema wie
beim Windows-Schwesterprodukt: `VERSION`-Zeile, verschachtelte
`Begin <Klassenname> <Name> … End`-Blöcke (Klassennamen siehe
forms-referenz.md), `Eigenschaft = Wert`-Zeilen. Der Parser des Vorbilds
akzeptiert Version 1.00 und 2.00 (Import aus dem Windows-Produkt via
Übersetzer).

```
VERSION 1.00
Begin Form Form1
   Caption          =  "Beispiel"
   Height          =  15
   Left            =  10
   Top             =  4
   Width           =  50
   Begin CommandButton cmdOK
      Caption          =  "&OK"
      Height          =  1
      Left            =  18
      Top             =  11
      Width           =  10
   End
End

SUB cmdOK_Click ()
    UNLOAD Form1
END SUB
```

- `VERSION`-Zeile, dann verschachtelte `Begin <Typ> <Name> … End`-Blöcke
  mit `Eigenschaft = Wert`-Zeilen (Strings in `"…"`).
- Danach normaler BASIC-Code des Formular-Moduls.
- Terminal Basic schreibt `VERSION` groß, Klassen- und Eigenschaftsnamen in der
  Schreibweise der Forms-Referenz und drei Leerzeichen je Blockebene.
- Der Eigenschaftsname belegt 16 Spalten; danach folgen ` =  ` und der Wert.
- Bei kanonischer Ausgabe stehen Eigenschaften in der alphabetischen Reihenfolge der Forms-Referenz
  und werden nur geschrieben, wenn ihr Wert vom Vorgabewert der Klasse
  abweicht. Ein explizites `Index = 0` bleibt erhalten: Es deklariert ein
  Control-Array und ist daher eine Strukturangabe, kein auslassbarer Default.
- Unverändert gelesene Dateien werden bytegleich zurückgegeben; für neu
  erzeugte oder veränderte Beschreibungen gilt die kanonische Form oben.
  Explizite Vorgabewerte (etwa `Enabled = -1`) bleiben im unveränderten
  Original erhalten und werden erst bei kanonischer Ausgabe ausgelassen.
- Zeichenketten verdoppeln ein enthaltenes `"`. Wahrheitswerte werden als `-1`
  und `0` geschrieben.

Die minimale kanonische Referenz ist vollständig:

```
VERSION 1.00
Begin Form Form1
   Caption          =  "Beispiel"
   Height           =  15
   Begin CommandButton cmdOK
      Caption          =  "&OK"
   End
End
```

### Binäres VBDOS-1.0-Formular

Der Binärleser ist ausschließlich ein Importpfad. Er erkennt keine Datei an der
Endung, sondern an `FC 08 01 00`. Aus den Originaldateien und dem
`cout/vbdos`-Bestand ergibt sich folgender Aufbau:

| Bereich | Kodierung | Bedeutung |
|---|---|---|
| `0x0000` | `FC 08 01 00` | Kennung und VBDOS-Formatversion |
| `0x001c` | `u16` little-endian + `0x16` | Dateiposition des Objektkatalogs |
| `0x001e` | `u16` little-endian | Länge des versionsgebundenen Objekt-/Eigenschaftsbereichs |
| `0x0020` | Wurzelkopf und klassenabhängiger Datensatz; danach je Objekt ein 7-Byte-Kopf und der klassenabhängige Datensatz | Der Objektkopf nennt Katalogindex, Klasse und Flags; der Datensatz enthält Eigenschaften, Array-Index und Containerverweis |
| danach | Folgen aus `u16 Länge` und CP437-Bytes | Zeichenkettenpool; ein Datensatzverweis `p` bezeichnet den Längeneintrag bei Dateiposition `p + 0x16`, die Bytes werden beim Import nach UTF-8 gewandelt |
| danach | `u16 Verweis`, `u8 Klasse`, `u8 Länge`, Name | Objektkatalog; Bit 7 der Klasse kennzeichnet ein Steuerelementfeld, die unteren sieben Bit entsprechen der Klassen-ID; `Verweis = 0` beendet den Katalog |
| Ende des Objektbereichs | aufsteigende `u16`-Verweise | Verweise auf Datensätze bei `Datensatzanfang + 3`, unter anderem für Feldinstanzen und physisch umgeordnete Objekte; jeder Verweis muss auf einen gelesenen Datensatz zeigen |
| Rest | Symboltabelle, Modulblöcke und tokenisierter BASIC-Code | Bezeichner und der in Text zurückübersetzte BASIC-Code |

In den Datensätzen liegen Containerverweis, `Tag`-Verweis und Arrayindex bei
`+0`, `+2` und `+4`, die Geometrie bei `+8` bis `+11` und `TabIndex` bei
`+13`. Beim Form-Root liegen `CurrentX`/`CurrentY` bei `+19`/`+20` und
`BackColor`/`ForeColor` bei `+21`/`+22`.
Klassenabhängig folgen unter anderem `TextBox.BorderStyle`/`ScrollBars` bei
`+19`/`+20`; bei TextBoxen kodiert außerdem das Common-Flag `0x08`
`MultiLine`. `ComboBox.Style` liegt bei `+31`,
`Label.BorderStyle`/`Alignment` bei `+19`/`+20`
und `PictureBox.BorderStyle`/`CurrentX`/`CurrentY` bei `+16`/`+17`/`+19`;
`Timer.Interval` ist ein `u16` bei
`+17`. Das Common-Flag `0x10` eines CommandButton kodiert `Cancel`.
Das `AutoRedraw`-Bit `0x08` von Form
und PictureBox steht im Objektkopf; bei Forms kodiert `0x02` zusätzlich
`FormType = 1`. Dort kodiert `0x80` bei ListBoxen `Sorted` und `0x20`
bei Labels `AutoSize`; bei Menüs kodieren `0x01` und `0x40` `Separator` und
`Checked`. Scrollbars verwenden ab `+14` ein eigenes Layout:
`Attached`, `SmallChange`, `LargeChange`, `Max` und `Min`; ihr Anfangswert ist
`Min`. Diese Bytes sind ausdrücklich keine Farbwerte.

Der Import ordnet jeden physischen Datensatz über dessen 7-Byte-Kopf dem
Katalogeintrag zu; die Katalogreihenfolge ist dafür ausdrücklich nicht
maßgeblich. Array-Indizes müssen eindeutig, Containerverweise auf bereits
gelesene Objekte gerichtet und Zeichenkettenverweise exakt auf einen
Pooleintrag auflösbar sein; auch bei Custom Controls darf kein Pooleintrag
unbelegt bleiben. Nicht unterstützte Custom Controls der binären Klasse 17
werden nicht als `Screen` ausgegeben, sondern mit Objektname und tatsächlicher
Byteposition gemeldet. Eine gespeicherte, vom Objektmodell nicht unterstützte
Menü-Tastenkombination liegt im erweiterten Menüdatensatz als `u16`
little-endian bei `+19`; sie wird als `<Menüname>.Shortcut` mit ihrer
Byteposition gemeldet und übersprungen. Eine unbekannte Klasse, ein ungültiger Verweis,
ein unbelegter String oder ein abgeschnittener Bereich führt an der Fundstelle
zum Abbruch; eine Ausgabedatei wird erst nach erfolgreichem Lesen angelegt.

## Projekt: `.MAK`

Reine Textdatei ohne Kopfzeile, **eine Projektdatei pro Zeile**
(`.BAS`/`.FRM`; belegt durch die Originalbeispiele). Terminal Basic
akzeptiert zusätzlich Kommentarzeilen mit `'`. `tbc check`, `build` und
`run` lösen die Einträge relativ zur `.MAK`-Datei und ohne Beachtung der
DOS-Großschreibung auf; Formularobjekte aller `.FRM`-Einträge stehen dem
gemeinsamen Modulverbund zur Verfügung.

Optional speichert das Projekt eine ausdrücklich gewählte Startdatei in
einem Metakommentar (Terminal-Basic-Erweiterung):

```mak
' Beispielprojekt
' $STARTUP: "main.bas"
main.bas
lib.bas
form.frm
```

Der Wert muss genau ein vorhandenes BAS-/FRM-Mitglied bezeichnen. Doppelte,
leere, fehlerhafte oder auf Nichtmitglieder gerichtete Angaben werden mit
Dateipfad diagnostiziert. Ohne Metakommentar bleiben die bisherige
Modulreihenfolge und Startformularwahl erhalten. Der gemeinsame Lader
erhält die Auswahl als `ProjectSources.manifest.startup`; ihre zusätzliche
Ausführungswirkung gehört zum Phase-5-Change 04. Ältere Leser ignorieren
diese Kommentarzeile. Mitgliedsreihenfolge, Leerzeilen und gewöhnliche
Kommentare bleiben beim Speichern erhalten; der Startup-Kommentar wird
kanonisch vorangestellt.

### Bearbeitete Dokumente und Speichern

`tb_vm::project_io::SourceLoader` ist der gemeinsame BAS-/FRM-/MAK- und
Include-Lader von CLI und IDE-Modell. Bearbeitete Dokumente überlagern ihren
Plattenstand; Includes behalten ihre physische Datei, Zeile und den
jeweiligen Modulkontext. Relative Includes gewinnen vor optionalen
Include-Suchverzeichnissen. Dateialiasse werden für die Dokumentverwaltung
auf eine gemeinsame absolute Identität aufgelöst; noch ungespeicherte
Dokumente haben ebenfalls einen stabilen Quellpfad, aber keinen Speicherpfad.

`tb_ide::documents::Project` stellt Projekt-, Datei-, Textimport-/Export-,
Undo- und Schließaktionen als direkt testbare API bereit. Ansichten haben
eigene Cursor-/Scrollpositionen und teilen den Dokumentinhalt. Ein
Formulardokument enthält den vorhandenen `FormFile` einschließlich Code;
Importwarnungen bleiben über `Document::import_warnings` abrufbar. Die
Terminaldialoge werden in Change 02 daran angebunden.

Save File und Save As schreiben zunächst eine temporäre Datei im
Zielverzeichnis und ersetzen das Ziel erst nach erfolgreichem Schreiben.
Externe Änderungen und bestehende neue Ziele erfordern eine ausdrückliche
Überschreibentscheidung für die jeweilige Datei. Schreibschutz und binäre
FRM-Originale bleiben geschützt. Binärimporte benötigen für Save File ein
anderes, ausdrücklich gewähltes FRM-Textziel. Ein Dateiexport verändert
weder Projektmitgliedschaft noch Dokumentpfad.

Save Project speichert geänderte Mitglieder und geöffnete geänderte
Include-/Textdokumente vor der MAK-Datei. Es gibt keine atomare Transaktion
über mehrere Dateien: Bei einem Teilfehler bleiben bereits erfolgreich
gespeicherte Dokumente gespeichert, die übrigen geändert und offen. Ein
Schließ-/Projektwechsel wird dann nicht als erfolgreich gemeldet. Die
Konfliktprüfung wird unmittelbar vor dem Ersetzen wiederholt; sie ist ein
optimistischer Abgleich und keine Sperre für fremde Editoren.

Projekt-Save-As berechnet die Mitgliedsverweise relativ zum neuen
Projektverzeichnis. Save As eines Moduls oder Formulars in ein anderes
Verzeichnis passt dessen relative Includes so an, dass sie dieselben
Dateien erreichen; unveränderte Originaldateien bleiben erhalten. Die
aktive Datei-/Projektidentität wechselt erst nach erfolgreicher Ausgabe.
Remove File entfernt nur die Mitgliedschaft; Dateien, geöffnete Ansichten
und ungespeicherter Code bleiben erhalten. Für die aktuelle Startdatei
muss ausdrücklich Ersatz, Standard oder Abbrechen gewählt werden.

Load Text fügt UTF-8-Text am Cursor als eine rückgängig machbare Aktion ein.
Save Text exportiert Auswahl oder gesamten Code. Zum Überschreiben eines
geöffneten Dokuments wird Save File verwendet, damit Dokumentstand und
Formularstruktur nicht durch einen reinen Textexport auseinanderlaufen.

## Record-Dateien (`GET`/`PUT`, `OPEN … FOR RANDOM`)

Feste Strings (`STRING * n`) in Records werden als **UTF-32LE** gespeichert
(4 Bytes pro Zeichen → feste Record-Länge bleibt erhalten). Entscheidung
2026-09-02: Die Binärdateien sind damit bewusst inkompatibel zu Dateien
des Vorbilds; numerische Felder behalten ihr klassisches Layout
(INTEGER i16, LONG i32, SINGLE f32, DOUBLE f64, CURRENCY i64, little-endian).

## ISAM-Datenbank (`OPEN … FOR ISAM`)

Eigenes Format, **bewusst nicht binärkompatibel** zu Datenbankdateien des
Vorbilds — dieselbe Linie wie bei den UTF-32-Records oben. Ein
Konvertierungswerkzeug ist Nicht-Ziel; das Vorbildformat wird weder
gelesen noch geschrieben.

**Träger.** Die Datei ist eine [`redb`](https://crates.io/crates/redb)-Datei
(eingebetteter transaktionaler B-Baum). `redb` liefert Seitenverwaltung,
Transaktionen und Crash-Sicherheit; die ISAM-Semantik darüber ist eigener
Code (siehe `tb-runtime::isam`).

**Formatversion.** Die Datei trägt in ihrer Metatabelle unter `version`
eine 32-Bit-Formatversion (little-endian), derzeit **1**. Eine Datei mit
höherer Version wird beim Öffnen mit Laufzeitfehler 88 („ISAM - Database
inconsistent") abgewiesen, statt fehlinterpretiert zu werden. Ebenso
abgewiesen wird eine bestehende Datei ohne Versionsmarke sowie jede
strukturell unlesbare Datei.

**Tabellen in der Datei.** Je Datenbankdatei:

| `redb`-Tabelle | Schlüssel → Wert | Inhalt |
|---|---|---|
| `tb_meta` | `&str → Bytes` | Formatversion, Layout, Indexdefinitionen, ID-Zähler |
| `satz/<tabelle>` | `u64 → Bytes` | ein Satz je Eintrag, Schlüssel ist die Satz-ID |
| `idx/<tabelle>/<index>` | `Bytes → u64` | Indexeintrag: Schlüsselbytes → Satz-ID |

Die Einträge in `tb_meta` je Tabelle `T`:

- `tab:T` — Layoutkurzform `spalte:typ;…` mit den Typkürzeln `I2`, `I4`,
  `R4`, `R8`, `CY`, `T<n>` (fester Text mit `n` Zeichen). Beim Öffnen einer
  bestehenden Tabelle wird sie gegen den angegebenen Satztyp geprüft;
  Abweichung ist Fehler 88.
- `idx:T` — eine Zeile je Index, `name<TAB>eindeutig<TAB>spalten`, Spalten
  als Feldnummern mit `-` für absteigend.
- `seq:T` — nächste Satz-ID (u64, little-endian). IDs werden **monoton**
  vergeben und **nie wiederverwendet**: ein Cursor merkt sich eine Satz-ID,
  und eine nach `DELETE` neu vergebene ID ließe ihn still auf einen fremden
  Satz zeigen.

**Satzbytes.** Ein Satz liegt genau so im Speicher wie ein Record bei
`PUT` auf eine `RANDOM`-Datei: numerische Felder klassisch und
little-endian, feste Strings als UTF-32LE. Daher gilt die Inkompatibilität
zum Vorbild aus demselben Grund wie oben.

**Indexschlüssel.** Die Schlüsselbytes sind **ordnungserhaltend** kodiert —
ihr Byte-Vergleich entspricht dem fachlichen Vergleich, weil `redb` nach
Bytes ordnet:

- Ganzzahlen: Big-Endian fester Breite mit gekipptem Vorzeichenbit.
- Gleitkommazahlen: Big-Endian der Bitdarstellung; bei negativem
  Vorzeichen alle Bits invertiert, sonst das oberste Bit gesetzt.
- Text: UTF-8 (dessen Bytereihenfolge ist die Codepoint-Reihenfolge, siehe
  „Sortierordnung" der Sprachreferenz). `00` im Text wird zu `00 FF`
  verdoppelt, den Abschluss bildet `00 00` — damit ordnet ein echtes
  Präfix vor jedem längeren Text.
- Absteigende Spalten: alle Bytes dieser Spalte invertiert.
- Mehrspaltig: Verkettung in Spaltenreihenfolge.

An jeden Indexschlüssel sind acht Bytes Satz-ID (Big-Endian) angehängt.
Das macht auch Einträge eines mehrdeutigen Index eindeutig, hält Dubletten
in Einfügereihenfolge und macht die Präfixsuche zur Bereichsabfrage.

**Datenbankidentität und Tabellenlöschung.** ISAM löst bestehende Datenbankpfade
kanonisch auf; bei einer Neuanlage wird zuerst der Elternpfad aufgelöst.
Nach der Anlage wird die endgültige Dateiidentität erneut bestimmt, sodass
auch Symlinks auf zuvor fehlende Zieldateien korrekt gebunden bleiben.
Relative Pfade beziehen sich auf das aktuelle Arbeitsverzeichnis. Ein späteres
`CHDIR` ändert bestehende Bindungen nicht. Relative, absolute und über Symlinks
auflösbare Aliase derselben vorhandenen Datei teilen einen Schreibkontext.
`DELETETABLE` schließt alle Bindungen der entfernten Tabelle. Zugriffe über die
alten Nummern melden Fehler 52. Nach Rücknahme der Löschung ist der Bestand
wieder vorhanden, muss aber mit `OPEN` neu gebunden werden.

**Transaktionen und Sicherungspunkte.** Außerhalb von `BEGINTRANS` wird
jede Änderung einzeln festgeschrieben. Innerhalb einer Transaktion teilen
alle Bindungen derselben Datenbank einen Schreibkontext; auch ein weiteres
`OPEN` sieht die bisherigen Änderungen und schreibt sie nicht fest.
`CLOSE #n`, `CLOSE` und `RESET` lösen nur Bindungen. `COMMITTRANS` schreibt
fest, `ROLLBACK ALL` bricht die Transaktion ab; eine beim Programmende
offene Transaktion verfällt. Bei mehreren Datenbankdateien erfolgt die
Festschreibung je Datei, ohne datenbankübergreifende Atomaritätsgarantie.

`SAVEPOINT` liefert eine Kennung. `ROLLBACK kennung` nimmt die danach
erfolgten Satzänderungen, Indexanlagen/-löschungen und Tabellenanlagen/-löschungen
einschließlich Layout, Indexdefinitionen und gelöschtem Tabellenbestand zurück.
Die Transaktion bleibt offen; der gewählte und alle jüngeren Sicherungspunkte
werden verbraucht. `ROLLBACK` ohne Kennung verwendet den letzten
Sicherungspunkt oder, wenn keiner besteht, den Transaktionsbeginn.
Die Rücknahme funktioniert auch nach dem Schließen aller Bindungen.
Bei Satz-Rücknahmen bleibt die ID-Vergabe monoton; das Wiederherstellen
einer gelöschten Tabelle stellt auch ihren gespeicherten ID-Zähler wieder her.

Nach einer Rücknahme sind Cursor unpositioniert; nicht mehr vorhandene aktive
Indizes werden auf den NULL-Index zurückgesetzt. Bindungen an zurückgenommene
Tabellenanlagen oder an ein zurückgenommenes Layout werden gelöst. Ein erneutes
`OPEN` bindet den wiederhergestellten Bestand beziehungsweise legt eine fehlende
Tabelle neu an. Die von `FREEFILE` gelieferten Nummern berücksichtigen gemeinsam
gewöhnliche Dateien und ISAM-Bindungen; doppelte Nummern werden mit Fehler 55
abgewiesen.

Das Rücknahmeprotokoll hält Layouts unabhängig von offenen Bindungen.
Für Strukturänderungen speichert es die betroffenen Metadaten und, bei Löschungen,
den gelöschten Index beziehungsweise Tabellenbestand, keine vollständige
Datenbankkopie je Sicherungspunkt. Diese gespeicherten Daten zählen zum
ISAM-Puffer (`SETMEM`); reicht er nicht, scheitert die Strukturänderung vor
dem Schreiben mit Fehler 89. Rücknahme und Transaktionsende geben diesen
Puffer wieder frei. Auch DELETE prüft seine Satzbytes vor der Änderung gegen
die verfügbare Grenze; Fehler 89 lässt Sätze, Indizes und Cursor unverändert.
Das Dateiformat bleibt Version 1.

## Kompilat: `.tbc` (neu, eigenes Format)

Version 4 ist ein eigenständig ausführbarer Bytecode-Container; das vollständige
Format steht in [tbvm-design.md](tbvm-design.md). `tbc build app.mak` erzeugt
`app.tbc`, `tbc run app.tbc` führt es ohne BAS-/FRM-/MAK-/Include-Quellen aus.
Quellorte, Prozedursignaturen und Formular-Anfangswerte einschließlich
Designzeit-Control-Arrays bleiben erhalten. Ältere Versionen 1–3 und unbekannte
Versionen werden mit Versionsangabe abgewiesen. Native Executable-Verpackung
ist noch nicht implementiert.
