Phase 4: Steuerelemente implementieren
This commit is contained in:
@@ -38,13 +38,11 @@ fn err_code(src: &str) -> u16 {
|
||||
/// Verzeichnis, das beim Verlassen samt Inhalt verschwindet. Programme
|
||||
/// hinterlassen dadurch nichts im Projektbaum.
|
||||
struct TempVerzeichnis {
|
||||
vorher: std::path::PathBuf,
|
||||
dir: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl TempVerzeichnis {
|
||||
fn neu(name: &str) -> TempVerzeichnis {
|
||||
let vorher = std::env::current_dir().unwrap();
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"tb_vm_{name}_{}_{:?}",
|
||||
std::process::id(),
|
||||
@@ -52,14 +50,16 @@ impl TempVerzeichnis {
|
||||
));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::env::set_current_dir(&dir).unwrap();
|
||||
TempVerzeichnis { vorher, dir }
|
||||
TempVerzeichnis { dir }
|
||||
}
|
||||
|
||||
fn pfad(&self, name: &str) -> String {
|
||||
self.dir.join(name).to_string_lossy().replace('"', "\"\"")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempVerzeichnis {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::env::set_current_dir(&self.vorher);
|
||||
let _ = std::fs::remove_dir_all(&self.dir);
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,48 @@ fn print_hallo_welt() {
|
||||
assert_eq!(out("PRINT \"Hallo, Welt!\"\nEND"), "Hallo, Welt!\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grafik_line_paint_und_view_zeichnen_in_den_zellenpuffer() {
|
||||
assert_eq!(
|
||||
out("SCREEN 2\nVIEW (0,0)-(31,15),0,1\nLINE (0,0)-(15,7),1,BF\nPAINT (24,8),1\nEND"),
|
||||
"████\n████\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_gibt_datei_oder_zeile_an_den_runner_weiter() {
|
||||
assert_eq!(
|
||||
run("RUN \"next\"").0,
|
||||
RunEvent::Restart {
|
||||
program: Some("next".into()),
|
||||
line: None
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
run("RUN 100").0,
|
||||
RunEvent::Restart {
|
||||
program: None,
|
||||
line: Some(100)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn common_dialog_registrierung_setzt_byref_erfolg() {
|
||||
assert_eq!(
|
||||
out("DECLARE SUB CmnDlgRegister(ok AS INTEGER)\nCmnDlgRegister ok%\nPRINT ok%\nEND"),
|
||||
"-1 \n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_liefert_die_feste_satzbreite_eines_udt() {
|
||||
assert_eq!(
|
||||
out("TYPE Satz\nText AS STRING * 3\nZahl AS INTEGER\nEND TYPE\nDIM Wert AS Satz\nPRINT LEN(Wert)"),
|
||||
" 14 \n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn banker_rounding_cint() {
|
||||
// Spec-Szenario: PRINT CINT(0.5); CINT(1.5); CINT(2.5) → " 0 2 2 "
|
||||
@@ -486,31 +528,19 @@ fn print_zonen_und_tab() {
|
||||
assert_eq!(out("PRINT \"a\"; SPC(3); \"b\""), "a b\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_feature_fehler_73() {
|
||||
// Dokumentiert, aber noch offen: `RUN` kommt mit Phase 5
|
||||
// → Laufzeitfehler 73 mit dem Katalogtext des Vorbilds (VBDOS).
|
||||
// (`SETUEVENT` stand hier bis zum Change `phase-4-ereignisschleife`.)
|
||||
let (ev, _) = run("RUN");
|
||||
match ev {
|
||||
RunEvent::Error { code, message, .. } => {
|
||||
assert_eq!(code, 73);
|
||||
assert_eq!(message, "Feature unavailable");
|
||||
}
|
||||
other => panic!("{other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Aufgabe 2.4 des Changes `phase-3-isam`: `OPEN … FOR ISAM` senkt nicht
|
||||
/// mehr auf den „nicht verfügbar"-Marker ab, sondern arbeitet. Der Lauf
|
||||
/// findet in einem temporären Verzeichnis statt und lässt nichts zurück.
|
||||
#[test]
|
||||
fn open_for_isam_endet_nicht_mehr_mit_fehler_73() {
|
||||
let _dir = TempVerzeichnis::neu("open_isam");
|
||||
let (ev, ausgabe) = run("TYPE T\n f AS INTEGER\nEND TYPE\n\
|
||||
OPEN \"db.isam\" FOR ISAM T \"Tab\" AS #1\n\
|
||||
let dir = TempVerzeichnis::neu("open_isam");
|
||||
let (ev, ausgabe) = run(&format!(
|
||||
"TYPE T\n f AS INTEGER\nEND TYPE\n\
|
||||
OPEN \"{}\" FOR ISAM T \"Tab\" AS #1\n\
|
||||
PRINT \"offen\"\n\
|
||||
CLOSE #1");
|
||||
CLOSE #1",
|
||||
dir.pfad("db.isam")
|
||||
));
|
||||
assert_eq!(ev, RunEvent::Ended, "unerwartetes Ende: {ev:?}\n{ausgabe}");
|
||||
assert!(ausgabe.contains("offen"), "{ausgabe}");
|
||||
}
|
||||
@@ -594,17 +624,10 @@ fn color_prueft_wertebereich() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn screen_anweisung_nur_textmodus() {
|
||||
// Literaler Grafikmodus schon zur Compile-Zeit.
|
||||
let d = tb_vm::compile_source("T", "SCREEN 1").unwrap_err();
|
||||
assert!(
|
||||
d.iter().any(|x| x.message.contains("Feature unavailable")),
|
||||
"{d:?}"
|
||||
);
|
||||
// Textmodus ist folgenlos zulässig.
|
||||
fn screen_anweisung_bildet_grafikmodi_auf_den_zellenpuffer_ab() {
|
||||
assert_eq!(out("SCREEN 0\nPRINT \"ok\""), "ok\n");
|
||||
// Berechneter Modus erst zur Laufzeit.
|
||||
assert_eq!(err_code("m% = 2\nSCREEN m%"), 73);
|
||||
assert_eq!(out("SCREEN 13\nPRINT \"ok\""), "ok\n");
|
||||
assert_eq!(err_code("m% = 14\nSCREEN m%"), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -644,6 +667,32 @@ fn input_s_liest_genau_n_zeichen_ohne_echo() {
|
||||
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen), "xyz\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_s_liest_aus_einer_binaerdatei() {
|
||||
let tmp = TempVerzeichnis::neu("input_s_datei");
|
||||
let pfad = tmp.pfad("daten.bin");
|
||||
std::fs::write(&pfad, b"abcdef").unwrap();
|
||||
assert_eq!(
|
||||
out(&format!(
|
||||
"OPEN \"{pfad}\" FOR BINARY AS #1\na$ = INPUT$(3, #1)\nPRINT a$\nCLOSE #1"
|
||||
)),
|
||||
"abc\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipboard_und_printer_objekte_haben_laufzeitwirkung() {
|
||||
let module = tb_vm::compile_source(
|
||||
"T",
|
||||
"CLIPBOARD.ADDITEM \"abc\"\nPRINT CLIPBOARD.GETTEXT\nPRINTER.PRINT \"Seite\"\nPRINTER.NEWPAGE\nPRINTER.ENDDOC",
|
||||
)
|
||||
.unwrap();
|
||||
let mut vm = Vm::new(module);
|
||||
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen), "abc\n");
|
||||
assert_eq!(vm.rt.print.drucker, "Seite\n\u{c}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cls_setzt_cursor_zurueck() {
|
||||
assert_eq!(out("PRINT \"weg\"\nCLS\nPRINT \"neu\""), "neu\n");
|
||||
@@ -1136,6 +1185,14 @@ fn objektzugriff_laeuft_ueber_objekt_und_eigenschaftsindex() {
|
||||
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "hallo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn screen_controlpanel_hat_einen_indexierten_laufzeitwert() {
|
||||
assert_eq!(
|
||||
out("SCREEN.CONTROLPANEL(5) = 3\nPRINT SCREEN.CONTROLPANEL(5)"),
|
||||
" 3 \n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn steuerarray_eigenschaften_und_objektparameter_funktionieren() {
|
||||
let mut vm = form_vm(
|
||||
@@ -1175,6 +1232,31 @@ fn modales_show_setzt_nach_unload_fort() {
|
||||
assert!(host.presents >= 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modelloses_formular_bleibt_nach_programmende_bedienbar() {
|
||||
let mut vm = form_vm(
|
||||
"DIM SHARED gesehen%\nEND\n\
|
||||
SUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\n\
|
||||
SHARED gesehen%\ngesehen% = 1\nUNLOAD Form1\nEND SUB",
|
||||
);
|
||||
vm.forms.show(0, false).unwrap();
|
||||
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||
let mut host = CaptureHost::default();
|
||||
host.ereignis_nach(
|
||||
1,
|
||||
tb_runtime::host::Ereignis::Maus(tb_runtime::host::MausEreignis {
|
||||
art: tb_runtime::host::MausArt::Druck,
|
||||
taste: 1,
|
||||
shift: 0,
|
||||
zeile: 1,
|
||||
spalte: 1,
|
||||
}),
|
||||
);
|
||||
assert_eq!(vm.run_visible_forms(&mut host), RunEvent::Ended);
|
||||
assert!(matches!(vm.inspect("gesehen"), Some(Value::Int(1))));
|
||||
assert!(!vm.forms.has_visible_forms());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fehlende_ereignisprozedur_verfaellt_und_typeof_prueft_klasse() {
|
||||
let mut vm = form_vm("Form1.Show\nIF TYPEOF Text1 IS TextBox THEN PRINT \"ja\"");
|
||||
@@ -1278,3 +1360,153 @@ fn objektcode_erzeugt_keine_zusaetzlichen_zustellopcodes() {
|
||||
.count();
|
||||
assert_eq!(n, 1, "nur das explizite DOEVENTS ist ein Zustellopcode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn listenmethoden_indexeigenschaft_und_picture_messung_laufen_in_der_vm() {
|
||||
use tb_frontend::forms::ObjectClass;
|
||||
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||
catalog.add("List1", ObjectClass::ListBox, Some("Form1"), false);
|
||||
catalog.add("Picture1", ObjectClass::PictureBox, Some("Form1"), false);
|
||||
let module = tb_vm::compile_source_with_forms(
|
||||
"FORM1",
|
||||
"List1.Sorted = -1\nList1.ADDITEM \"b\"\nList1.ADDITEM \"a\"\nPRINT List1.List(0)\nPRINT List1.ListCount\nPRINT Picture1.TEXTWIDTH(\"abc\")",
|
||||
&catalog,
|
||||
)
|
||||
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||
let mut vm = Vm::new(module);
|
||||
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||
assert_eq!(
|
||||
tb_runtime::snapshot::text(&vm.rt.screen).trim(),
|
||||
"a\n 2 \n 3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn listenmethoden_und_indexeigenschaft_laufen_auf_control_arrays() {
|
||||
use tb_frontend::forms::ObjectClass;
|
||||
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||
catalog.add("List1", ObjectClass::ListBox, Some("Form1"), true);
|
||||
let module = tb_vm::compile_source_with_forms(
|
||||
"FORM1",
|
||||
"LOAD List1(1)\nList1(1).ADDITEM \"x\"\nPRINT List1(1).List(0)",
|
||||
&catalog,
|
||||
)
|
||||
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||
let mut vm = Vm::new(module);
|
||||
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msgbox_und_inputbox_sind_keine_unsupported_opcodes_mehr() {
|
||||
use tb_runtime::host::{taste, Ereignis};
|
||||
let module = tb_vm::compile_source(
|
||||
"DIALOG",
|
||||
"r% = MSGBOX(\"Weiter?\", 4, \"Frage\")\ns$ = INPUTBOX$(\"Name\")\nPRINT r%; s$",
|
||||
)
|
||||
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||
assert!(!module.procs[0]
|
||||
.code
|
||||
.iter()
|
||||
.any(|instruction| matches!(instruction, tb_vm::bytecode::Instr::Unsupported(_))));
|
||||
let mut vm = Vm::new(module);
|
||||
let mut host = CaptureHost::default();
|
||||
for key in [taste::ENTER, "A", taste::ENTER] {
|
||||
host.ereignis(Ereignis::Taste(key.into(), 0));
|
||||
}
|
||||
assert_eq!(vm.run(&mut host), RunEvent::Ended);
|
||||
assert_eq!(tb_runtime::snapshot::text(&vm.rt.screen).trim(), "6 A");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_button_hat_vorrang_und_beendet_modales_formular() {
|
||||
use tb_frontend::forms::ObjectClass;
|
||||
use tb_runtime::host::{taste, Ereignis};
|
||||
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||
catalog.add("Command1", ObjectClass::CommandButton, Some("Form1"), false);
|
||||
let module = tb_vm::compile_source_with_forms(
|
||||
"FORM1",
|
||||
"DIM SHARED gesehen%\nForm1.Show 1\nPRINT gesehen%\nEND\nSUB Command1_Click()\nSHARED gesehen%\ngesehen% = 1\nUNLOAD Form1\nEND SUB",
|
||||
&catalog,
|
||||
)
|
||||
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||
let mut vm = Vm::new(module);
|
||||
let default = tb_frontend::forms::property(ObjectClass::CommandButton, "DEFAULT")
|
||||
.unwrap()
|
||||
.0;
|
||||
vm.forms
|
||||
.set(1, default, tb_ui::forms::PropertyValue::Boolean(true))
|
||||
.unwrap();
|
||||
let mut host = CaptureHost::default();
|
||||
host.ereignis(Ereignis::Taste(taste::ENTER.into(), 0));
|
||||
assert_eq!(vm.run(&mut host), RunEvent::Ended);
|
||||
assert!(matches!(vm.inspect("gesehen"), Some(Value::Int(1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn printform_schreibt_das_textformular_in_den_druckerkanal() {
|
||||
use tb_frontend::forms::ObjectClass;
|
||||
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||
let module = tb_vm::compile_source_with_forms("FORM1", "Form1.Show\nForm1.PRINTFORM", &catalog)
|
||||
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||
let mut vm = Vm::new(module);
|
||||
for (name, value) in [("WIDTH", 12), ("HEIGHT", 4)] {
|
||||
let property = tb_frontend::forms::property(ObjectClass::Form, name)
|
||||
.unwrap()
|
||||
.0;
|
||||
vm.forms
|
||||
.set_initial(0, property, tb_ui::forms::PropertyValue::Integer(value))
|
||||
.unwrap();
|
||||
}
|
||||
let caption = tb_frontend::forms::property(ObjectClass::Form, "CAPTION")
|
||||
.unwrap()
|
||||
.0;
|
||||
vm.forms
|
||||
.set_initial(
|
||||
0,
|
||||
caption,
|
||||
tb_ui::forms::PropertyValue::String("Druck".into()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||
assert!(vm.rt.print.drucker.contains("Druck"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn klassischer_timer_trap_ruht_solange_das_menu_offen_ist() {
|
||||
use tb_frontend::forms::ObjectClass;
|
||||
use tb_runtime::host::umschalt;
|
||||
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||
catalog.add("Form1", ObjectClass::Form, None, false);
|
||||
catalog.add("mnuDatei", ObjectClass::Menu, Some("Form1"), false);
|
||||
let module = tb_vm::compile_source_with_forms(
|
||||
"FORM1",
|
||||
"DIM SHARED n%\nON TIMER(1) GOSUB Tick\nTIMER ON\nForm1.Show\nSTOP\nDOEVENTS\nSTOP\nDOEVENTS\nPRINT n%\nEND\nTick:\nn% = n% + 1\nRETURN",
|
||||
&catalog,
|
||||
)
|
||||
.unwrap_or_else(|diagnostics| panic!("Compile-Fehler: {diagnostics:?}"));
|
||||
let mut vm = Vm::new(module);
|
||||
let caption = tb_frontend::forms::property(ObjectClass::Menu, "CAPTION")
|
||||
.unwrap()
|
||||
.0;
|
||||
vm.forms
|
||||
.set_initial(
|
||||
1,
|
||||
caption,
|
||||
tb_ui::forms::PropertyValue::String("&Datei".into()),
|
||||
)
|
||||
.unwrap();
|
||||
let mut host = CaptureHost::default();
|
||||
assert!(matches!(vm.run(&mut host), RunEvent::Stopped { .. }));
|
||||
assert!(vm.forms.handle_key("d", umschalt::ALT));
|
||||
host.uhr_vorruecken(1_500);
|
||||
assert!(matches!(vm.run(&mut host), RunEvent::Stopped { .. }));
|
||||
assert!(matches!(vm.inspect("n"), Some(Value::Int(0))));
|
||||
assert!(vm.forms.handle_key(tb_runtime::host::taste::ESC, 0));
|
||||
assert_eq!(vm.run(&mut host), RunEvent::Ended);
|
||||
assert!(matches!(vm.inspect("n"), Some(Value::Int(1))));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user