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();
}