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
|
Timer — mit allen Eigenschaften/Methoden/Ereignissen des Vorbilds
|
||||||
- [ ] Menüsystem (Menüleiste, Shortcuts, Access Keys)
|
- [ ] Menüsystem (Menüleiste, Shortcuts, Access Keys)
|
||||||
- [ ] Fokus-/Tab-Reihenfolge, Access-Keys, Maussteuerung
|
- [ ] 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),
|
Beispiel verfügbar — Windows-1.0-Schema, siehe dateiformate.md),
|
||||||
dokumentieren, dann lesen/schreiben implementieren
|
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
|
(Gegenstück zu FT.EXE des Vorbilds; Magic `FC 08 01 00`): als
|
||||||
`tbc convert-frm`. Format per Reverse Engineering aus den
|
`tbc convert-frm`. Format per Reverse Engineering aus den
|
||||||
Beispieldateien des Originalpakets und des cout/vbdos-Repos
|
Beispieldateien des Originalpakets und des cout/vbdos-Repos
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
//! - `tbc run <datei.bas>` Kompilieren und sofort ausführen
|
//! - `tbc run <datei.bas>` Kompilieren und sofort ausführen
|
||||||
//! - `tbc build <datei.bas>` Kompilieren zu `datei.tbc`
|
//! - `tbc build <datei.bas>` Kompilieren zu `datei.tbc`
|
||||||
//! - `tbc check <datei.bas>` Nur Syntax-/Semantikprüfung
|
//! - `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):
|
//! Exit-Codes von `run` (Entscheidung D6, docs/tbvm-design.md):
|
||||||
//! 0 = END/SYSTEM/Programmende · 3 = STOP · 2 = Laufzeitfehler ·
|
//! 0 = END/SYSTEM/Programmende · 3 = STOP · 2 = Laufzeitfehler ·
|
||||||
@@ -21,20 +22,68 @@ fn main() -> ExitCode {
|
|||||||
Some("run") => cmd_run(&args[1..]),
|
Some("run") => cmd_run(&args[1..]),
|
||||||
Some("build") => cmd_build(&args[1..]),
|
Some("build") => cmd_build(&args[1..]),
|
||||||
Some("check") => cmd_check(&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)
|
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 {
|
fn module_name(path: &Path) -> String {
|
||||||
path.file_stem()
|
path.file_stem()
|
||||||
.map(|s| s.to_string_lossy().to_uppercase())
|
.map(|s| s.to_string_lossy().to_uppercase())
|
||||||
.unwrap_or_else(|| "MODUL".into())
|
.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 {
|
let Some(path) = path_arg else {
|
||||||
eprintln!("Aufruf: tbc run|build|check <datei.bas>");
|
eprintln!("Aufruf: tbc run|build|check <datei.bas>");
|
||||||
return Err(ExitCode::from(1));
|
return Err(ExitCode::from(1));
|
||||||
@@ -133,7 +182,11 @@ fn cmd_run(args: &[String]) -> ExitCode {
|
|||||||
eprintln!("STOP in line {line}");
|
eprintln!("STOP in line {line}");
|
||||||
ExitCode::from(3)
|
ExitCode::from(3)
|
||||||
}
|
}
|
||||||
RunEvent::Error { code, line, message } => {
|
RunEvent::Error {
|
||||||
|
code,
|
||||||
|
line,
|
||||||
|
message,
|
||||||
|
} => {
|
||||||
eprintln!("Runtime error {code}: {message} in line {line}");
|
eprintln!("Runtime error {code}: {message} in line {line}");
|
||||||
ExitCode::from(2)
|
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> {
|
pub fn parse(name: &str) -> Option<Self> {
|
||||||
Self::ALL
|
Self::ALL
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -182,11 +205,12 @@ pub fn properties(class: ObjectClass) -> Vec<PropertySpec> {
|
|||||||
range("WIDTH", 1, 1, 254),
|
range("WIDTH", 1, 1, 254),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if !matches!(
|
if !matches!(class, Form | Timer | Menu | Screen) {
|
||||||
class,
|
p.push(int("INDEX", 0));
|
||||||
Form | Frame | Label | PictureBox | Timer | Menu | Screen
|
p.push(int("TABINDEX", 0));
|
||||||
) {
|
}
|
||||||
p.extend([int("INDEX", 0), int("TABINDEX", 0), boolp("TABSTOP", true)]);
|
if !matches!(class, Form | Frame | Label | Timer | Menu | Screen) {
|
||||||
|
p.push(boolp("TABSTOP", true));
|
||||||
}
|
}
|
||||||
if !matches!(class, Form | Timer | Menu | Screen) {
|
if !matches!(class, Form | Timer | Menu | Screen) {
|
||||||
p.push(string("CTLNAME", ""));
|
p.push(string("CTLNAME", ""));
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ impl PropertyValue {
|
|||||||
PropertyDefault::Boolean(v) => Self::Boolean(v),
|
PropertyDefault::Boolean(v) => Self::Boolean(v),
|
||||||
PropertyDefault::Empty if ty == PropertyType::Object => Self::Object(None),
|
PropertyDefault::Empty if ty == PropertyType::Object => Self::Object(None),
|
||||||
PropertyDefault::Empty if ty == PropertyType::String => Self::String(String::new()),
|
PropertyDefault::Empty if ty == PropertyType::String => Self::String(String::new()),
|
||||||
|
PropertyDefault::Empty if ty == PropertyType::Boolean => Self::Boolean(false),
|
||||||
PropertyDefault::Empty => Self::Integer(0),
|
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 events;
|
||||||
pub mod forms;
|
pub mod forms;
|
||||||
|
pub mod frm;
|
||||||
|
mod frm_pcode;
|
||||||
pub mod host; // Terminal-Host: Anzeige + Tastatur-/Größenereignisse
|
pub mod host; // Terminal-Host: Anzeige + Tastatur-/Größenereignisse
|
||||||
pub mod screen;
|
pub mod screen;
|
||||||
pub mod signale; // Betriebssystemsignale als Ereignisquelle (SIGNAL)
|
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
|
Terminal Basic liest und schreibt die Textformate des Vorbilds, durchgängig
|
||||||
in UTF-8 (Abweichung: das Vorbild nutzte die DOS-Codepage). Binäre
|
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`
|
## Quelltext: `.BAS`
|
||||||
|
|
||||||
@@ -13,9 +14,8 @@ Metabefehle in Kommentaren: `'$INCLUDE: 'datei.bi'`, `'$STATIC`, `'$DYNAMIC`.
|
|||||||
## Formular: `.FRM`
|
## Formular: `.FRM`
|
||||||
|
|
||||||
Das Vorbild kannte zwei Speicherformate: **binär** („Fast load and save",
|
Das Vorbild kannte zwei Speicherformate: **binär** („Fast load and save",
|
||||||
Standard) und **Text** („Readable by other programs"). Terminal Basic
|
Standard) und **Text** („Readable by other programs"). Terminal Basic schreibt
|
||||||
implementiert nur das Textformat; das Binärformat ist Nicht-Ziel (FT.EXE
|
das Textformat und liest das Binärformat ausschließlich zur Konvertierung.
|
||||||
des Vorbilds konvertierte zwischen beiden).
|
|
||||||
|
|
||||||
Textformat, zwei Abschnitte: Formular-Beschreibung, dann Code. Schema wie
|
Textformat, zwei Abschnitte: Formular-Beschreibung, dann Code. Schema wie
|
||||||
beim Windows-Schwesterprodukt: `VERSION`-Zeile, verschachtelte
|
beim Windows-Schwesterprodukt: `VERSION`-Zeile, verschachtelte
|
||||||
@@ -49,11 +49,79 @@ END SUB
|
|||||||
- `VERSION`-Zeile, dann verschachtelte `Begin <Typ> <Name> … End`-Blöcke
|
- `VERSION`-Zeile, dann verschachtelte `Begin <Typ> <Name> … End`-Blöcke
|
||||||
mit `Eigenschaft = Wert`-Zeilen (Strings in `"…"`).
|
mit `Eigenschaft = Wert`-Zeilen (Strings in `"…"`).
|
||||||
- Danach normaler BASIC-Code des Formular-Moduls.
|
- Danach normaler BASIC-Code des Formular-Moduls.
|
||||||
- TODO: Ein wörtliches Original-Beispiel einer Text-`.FRM` war online nicht
|
- Terminal Basic schreibt `VERSION` groß, Klassen- und Eigenschaftsnamen in der
|
||||||
auffindbar (Beispieldateien des Originalpakets liegen alle binär vor).
|
Schreibweise der Forms-Referenz und drei Leerzeichen je Blockebene.
|
||||||
Exakte Serialisierung (Kopfzeile, Einrückung, welche Eigenschaften
|
- Der Eigenschaftsname belegt 16 Spalten; danach folgen ` = ` und der Wert.
|
||||||
geschrieben werden) ist daher festzulegen: wir folgen dem
|
- Eigenschaften stehen in der alphabetischen Reihenfolge der Forms-Referenz
|
||||||
Windows-1.0-Schema und dokumentieren unsere Fassung als 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`
|
## Projekt: `.MAK`
|
||||||
|
|
||||||
|
|||||||
@@ -71,11 +71,12 @@ Height, Index, Left, MousePointer, Parent, TabIndex, TabStop, Tag, Top,
|
|||||||
Value, Visible, Width.
|
Value, Visible, Width.
|
||||||
|
|
||||||
**Frame** — nur DragDrop/DragOver; DRAG, MOVE, REFRESH (kein SetFocus);
|
**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:
|
**Label** — Ereignisse: Change, Click, DblClick, Drag*, Mouse*. Methoden:
|
||||||
DRAG, MOVE, REFRESH. Eigenschaften: zusätzlich Alignment, AutoSize,
|
DRAG, MOVE, REFRESH. Eigenschaften: zusätzlich Index, TabIndex, Alignment,
|
||||||
BorderStyle.
|
AutoSize, BorderStyle.
|
||||||
|
|
||||||
**HScrollBar/VScrollBar** — Ereignisse: Change, Drag*, GotFocus, Key*,
|
**HScrollBar/VScrollBar** — Ereignisse: Change, Drag*, GotFocus, Key*,
|
||||||
LostFocus (**kein** separates Scroll-Ereignis). Eigenschaften: zusätzlich
|
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
|
**PictureBox** — Text-Zeichenfläche (PRINT/CLS aufs Control) und Container
|
||||||
für OptionButton-Gruppen. Ereignisse: Click, DblClick, Drag*, GotFocus,
|
für OptionButton-Gruppen. Ereignisse: Click, DblClick, Drag*, GotFocus,
|
||||||
Key*, LostFocus, Mouse*, Paint. Methoden: CLS, DRAG, MOVE, PRINT, REFRESH,
|
Key*, LostFocus, Mouse*, Paint. Methoden: CLS, DRAG, MOVE, PRINT, REFRESH,
|
||||||
SETFOCUS, TEXTHEIGHT, TEXTWIDTH. Eigenschaften: zusätzlich AutoRedraw,
|
SETFOCUS, TEXTHEIGHT, TEXTWIDTH. Eigenschaften: zusätzlich Index, TabIndex,
|
||||||
CurrentX, CurrentY, ScaleHeight, ScaleWidth.
|
TabStop, AutoRedraw, CurrentX, CurrentY, ScaleHeight, ScaleWidth.
|
||||||
|
|
||||||
**Timer** — Ereignis Timer, keine Methoden; Eigenschaften: CtlName, Enabled,
|
**Timer** — Ereignis Timer, keine Methoden; Eigenschaften: CtlName, Enabled,
|
||||||
Index, Interval (0 = aus … 65 535 ms), Parent, Tag.
|
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
|
**Fundstelle.** Bei `implementiert` das Modul, bei `Non-Feature` der
|
||||||
Abschnitt der Sprachreferenz, bei `offen` ein Strich.
|
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 |
|
| 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.TOP` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FRAME.VISIBLE` | 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.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.CTLNAME` | Eigenschaft | Forms/FRAME | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `FRAME.DRAGMODE` | 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 |
|
| `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.TOP` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LABEL.VISIBLE` | 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.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.CTLNAME` | Eigenschaft | Forms/LABEL | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `LABEL.FORECOLOR` | 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 |
|
| `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.TOP` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.VISIBLE` | 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.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.CTLNAME` | Eigenschaft | Forms/PICTUREBOX | implementiert | tb-ui::forms | forms-referenz |
|
||||||
| `PICTUREBOX.FORECOLOR` | 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 |
|
| `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