Laufzeit-Eingabe und Zonenzustand korrigieren und Change archivieren

This commit is contained in:
2026-09-06 08:04:22 +02:00
parent 815825dde7
commit c8b92f0619
36 changed files with 1104 additions and 91 deletions

View File

@@ -1516,3 +1516,203 @@ fn klassischer_timer_trap_ruht_solange_das_menu_offen_ist() {
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert!(matches!(vm.inspect("n"), Some(Value::Int(1))));
}
// ---- Laufzeit-Eingabe und Systemstatus ------------------------------------
#[test]
fn input_datei_bewahrt_felder_und_unterscheidet_leer_von_fehlend() {
let tmp = TempVerzeichnis::neu("input_felder");
let p = tmp.pfad("daten.txt");
assert_eq!(out(&format!(
"OPEN \"{p}\" FOR OUTPUT AS #1\nWRITE #1, \"a\", \"b\"\nWRITE #1, \"a,b\", 5\nWRITE #1, CHR$(34) + \"ä\" + CHR$(34), \"\"\nCLOSE #1\nOPEN \"{p}\" FOR INPUT AS #1\nINPUT #1, a$\nINPUT #1, b$, c$, n%, d$, e$\nPRINT a$; b$; c$; n%; d$; LEN(e$); EOF(1)\nCLOSE"
)), "aba,b 5 \"ä\" 0 -1 \n");
for (data, expected) in [("\"a\"\n", 62), ("a", 62), ("", 62), ("\"a", 62)] {
std::fs::write(&p, data).unwrap();
assert_eq!(
err_code(&format!("OPEN \"{p}\" FOR INPUT AS #1\nINPUT #1, a$, b$")),
expected
);
}
std::fs::write(&p, "a,\r\n\r\n\"b\n,c\"\r\n").unwrap();
assert_eq!(out(&format!("OPEN \"{p}\" FOR INPUT AS #1\nINPUT #1, a$, b$, c$, d$\nPRINT a$; LEN(b$); LEN(c$); LEN(d$); EOF(1)")), "a 0 0 4 -1 \n");
std::fs::write(&p, "a,").unwrap();
assert_eq!(out(&format!("OPEN \"{p}\" FOR INPUT AS #1\nINPUT #1, a$\nPRINT EOF(1);\nINPUT #1, b$\nPRINT LEN(b$); EOF(1)")), " 0 0 -1 \n");
}
#[test]
fn input_datei_alle_lesearten_teilen_utf8_cursor_und_seek() {
let tmp = TempVerzeichnis::neu("input_cursor");
let p = tmp.pfad("daten.txt");
std::fs::write(&p, "ä😀,rest\r\n\"a,b\",Ende\r\nZ").unwrap();
assert_eq!(out(&format!(
"OPEN \"{p}\" FOR INPUT AS #1\nPRINT INPUT$(1, #1); INPUT$(1, #1); SEEK(1)\nLINE INPUT #1, s$\nPRINT s$\nINPUT #1, s$\nPRINT s$; INPUT$(2, #1)\nLINE INPUT #1, s$\nPRINT s$\nPRINT INPUT$(1, #1); EOF(1); LEN(INPUT$(0, #1))\nSEEK #1, 1\nINPUT #1, s$\nPRINT s$; SEEK(1)\nCLOSE"
)), "ä😀 7 \n,rest\na,bEn\nde\nZ-1 0 \nä😀 8 \n");
for mode in ["INPUT", "BINARY"] {
std::fs::write(&p, "ä😀x").unwrap();
assert_eq!(
out(&format!(
"OPEN \"{p}\" FOR {mode} AS #1\nPRINT INPUT$(2, #1); INPUT$(1, #1); EOF(1)"
)),
"ä😀x-1 \n"
);
assert_eq!(
err_code(&format!(
"OPEN \"{p}\" FOR {mode} AS #1\ns$ = INPUT$(4, #1)"
)),
62
);
for bad in [&[0xc3][..], &[0xff], &[0xe0, 0x80, 0x80], &[0xf0, 0x9f]] {
std::fs::write(&p, bad).unwrap();
assert_eq!(
err_code(&format!(
"OPEN \"{p}\" FOR {mode} AS #1\ns$ = INPUT$(1, #1)"
)),
57
);
}
}
for input in ["INPUT #1, s$", "LINE INPUT #1, s$"] {
assert_eq!(
err_code(&format!("OPEN \"{p}\" FOR INPUT AS #1\n{input}")),
57
);
}
for mode in ["OUTPUT", "APPEND", "RANDOM"] {
assert_eq!(
err_code(&format!(
"OPEN \"{p}\" FOR {mode} AS #1\ns$ = INPUT$(1, #1)"
)),
54
);
}
}
#[test]
fn input_s_teilt_sondertasten_und_bewahrt_rest_fuer_inkey() {
use tb_runtime::host::Ereignis;
for second in ["INPUT$(1)", "INKEY$"] {
let src = format!("a$ = INPUT$(1)\nb$ = {second}\nc$ = INPUT$(2)\nPRINT LEN(a$); ASC(a$); LEN(b$); ASC(b$); c$; LEN(INKEY$)");
let mut vm = Vm::new(tb_vm::compile_source("T", &src).unwrap());
let mut host = CaptureHost::default();
for text in ["\0;", "ä", "😀"] {
host.ereignis(Ereignis::Taste(text.into(), 0));
}
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert_eq!(
tb_runtime::snapshot::text(&vm.rt.screen),
" 1 0 1 59 ä😀 0 \n"
);
}
}
#[test]
fn alle_mk_cv_paare_verlangen_exakte_bytezeichenbreite() {
for (mk, cv, n, breite) in [
("MKI$", "CVI", "-123", 2),
("MKL$", "CVL", "123456", 4),
("MKS$", "CVS", "1.5", 4),
("MKD$", "CVD", "-12.25", 8),
("MKC$", "CVC", "12.3456@", 8),
] {
assert_eq!(out(&format!("PRINT {cv}({mk}({n})) = {n}")), "-1 \n");
for arg in [
format!("LEFT$({mk}({n}), {})", breite - 1),
format!("{mk}({n}) + CHR$(0)"),
format!("STRING$({breite}, \"😀\")"),
] {
assert_eq!(err_code(&format!("PRINT {cv}({arg})")), 5, "{cv}({arg})");
}
}
}
#[test]
fn datei_und_elternpfadfehler_sind_am_sprachzugang_unterscheidbar() {
let tmp = TempVerzeichnis::neu("pfadfehler");
let leaf = tmp.pfad("fehlt.txt");
let parent = tmp.pfad("fehlt/datei.txt");
let source = tmp.pfad("quelle.txt");
std::fs::write(&source, "erhalten").unwrap();
for (path, code) in [(&leaf, 53), (&parent, 76)] {
for stmt in [
format!("OPEN \"{path}\" FOR INPUT AS #1"),
format!("KILL \"{path}\""),
format!("NAME \"{path}\" AS \"{source}\""),
] {
assert_eq!(err_code(&stmt), code, "{stmt}");
}
}
for stmt in [
format!("MKDIR \"{parent}\""),
format!("NAME \"{source}\" AS \"{parent}\""),
format!("CHDIR \"{parent}\""),
format!("RMDIR \"{parent}\""),
format!("PRINT DIR$(\"{parent}\")"),
] {
assert_eq!(err_code(&stmt), 76, "{stmt}");
}
for mode in ["OUTPUT", "APPEND", "BINARY", "RANDOM"] {
assert_eq!(err_code(&format!("OPEN \"{parent}\" FOR {mode} AS #1")), 76);
}
assert_eq!(std::fs::read_to_string(&source).unwrap(), "erhalten");
let sub = tmp.pfad("unter");
let a = format!("{sub}\\a.txt");
let b = format!("{sub}/b.txt");
assert_eq!(out(&format!("MKDIR \"{sub}\"\nOPEN \"{a}\" FOR OUTPUT AS #1\nWRITE #1, \"ok\"\nCLOSE\nNAME \"{a}\" AS \"{b}\"\nPRINT DIR$(\"{sub}/*.txt\"); LEN(DIR$(\"\"))\nOPEN \"{b}\" FOR INPUT AS #1\nINPUT #1, s$\nPRINT s$\nCLOSE\nKILL \"{b}\"\nRMDIR \"{sub}\"")), "b.txt 0 \nok\n");
}
#[test]
fn timezoneknown_hat_integer_signatur_und_zeigt_aktuellen_status() {
for (zone, expected) in [(Zeitzone::Fest(0), "-1 \n"), (Zeitzone::Unbekannt, " 0 \n")] {
assert_eq!(out_mit_zone("PRINT TIMEZONEKNOWN", zone), expected);
}
let diagnostics = tb_vm::compile_source("T", "PRINT TIMEZONEKNOWN(1)").unwrap_err();
assert!(
diagnostics
.iter()
.any(|d| d.message.contains("TIMEZONEKNOWN")),
"{diagnostics:?}"
);
let mut vm = Vm::new(
tb_vm::compile_source(
"T",
"x = TIMEZONEKNOWN\nSTOP\ny = TIMEZONEKNOWN\nSTOP\nz = TIMEZONEKNOWN\nEND",
)
.unwrap(),
);
let mut host = CaptureHost::default();
vm.rt.zonen_ermittler = |_| Some(7200);
assert!(matches!(vm.run(&mut host), RunEvent::Stopped { .. }));
assert!(matches!(vm.inspect("x"), Some(Value::Sng(-1.0))));
vm.rt.zonen_ermittler = |_| None;
assert!(matches!(vm.run(&mut host), RunEvent::Stopped { .. }));
assert!(matches!(vm.inspect("y"), Some(Value::Sng(0.0))));
vm.rt.zonen_ermittler = |_| Some(0);
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert!(matches!(vm.inspect("z"), Some(Value::Sng(-1.0))));
}
#[test]
fn unicode_puffergrenzen_und_binaere_records_bleiben_getrennt() {
let tmp = TempVerzeichnis::neu("input_puffer");
let p = tmp.pfad("daten.txt");
for n in [8190, 8191, 8192] {
std::fs::write(&p, format!("{}ä😀\r\nZ", "x".repeat(n))).unwrap();
assert_eq!(out(&format!("OPEN \"{p}\" FOR INPUT AS #1\na$ = INPUT$({n}, #1)\nPRINT INPUT$(2, #1)\nLINE INPUT #1, b$\nINPUT #1, c$\nPRINT LEN(b$); c$; EOF(1)")), "ä😀\n 0 Z-1 \n");
}
let mut bytes = "ä😀".as_bytes().to_vec();
bytes.extend_from_slice(&513i16.to_le_bytes());
bytes.push(b'Z');
std::fs::write(&p, bytes).unwrap();
assert_eq!(out(&format!("OPEN \"{p}\" FOR BINARY AS #1\nPRINT INPUT$(2, #1);\nGET #1, , n%\nPRINT n%; INPUT$(1, #1); EOF(1)\nSEEK #1, 7\nn% = 1027\nPUT #1, , n%\nSEEK #1, 7\nGET #1, , n%\nPRINT n%; INPUT$(1, #1)")), "ä😀 513 Z-1 \n 1027 Z\n");
}
#[test]
fn sondertastenrest_wird_nicht_erneut_getrappt() {
use tb_runtime::host::Ereignis;
let src = "ON KEY(1) GOSUB H\nKEY(1) STOP\na$ = INPUT$(1)\nKEY(1) ON\nb$ = INPUT$(1)\nDOEVENTS\nPRINT ASC(a$); ASC(b$); n%\nEND\nH:\nn% = n% + 1\nRETURN";
let mut vm = Vm::new(tb_vm::compile_source("T", src).unwrap());
let mut host = CaptureHost::default();
host.ereignis(Ereignis::Taste("\0;".into(), 0));
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen), " 0 59 1 \n");
}