Phase 4: Formulardateien und Konvertierung
This commit is contained in:
4
PLAN.md
4
PLAN.md
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
24
crates/tb-cli/tests/convert_frm.rs
Normal file
24
crates/tb-cli/tests/convert_frm.rs
Normal 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();
|
||||
}
|
||||
@@ -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", ""));
|
||||
|
||||
@@ -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
2042
crates/tb-ui/src/frm.rs
Normal file
File diff suppressed because it is too large
Load Diff
2687
crates/tb-ui/src/frm_pcode.rs
Normal file
2687
crates/tb-ui/src/frm_pcode.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
16
crates/tb-ui/tests/data/new.frm.hex
Normal file
16
crates/tb-ui/tests/data/new.frm.hex
Normal 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
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
2
openspec/changes/phase-4-frm/.openspec.yaml
Normal file
2
openspec/changes/phase-4-frm/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-04
|
||||
88
openspec/changes/phase-4-frm/beispieldateien.md
Normal file
88
openspec/changes/phase-4-frm/beispieldateien.md
Normal 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.
|
||||
88
openspec/changes/phase-4-frm/design.md
Normal file
88
openspec/changes/phase-4-frm/design.md
Normal 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.
|
||||
63
openspec/changes/phase-4-frm/proposal.md
Normal file
63
openspec/changes/phase-4-frm/proposal.md
Normal 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".
|
||||
70
openspec/changes/phase-4-frm/specs/forms-dateiformat/spec.md
Normal file
70
openspec/changes/phase-4-frm/specs/forms-dateiformat/spec.md
Normal 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
|
||||
24
openspec/changes/phase-4-frm/tasks.md
Normal file
24
openspec/changes/phase-4-frm/tasks.md
Normal 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
|
||||
Reference in New Issue
Block a user