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

@@ -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