Phase 4: Formulardateien und Konvertierung

This commit is contained in:
2026-09-05 08:18:55 +02:00
parent 54488b5b67
commit 05837cd846
18 changed files with 5287 additions and 27 deletions

View File

@@ -406,10 +406,10 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
Timer — mit allen Eigenschaften/Methoden/Ereignissen des Vorbilds
- [ ] Menüsystem (Menüleiste, Shortcuts, Access Keys)
- [ ] Fokus-/Tab-Reihenfolge, Access-Keys, Maussteuerung
- [ ] `.FRM`-Textformat: Serialisierung **definieren** (kein Original-
- [x] `.FRM`-Textformat: Serialisierung **definieren** (kein Original-
Beispiel verfügbar — Windows-1.0-Schema, siehe dateiformate.md),
dokumentieren, dann lesen/schreiben implementieren
- [ ] Konvertierungstool binäre `.FRM` → unsere Text-Serialisierung
- [x] Konvertierungstool binäre `.FRM` → unsere Text-Serialisierung
(Gegenstück zu FT.EXE des Vorbilds; Magic `FC 08 01 00`): als
`tbc convert-frm`. Format per Reverse Engineering aus den
Beispieldateien des Originalpakets und des cout/vbdos-Repos

View File

@@ -4,6 +4,7 @@
//! - `tbc run <datei.bas>` Kompilieren und sofort ausführen
//! - `tbc build <datei.bas>` Kompilieren zu `datei.tbc`
//! - `tbc check <datei.bas>` Nur Syntax-/Semantikprüfung
//! - `tbc convert-frm <quelle.frm> <ziel.frm>` Binärformular in Text wandeln
//!
//! Exit-Codes von `run` (Entscheidung D6, docs/tbvm-design.md):
//! 0 = END/SYSTEM/Programmende · 3 = STOP · 2 = Laufzeitfehler ·
@@ -21,20 +22,68 @@ fn main() -> ExitCode {
Some("run") => cmd_run(&args[1..]),
Some("build") => cmd_build(&args[1..]),
Some("check") => cmd_check(&args[1..]),
Some("convert-frm") => cmd_convert_frm(&args[1..]),
_ => {
eprintln!("Aufruf: tbc run|build|check <datei.bas>");
eprintln!(
"Aufruf: tbc run|build|check <datei.bas> | tbc convert-frm <quelle.frm> <ziel.frm>"
);
ExitCode::from(1)
}
}
}
fn cmd_convert_frm(args: &[String]) -> ExitCode {
if args.len() != 2 {
eprintln!("Aufruf: tbc convert-frm <quelle.frm> <ziel.frm>");
return ExitCode::from(1);
}
let input = Path::new(&args[0]);
let output = Path::new(&args[1]);
let bytes = match std::fs::read(input) {
Ok(bytes) => bytes,
Err(error) => {
eprintln!("{}: {error}", input.display());
return ExitCode::from(1);
}
};
let converted = match tb_ui::frm::read_binary(&input.display().to_string(), &bytes) {
Ok(converted) => converted,
Err(error) => {
eprintln!("{error}");
return ExitCode::from(1);
}
};
let text = tb_ui::frm::write_text(&converted.form);
if let Err(error) = std::fs::write(output, text) {
eprintln!("{}: {error}", output.display());
return ExitCode::from(1);
}
for warning in &converted.skipped {
eprintln!(
"{}: Byte 0x{:04x}: {} nicht übernommen",
input.display(),
warning.offset,
warning.name
);
}
eprintln!(
"{}: {} nicht übernommene Binärangaben",
input.display(),
converted.skipped.len()
);
println!("{}", output.display());
ExitCode::SUCCESS
}
fn module_name(path: &Path) -> String {
path.file_stem()
.map(|s| s.to_string_lossy().to_uppercase())
.unwrap_or_else(|| "MODUL".into())
}
fn compile(path_arg: Option<&String>) -> Result<(PathBuf, tb_vm::bytecode::CompiledModule), ExitCode> {
fn compile(
path_arg: Option<&String>,
) -> Result<(PathBuf, tb_vm::bytecode::CompiledModule), ExitCode> {
let Some(path) = path_arg else {
eprintln!("Aufruf: tbc run|build|check <datei.bas>");
return Err(ExitCode::from(1));
@@ -133,7 +182,11 @@ fn cmd_run(args: &[String]) -> ExitCode {
eprintln!("STOP in line {line}");
ExitCode::from(3)
}
RunEvent::Error { code, line, message } => {
RunEvent::Error {
code,
line,
message,
} => {
eprintln!("Runtime error {code}: {message} in line {line}");
ExitCode::from(2)
}

View File

@@ -0,0 +1,24 @@
use std::process::Command;
#[test]
fn conversion_failure_leaves_no_output() {
let dir = std::env::temp_dir().join(format!("tbc-convert-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let input = dir.join("broken.frm");
let output = dir.join("converted.frm");
std::fs::write(&input, b"not a form").unwrap();
let result = Command::new(env!("CARGO_BIN_EXE_tbc"))
.args([
"convert-frm",
input.to_str().unwrap(),
output.to_str().unwrap(),
])
.output()
.unwrap();
assert!(!result.status.success());
assert!(!output.exists());
assert!(String::from_utf8_lossy(&result.stderr).contains("0x0000"));
std::fs::remove_dir_all(dir).unwrap();
}

View File

@@ -68,6 +68,29 @@ impl ObjectClass {
}
}
pub fn display_name(self) -> &'static str {
match self {
Self::Form => "Form",
Self::CheckBox => "CheckBox",
Self::ComboBox => "ComboBox",
Self::CommandButton => "CommandButton",
Self::DirListBox => "DirListBox",
Self::DriveListBox => "DriveListBox",
Self::FileListBox => "FileListBox",
Self::Frame => "Frame",
Self::HScrollBar => "HScrollBar",
Self::Label => "Label",
Self::ListBox => "ListBox",
Self::Menu => "Menu",
Self::OptionButton => "OptionButton",
Self::PictureBox => "PictureBox",
Self::TextBox => "TextBox",
Self::Timer => "Timer",
Self::VScrollBar => "VScrollBar",
Self::Screen => "Screen",
}
}
pub fn parse(name: &str) -> Option<Self> {
Self::ALL
.into_iter()
@@ -182,11 +205,12 @@ pub fn properties(class: ObjectClass) -> Vec<PropertySpec> {
range("WIDTH", 1, 1, 254),
]);
}
if !matches!(
class,
Form | Frame | Label | PictureBox | Timer | Menu | Screen
) {
p.extend([int("INDEX", 0), int("TABINDEX", 0), boolp("TABSTOP", true)]);
if !matches!(class, Form | Timer | Menu | Screen) {
p.push(int("INDEX", 0));
p.push(int("TABINDEX", 0));
}
if !matches!(class, Form | Frame | Label | Timer | Menu | Screen) {
p.push(boolp("TABSTOP", true));
}
if !matches!(class, Form | Timer | Menu | Screen) {
p.push(string("CTLNAME", ""));

View File

@@ -30,6 +30,7 @@ impl PropertyValue {
PropertyDefault::Boolean(v) => Self::Boolean(v),
PropertyDefault::Empty if ty == PropertyType::Object => Self::Object(None),
PropertyDefault::Empty if ty == PropertyType::String => Self::String(String::new()),
PropertyDefault::Empty if ty == PropertyType::Boolean => Self::Boolean(false),
PropertyDefault::Empty => Self::Integer(0),
}
}

2042
crates/tb-ui/src/frm.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,8 @@
pub mod events;
pub mod forms;
pub mod frm;
mod frm_pcode;
pub mod host; // Terminal-Host: Anzeige + Tastatur-/Größenereignisse
pub mod screen;
pub mod signale; // Betriebssystemsignale als Ereignisquelle (SIGNAL)

View File

@@ -0,0 +1,16 @@
fc 08 01 00 0e 00 a8 01 c3 01 09 00 01 02 03 04
06 05 08 0a 69 00 00 00 00 00 00 00 56 00 3d 00
00 00 00 22 85 29 00 00 00 00 00 52 29 00 00 03
0f 11 3f 00 00 07 00 00 47 00 02 00 00 0f 3d 01
03 00 00 c1 00 00 0a 00 00 00 00 00 00 00 0b 08
03 0c 00 00 07 00 00 4c 00 00 00 29 00 03 00 4e
65 77 08 00 43 6f 6d 6d 61 6e 64 31 5d 00 00 03
4e 65 77 00 00 03 08 43 6f 6d 6d 61 6e 64 31 05
01 ff ff 24 00 ff ff 56 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 56 00 00 00 04
00 09 00 08 00 ff ff ff ff ff ff ff ff 00 00 00
00 00 00 03 01

View File

@@ -2,7 +2,8 @@
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"-Varianten des Vorbilds sind Nicht-Ziel — nur Textformate.
„Fast-Load"-Formulare des Vorbilds werden nur durch den Konverter gelesen,
aber nie geschrieben.
## Quelltext: `.BAS`
@@ -13,9 +14,8 @@ 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
implementiert nur das Textformat; das Binärformat ist Nicht-Ziel (FT.EXE
des Vorbilds konvertierte zwischen beiden).
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
@@ -27,13 +27,13 @@ akzeptiert Version 1.00 und 2.00 (Import aus dem Windows-Produkt via
```
VERSION 1.00
Begin Form Form1
Caption = "Beispiel"
Caption = "Beispiel"
Height = 15
Left = 10
Top = 4
Width = 50
Begin CommandButton cmdOK
Caption = "&OK"
Caption = "&OK"
Height = 1
Left = 18
Top = 11
@@ -49,11 +49,79 @@ 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.
- TODO: Ein wörtliches Original-Beispiel einer Text-`.FRM` war online nicht
auffindbar (Beispieldateien des Originalpakets liegen alle binär vor).
Exakte Serialisierung (Kopfzeile, Einrückung, welche Eigenschaften
geschrieben werden) ist daher festzulegen: wir folgen dem
Windows-1.0-Schema und dokumentieren unsere Fassung als Referenz.
- 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.
- Eigenschaften stehen in der alphabetischen Reihenfolge der Forms-Referenz
und werden nur geschrieben, wenn ihr Wert vom Vorgabewert der Klasse
abweicht.
- Unverändert gelesene Dateien werden bytegleich zurückgegeben; für neu
erzeugte oder veränderte Beschreibungen gilt die kanonische Form oben.
- 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`

View File

@@ -71,11 +71,12 @@ Height, Index, Left, MousePointer, Parent, TabIndex, TabStop, Tag, Top,
Value, Visible, Width.
**Frame** — nur DragDrop/DragOver; DRAG, MOVE, REFRESH (kein SetFocus);
Container für Gruppierung (OptionButton-Gruppen).
Container für Gruppierung (OptionButton-Gruppen). `Index` und `TabIndex`
werden in den Originaldateien auch für Frames gespeichert.
**Label** — Ereignisse: Change, Click, DblClick, Drag*, Mouse*. Methoden:
DRAG, MOVE, REFRESH. Eigenschaften: zusätzlich Alignment, AutoSize,
BorderStyle.
DRAG, MOVE, REFRESH. Eigenschaften: zusätzlich Index, TabIndex, Alignment,
AutoSize, BorderStyle.
**HScrollBar/VScrollBar** — Ereignisse: Change, Drag*, GotFocus, Key*,
LostFocus (**kein** separates Scroll-Ereignis). Eigenschaften: zusätzlich
@@ -85,8 +86,8 @@ LargeChange, Min, Max, SmallChange, Value.
**PictureBox** — Text-Zeichenfläche (PRINT/CLS aufs Control) und Container
für OptionButton-Gruppen. Ereignisse: Click, DblClick, Drag*, GotFocus,
Key*, LostFocus, Mouse*, Paint. Methoden: CLS, DRAG, MOVE, PRINT, REFRESH,
SETFOCUS, TEXTHEIGHT, TEXTWIDTH. Eigenschaften: zusätzlich AutoRedraw,
CurrentX, CurrentY, ScaleHeight, ScaleWidth.
SETFOCUS, TEXTHEIGHT, TEXTWIDTH. Eigenschaften: zusätzlich Index, TabIndex,
TabStop, AutoRedraw, CurrentX, CurrentY, ScaleHeight, ScaleWidth.
**Timer** — Ereignis Timer, keine Methoden; Eigenschaften: CtlName, Enabled,
Index, Interval (0 = aus … 65 535 ms), Parent, Tag.

View File

@@ -22,7 +22,7 @@ namentlich abgewiesen. `Non-Feature` = abgelehnt, gelistet unter
**Fundstelle.** Bei `implementiert` das Modul, bei `Non-Feature` der
Abschnitt der Sprachreferenz, bei `offen` ein Strich.
**Abdeckung.** implementiert 682 · offen 61 · Non-Feature 53 · gesamt 796
**Abdeckung.** implementiert 689 · offen 61 · Non-Feature 53 · gesamt 803
| Name | Art | Gruppe | Status | Fundstelle | Quelle |
|---|---|---|---|---|---|
@@ -549,6 +549,8 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
| `FRAME.TOP` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.VISIBLE` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.WIDTH` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.INDEX` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.TABINDEX` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.CTLNAME` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.DRAGMODE` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
| `FRAME.CAPTION` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
@@ -600,6 +602,8 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
| `LABEL.TOP` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.VISIBLE` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.WIDTH` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.INDEX` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.TABINDEX` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.CTLNAME` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.FORECOLOR` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
| `LABEL.DRAGMODE` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
@@ -708,6 +712,9 @@ Abschnitt der Sprachreferenz, bei `offen` ein Strich.
| `PICTUREBOX.TOP` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.VISIBLE` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.WIDTH` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.INDEX` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.TABINDEX` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.TABSTOP` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.CTLNAME` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.FORECOLOR` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
| `PICTUREBOX.DRAGMODE` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-04

View File

@@ -0,0 +1,88 @@
# Binäre Beispieldateien
Alle Prüfsummen sind SHA-256 über die dekomprimierte `.FRM`-Datei. Die Dateien
werden wegen ihrer fremden Lizenz nicht in das Repository übernommen; der Test
enthält nur eine Hexdarstellung des 245-Byte-Belegs `new.frm`.
## Microsoft Visual Basic for MS-DOS 1.00 Professional
Quelle: WinWorld-Abbild „Microsoft Visual Basic 1.0 Professional for MS-DOS
(1992) (3.5-1.44mb)", Archiv-SHA-1
`052ac72c28de119573611e09bb4011c9610efb10`; Dateien mit Microsofts
`DECOMP`-kompatiblem KWAJ-Verfahren aus `*.FR$` entpackt.
| Datei | SHA-256 |
|---|---|
| BOOKCARD.FRM | `f24625a21098d02814bb5ecad2a9e655b4f6a3a352dd9af31d7a4e2d5797ad86` |
| BOOKLIST.FRM | `7f3c091a87c899d3df4cbeacd7a507bdcdd0379bc61e3cad29175c4bcb7e0cb2` |
| BOOKLOOK.FRM | `4e7356eecf5f378ae25ca1481260c048eda57614925ce5ecf8318378c8414cd8` |
| BOOKSRCH.FRM | `af19b7b1716efaa1f44990fa90cf94553028c6fed6e09e40db4c2a2963c82ffa` |
| BOOKSTCK.FRM | `7983beaddc47c819922d77c0bfdc269e1033c09887adbdbb37df75bdca670f7d` |
| CALC.FRM | `189eda4d341804c54fe24ac5df696f7d7ceadec936776e89258ce75e78636201` |
| CHECK.FRM | `ed552601aca93c2a773159cb3fe13719560924e9fdaa286468bf3b8e051e52c9` |
| CHRTATTR.FRM | `8b2c86fc0ec3cae126e0c1b4e67a494302b82ad380aae617b4e855e66c1327cf` |
| CHRTDATA.FRM | `64ba285c863cb685c981d13bf0211034140fa2f8cb8d99d6d0911060cb017de8` |
| CHRTDEMO.FRM | `3a24445bd8ca68bb35a3e7ff07595d13c275c4240b5839f8ec5d8b71c15586ea` |
| CHRTFONT.FRM | `99bef5c1d9f578f01bcdbbd84d17213a040311a78affa94b4c275a1d6232e2fc` |
| CHRTTYPE.FRM | `f4f566a96f97274b9b50c0970e33760e0d111656f4fa376660d3d72c517768e6` |
| CLOCK.FRM | `97f75993a7558bfb0dbcb7aa8908731a5537ea6542e36b5ee61a30e32b16d3b4` |
| CMNDLGF.FRM | `7273671ef4a938251472a3a8221347f7717d969a69b6b1bfb6fa27412e26c580` |
| CONTROLP.FRM | `a8bb99de1600d44f74922e1d493c9b54afd5cf08aadfde26b453eedf7695dcee` |
| DEBUG.FRM | `e08348af465f3f8c14187a3b4f79a210669170e84a5884c38ccdf672246ffb1c` |
| FONTDEMO.FRM | `90730abb9eac5d1b3c7a0ed47ebfff086b68942742e3dac1f561bfe64273cb6e` |
| GRAPHICS.FRM | `5890134890a950a34d62fa69e2cd013b5b8233c590d924f42d21c6959b4ecb0f` |
| HELPF.FRM | `4dea528924fdcfad4bd21fef3ff2b6508f010dcca71a8e0c012a1ee6924b06d9` |
| HELPUTIL.FRM | `fe49a8c6086b8f77e77004eb624849647ca740e86d5659dd4a8bf44d5146ea48` |
| NOTEPAD.FRM | `24580d5fb284dee839ca26b03cc755b46d574b32b34896d37479ad9ad4379d40` |
| QLBVIEW.FRM | `358e7babd6b9205341a2e79611fd79c5838b546a327fbb0eefa36c69ef38db97` |
| SEEK.FRM | `2c7bf1df44fe59feef40853fb722ff981824662327eea9c4f8722fe38efb1b68` |
| SETUPMSG.FRM | `4c0826c937a6c1b1737e38719dffc6a73e24a544d04b450fb2ad393b84eb4630` |
| SETUPOPT.FRM | `e832533cd06ddb745656a94b0bf7d04eee9cc8da208cf22ebd3ba52eaba74fc4` |
| SETUPPTH.FRM | `814eeb2c94f11c3ab30b45d9b56a4af8973e79f4b01cd6710653bdd5ac2d0bfe` |
| SETUPSTS.FRM | `56c051320317048fba758e65920dde9878c2bafd37ad3f459c8e7ffb015d9cfd` |
| SPINDEMO.FRM | `259e67a3ce0eb72c2cd19a72b957944f5e1a4269425a254f83861c6175f9c738` |
| TORUS.FRM | `fe645610b661d6cf946e47134602c8bece26d8ff8054d85fcd030c62fbd6c620` |
## `github.com/cout/vbdos`
Quelle: Commit `1cdd2b32b829fe1721d0b6aecc433abc47a96fb6`; die beiden
`misc/mdi/*.zip` wurden vor dem Prüfen entpackt.
| Pfad | SHA-256 |
|---|---|
| graphics/graphics.frm | `5890134890a950a34d62fa69e2cd013b5b8233c590d924f42d21c6959b4ecb0f` |
| microsoft/check.frm | `ed552601aca93c2a773159cb3fe13719560924e9fdaa286468bf3b8e051e52c9` |
| microsoft/notepad.frm | `e11b76f60eb1f1e8fc7fdbc37acebfbae4b9d9d75f3dff77cc26e8cb33d43c97` |
| microsoft/qlbview.frm | `358e7babd6b9205341a2e79611fd79c5838b546a327fbb0eefa36c69ef38db97` |
| microsoft/seek.frm | `2c7bf1df44fe59feef40853fb722ff981824662327eea9c4f8722fe38efb1b68` |
| microsoft/spindemo.frm | `259e67a3ce0eb72c2cd19a72b957944f5e1a4269425a254f83861c6175f9c738` |
| misc/mentors/mentors.frm | `e3ac6a4ea998b6050f9baf066b2ce776dd9f2a7a276a87350a43243340e287fb` |
| misc/mdi/mdi.zip: bargraph.frm | `e44707a7b9ff918760729f520d828189e4bb5bc5eb7fbff0f01fc53ed8fe7530` |
| misc/mdi/mdi.zip: desktop.frm | `63c0ad0fb9f205e56d64879ccd6b00fc140eabc9d8804a10646e15abc6c10c6a` |
| misc/mdi/mdi.zip: draw.frm | `c59e4ccb540bd51f26c9021fa39554e094f70dbab373f21c89e2e62766a4ee6a` |
| misc/mdi/mdi.zip: graph.frm | `78065efb81d31cd4d5cb0e6a8c073686392d255c1142b66864092207b056d597` |
| misc/mdi/mdi.zip: icondraw.frm | `ebcfd115d5439bf3b3531516b2c843e862430b7fb4ea41763fa7767911489025` |
| misc/mdi/mdi.zip: new.frm | `a888365e84a1ed469e16b5aa82a7f09ff9145e83e7aac7ff1b615f3aac8634f4` |
| misc/mdi/mdi.zip: piano.frm | `371ba0ced7597cc3203e34f25b40870ef6da1fc9ab0ef619ce7855d18416112f` |
| misc/mdi/mdi.zip: pingpong.frm | `33e322462efa0b7e0855ad34a9682c5556b13dde18c27c443c4196327776ea0f` |
| misc/mdi/mdi.zip: scribble.frm | `e9aa68bba8e39dde7259c1f3ce1c6aed418ffb191a7c2fab9ca2be6c0edcacce` |
| misc/mdi/mdi2.zip: calc.frm | `b06bdf0baa104c19fc147e47397cd353aa1cfbfcab6989a606b190dda0a93fb4` |
| misc/mdi/mdi2.zip: clock.frm | `632393c79f8b895887f04680f68fbcca1bcd6d005009a7bd42e6d1d192f7351c` |
| misc/mdi/mdi2.zip: cmndlgf.frm | `7273671ef4a938251472a3a8221347f7717d969a69b6b1bfb6fa27412e26c580` |
| misc/mdi/mdi2.zip: controlp.frm | `d4654cd54596a536f16ed828882ff6e3bb231e00430a6037d72c449a71dc24b8` |
| misc/mdi/mdi2.zip: debug.frm | `e08348af465f3f8c14187a3b4f79a210669170e84a5884c38ccdf672246ffb1c` |
| misc/mdi/mdi2.zip: fastedit.frm | `5f5bc1abe48c1d97f864795df7ebbc81a1180cf246c223f94c8781ea1296da9c` |
| misc/mdi/mdi2.zip: mdi.frm | `af7a30b08d8e8d8634019173e7a55d9e27717d3a49f1cd8e2586021d3dcdca01` |
| misc/mdi/mdi2.zip: run.frm | `e7d5cb04168d3db60b3fe98327e206407a088c7d44f6cc73405c80b7644addff` |
| misc/mdi/mdi2.zip: scribble.frm | `c96250f8529dc875deab7bfcb27fd0cfbc46b0959d82ceffcc991ca5b6d9b0d7` |
| misc/mdi/mdi2.zip: wmaster.frm | `53d6caa956ffc6cc714ec13ca4cd76186a209857330eb35c1f4ccd7d463f0f9c` |
| misc/mdi/mdi2.zip: wmsetup.frm | `963f9e8eb45858afc84c4a530146c1da5047472542308c8f53a993067b775522` |
## Konvertierungslauf
`tbc convert-frm` wurde am 2026-09-04 über alle 56 oben aufgeführten Dateien
ausgeführt: 56 konvertiert, 0 strukturelle Abbrüche, 4 konkrete Warnungen. Die
Warnungen betreffen `VSpin.CustomControl` und `HSpin.CustomControl` in den zwei
aufgeführten Kopien von `SPINDEMO.FRM`; beide Objekte werden ausgelassen und
nicht als `Screen` fehlinterpretiert. Jede Warnung nennt den Objektnamen und
die tatsächliche Byteposition.

View File

@@ -0,0 +1,88 @@
# Design — Formulardateien und Konvertierung
## Context
Siehe proposal.md — Why. Ausgangslage: `docs/dateiformate.md` hält das
Schema des Windows-Schwesterprodukts als Vorlage fest und markiert die
exakte Serialisierung als offen. Die Formularbeschreibung, die gelesen
und geschrieben wird, stammt aus `phase-4-objektmodell`; dieser Change
fügt nur Ein- und Ausgabe hinzu.
## Goals / Non-Goals
**Goals:**
- Ein Format, das ein Mensch im Editor bearbeiten kann und das ein
Werkzeug reproduzierbar schreibt.
- Binäre Beispieldateien werden lesbar, ohne dass wir das Binärformat
jemals schreiben.
**Non-Goals:**
- Binärkompatibilität in Schreibrichtung.
- Verlustfreie Übernahme von Eigenschaften, die es bei uns nicht gibt
(etwa reine Windows-Eigenschaften aus Version 2.00).
## Decisions
### D1 — Wir legen die Serialisierung fest und dokumentieren sie als Referenz
Es gibt keine Originalfassung zum Nachbilden. Festgelegt wird das
Sparsamste, das den Rundlauf trägt:
```
VERSION 1.00
Begin Form Form1
Caption = "Beispiel"
Height = 15
Begin CommandButton cmdOK
Caption = "&OK"
End
End
```
- Drei Leerzeichen Einrückung je Ebene, Eigenschaftsname auf 16 Spalten
aufgefüllt, `=` mit zwei Leerzeichen davor und danach — die Optik des
Vorlagenschemas.
- Nur Eigenschaften, die vom Vorgabewert abweichen. Das hält Dateien
klein und macht Vorgabewertänderungen sichtbar, statt sie in
Altbeständen einzufrieren.
- Reihenfolge: alphabetisch wie in der Forms-Referenz je Klasse, nicht die
Einfügereihenfolge — sonst hängt die Datei von der Bedienung des
Designers ab.
### D2 — Rundlauf ist die Prüfung, nicht der Vergleich mit einer Sollddatei
Da das Format unsere Festlegung ist, prüft ein Vergleich gegen eine
handgeschriebene Solldatei nur uns selbst. Aussagekräftig ist der
Rundlauf: lesen, schreiben, byte-vergleichen — und zusätzlich
schreiben, lesen, Beschreibung vergleichen. Beide Richtungen fangen
verschiedene Fehler.
### D3 — Der Binärleser bleibt eigenständig und liest nur
Das Reverse Engineering erschließt den Aufbau aus den Beispieldateien.
Der Leser übersetzt in dieselbe Formularbeschreibung wie der Textleser
und teilt sich mit ihm die Prüfungen. Was er nicht erkennt, bricht ab —
eine „beste Vermutung" hinterließe ein Formular, das anders aussieht als
das Original, ohne dass jemand es merkt.
Unbekannte Eigenschaften aus dem Windows-Erbe werden beim Konvertieren
namentlich gemeldet und übersprungen; die Zusammenfassung nennt sie am
Ende, damit sie nicht in der Ausgabeflut untergehen.
## Risks / Trade-offs
- **Das Binärformat ist nur teilweise erschließbar** → der Befehl bricht
ab, statt zu raten; die dokumentierte Fundstelle sagt, wo es endete.
- **„Nur Abweichungen schreiben" verliert Werte, wenn sich ein
Vorgabewert ändert** → die Vorgabewerte stehen im Inventar und in der
Forms-Referenz; eine Änderung dort ist ein bewusster Vorgang.
- **Version 2.00 kennt Eigenschaften, die wir nicht haben** → beim Lesen
namentlich gemeldet und übersprungen, nicht als Fehler behandelt.
## Open Questions
- Ob eine Formulardatei mehrere Formulare enthalten darf, klärt sich mit
der Projektverwaltung (`.MAK`, Phase 5); bis dahin gilt: ein Formular
je Datei.

View File

@@ -0,0 +1,63 @@
# Phase 4 — Formulardateien (`.FRM`) und Konvertierung
## Why
Formulare müssen sich speichern und laden lassen, sonst gibt es weder
den Formular-Designer der Phase 5 noch den Kompatibilitätstest an den
Programmen des Vorbilds. Das Vorbild kannte zwei Formate: binär
(Standard) und Text. Terminal Basic implementiert nur das Textformat —
das Binärformat ist Nicht-Ziel, seine Dateien müssen aber lesbar werden,
denn die Beispielprojekte des Originalpakets und die Programme aus
`github.com/cout/vbdos` liegen **alle** binär vor.
Ein wörtliches Original-Beispiel einer Text-`.FRM` war nicht auffindbar
(Befund in `docs/dateiformate.md`). Die Serialisierung ist deshalb
festzulegen und zu dokumentieren, statt sie zu rekonstruieren — der
einzige Punkt der Phase, an dem die Leitplanke „Referenzverhalten schlägt
Eleganz" mangels Referenz nicht greift.
Dieser Change hängt weder an den Steuerelementen noch an der
Ereignisschleife: er beschreibt Formulare, er stellt sie nicht dar.
## What Changes
- **Textformat festlegen und dokumentieren**: `VERSION`-Zeile,
verschachtelte `Begin <Klasse> <Name> … End`-Blöcke,
`Eigenschaft = Wert`-Zeilen, danach der BASIC-Code des
Formularmoduls. Festgelegt werden Kopfzeile, Einrückung, Reihenfolge
und die Regel, welche Eigenschaften überhaupt geschrieben werden.
Angenommen werden die Versionen 1.00 und 2.00.
- **Lesen und Schreiben**: Eine `.FRM` wird in eine
Formularbeschreibung gelesen und aus ihr wieder geschrieben; das
erneute Schreiben einer gelesenen Datei MUST dieselbe Datei ergeben.
- **Fehlerhafte Dateien**: Unbekannte Klasse, unbekannte Eigenschaft,
unpassender Wert und unausgeglichene Blöcke werden mit Datei, Zeile
und Name gemeldet, nicht stillschweigend übergangen.
- **`tbc convert-frm`**: Konvertierung binärer `.FRM` (Magic
`FC 08 01 00`) in unser Textformat, als Gegenstück zum
Konvertierungswerkzeug des Vorbilds. Das Binärformat wird per Reverse
Engineering aus den Beispieldateien des Originalpakets und des
cout/vbdos-Repos erschlossen und dokumentiert.
**Non-Goals:** Schreiben des Binärformats; Darstellung oder Ausführung
der beschriebenen Formulare (Changes `phase-4-objektmodell` und
`phase-4-steuerelemente`); der Formular-Designer (Phase 5).
## Capabilities
### New Capabilities
- `forms-dateiformat`: Textformat der Formulardateien — Aufbau, Lesen,
Schreiben, Fehlermeldungen bei fehlerhaften Dateien — sowie die
Konvertierung binärer Formulardateien des Vorbilds.
## Impact
- `crates/tb-ui`: Leser und Schreiber des Textformats auf der
Formularbeschreibung aus `phase-4-objektmodell`.
- `crates/tb-cli`: Unterbefehl `convert-frm`.
- `docs/dateiformate.md`: Das TODO zur Serialisierung wird durch die
festgelegte Fassung ersetzt; das Binärformat wird beschrieben, soweit
erschlossen.
- `tests/`: Beispieldateien und ihre erwartete Formularbeschreibung.
- PLAN.md: Punkte „`.FRM`-Textformat" und „Konvertierungstool".

View File

@@ -0,0 +1,70 @@
## Purpose
Das Formulardateiformat legt fest, wie ein Formular samt seiner
Steuerelemente und seines Codes als Textdatei abgelegt, wieder gelesen
und aus binären Dateien des Vorbilds übernommen wird — die Grundlage
dafür, dass Formulare überhaupt gespeichert und ausgetauscht werden
können.
## ADDED Requirements
### Requirement: Aufbau des Textformats
Eine Formulardatei SHALL aus einer `VERSION`-Zeile, einem
Beschreibungsteil und einem Codeteil bestehen. Der Beschreibungsteil
SHALL aus verschachtelten Blöcken `Begin <Klasse> <Name>``End` mit
Zeilen `Eigenschaft = Wert` bestehen; Zeichenketten stehen in
Anführungszeichen. Der Codeteil SHALL gewöhnlicher Quelltext des
Formularmoduls sein. Die Versionen `1.00` und `2.00` SHALL angenommen
werden; eine andere Version MUST mit Nennung der vorgefundenen Version
abgewiesen werden.
#### Scenario: Formular mit einem Steuerelement
- **WHEN** eine Datei ein `Form`-Blockelement mit einem eingebetteten `CommandButton`-Block und anschließendem `SUB`-Code enthält
- **THEN** entsteht daraus eine Formularbeschreibung mit einem Steuerelement und dem zugehörigen Quelltext
#### Scenario: Unbekannte Version
- **WHEN** die Datei mit `VERSION 3.00` beginnt
- **THEN** wird sie abgewiesen und die Meldung nennt `3.00`
### Requirement: Schreiben ist die Umkehrung des Lesens
Das Schreiben einer gelesenen Formularbeschreibung SHALL dieselbe Datei
ergeben. Geschrieben SHALL nur werden, was vom Vorgabewert abweicht;
Reihenfolge und Einrückung SHALL festgelegt und dokumentiert sein, damit
zwei Läufe dieselbe Datei erzeugen.
#### Scenario: Rundlauf
- **WHEN** eine Formulardatei gelesen und unverändert wieder geschrieben wird
- **THEN** ist die geschriebene Datei byte-gleich zur gelesenen
#### Scenario: Vorgabewerte werden nicht geschrieben
- **WHEN** ein Steuerelement nur Vorgabewerte trägt
- **THEN** enthält sein Block außer `Begin`/`End` keine Eigenschaftszeile
### Requirement: Fehlerhafte Dateien werden benannt
Eine unbekannte Klasse, eine für die Klasse unbekannte Eigenschaft, ein
Wert außerhalb des Wertebereichs und ein unausgeglichener Block MUST je
mit Dateiname, Zeilennummer und dem betroffenen Namen gemeldet werden.
Eine solche Datei MUST NOT teilweise übernommen werden.
#### Scenario: Unbekannte Eigenschaft
- **WHEN** ein `CommandButton`-Block die Zeile `Farbe = 3` enthält
- **THEN** nennt die Meldung Datei, Zeile, `CommandButton` und `Farbe`
#### Scenario: Unausgeglichener Block
- **WHEN** einer Datei ein `End` fehlt
- **THEN** nennt die Meldung die Zeile des offenen `Begin`-Blocks
### Requirement: Konvertierung binärer Formulardateien
Ein Unterbefehl SHALL eine binäre Formulardatei des Vorbilds (Kennung
`FC 08 01 00`) in das Textformat übersetzen. Eine nicht erkannte oder
beschädigte Datei MUST mit Nennung der Fundstelle abgewiesen werden;
eine Teilausgabe MUST NOT entstehen. Der erschlossene Aufbau des
Binärformats SHALL dokumentiert sein.
#### Scenario: Bekannte Beispieldatei
- **WHEN** eine binäre Beispieldatei konvertiert wird
- **THEN** entsteht eine Textdatei, deren Lesen dieselbe Formularbeschreibung ergibt wie die dokumentierte Erwartung
#### Scenario: Fremde Datei
- **WHEN** eine Datei ohne die Kennung übergeben wird
- **THEN** bricht der Befehl mit einer Meldung ab und schreibt keine Ausgabedatei

View File

@@ -0,0 +1,24 @@
## 1. Belege und Festlegung
- [x] 1.1 Vorhandene binäre Beispieldateien aus dem Originalpaket und `github.com/cout/vbdos` sammeln und in `beispieldateien.md` dieses Changes mit Herkunft auflisten; verifiziert durch die Liste mit Prüfsumme je Datei
- [x] 1.2 Serialisierung nach D1 festlegen und in `docs/dateiformate.md` an die Stelle des TODO schreiben; verifiziert durch den Abschnitt mit vollständigem Beispiel
## 2. Textformat lesen und schreiben
- [x] 2.1 Leser für `VERSION`, verschachtelte `Begin`/`End`-Blöcke und Eigenschaftszeilen; verifiziert durch Unit-Tests für ein Formular mit eingebettetem Steuerelement und für eine unbekannte Version
- [x] 2.2 Prüfungen gegen Klasse, Eigenschaftsname und Wertebereich mit Datei, Zeile und Name in der Meldung; verifiziert durch Unit-Tests je Fehlerart, einschließlich unausgeglichenem Block
- [x] 2.3 Codeteil vom Beschreibungsteil trennen und unverändert durchreichen; verifiziert durch einen Test, der den Quelltext byte-gleich zurückgibt
- [x] 2.4 Schreiber nach D1 — Einrückung, Spaltenbreite, Reihenfolge, nur Abweichungen; verifiziert durch einen Test gegen das dokumentierte Beispiel
- [x] 2.5 Rundlauf in beiden Richtungen (D2); verifiziert durch Tests „lesen, schreiben, byte-gleich" und „schreiben, lesen, Beschreibung gleich"
## 3. Binäre Dateien
- [x] 3.1 Aufbau des Binärformats aus den Dateien aus 1.1 erschließen und in `docs/dateiformate.md` beschreiben; verifiziert durch die Beschreibung mit Feldtabelle und Kennung `FC 08 01 00`
- [x] 3.2 Binärleser auf dieselbe Formularbeschreibung wie der Textleser; verifiziert durch einen Test, der eine Beispieldatei liest und gegen die dokumentierte Erwartung prüft
- [x] 3.3 Unbekannte Eigenschaften namentlich melden und überspringen, unerkannte Struktur mit Fundstelle abbrechen; verifiziert durch Unit-Tests für beide Fälle
- [x] 3.4 Unterbefehl `tbc convert-frm` mit Abbruch ohne Teilausgabe; verifiziert durch einen Test, der nach dem Abbruch das Fehlen der Ausgabedatei prüft
## 4. Abnahme
- [x] 4.1 Alle Beispieldateien aus 1.1 konvertieren und die Zusammenfassung der übersprungenen Eigenschaften festhalten; verifiziert durch den Lauf über den gesamten Bestand
- [x] 4.2 `cargo test` grün und `openspec validate phase-4-frm --strict` ohne Befund; verifiziert durch beide Kommandos