//! Unabhängige Vertragsproben; Ausführung mit dem temporären Treiber aus review.md. use tb_frontend::{ forms::FormCatalog, source::{SourceSegment, SourceUnit}, }; use tb_runtime::{host::CaptureHost, value::Value}; use tb_vm::{ bytecode::CompiledModule, compile_project, interp::{RunEvent, Vm}, }; fn units(files: &[(&str, &str)]) -> Vec { files .iter() .map(|(name, code)| SourceUnit::new(name, &format!("{name}.bas"), code)) .collect() } fn compile(files: &[(&str, &str)]) -> Result> { compile_project("APP", &units(files), &FormCatalog::default(), &[]) } fn run(module: CompiledModule) -> (RunEvent, String) { let bytes = module.to_tbc(); let loaded = CompiledModule::from_tbc(&bytes).unwrap(); assert_eq!(bytes, loaded.to_tbc()); let mut vm = Vm::new(loaded); let event = vm.run(&mut CaptureHost::default()); (event, tb_runtime::snapshot::text(&vm.rt.screen)) } #[test] fn v1_zweige_und_schleifen_behalten_eigene_quellorte() { let mut failures = vec![]; for (name, code, line) in [ ( "ELSEIF", "IF 0 THEN\nPRINT 1\nELSEIF 1/0 THEN\nPRINT 2\nEND IF", 3, ), ("CASE", "SELECT CASE 1\nCASE 1/0\nPRINT 2\nEND SELECT", 2), ("NEXT", "FOR i%=32767 TO 32767\nPRINT 1\nNEXT", 3), ("LOOP", "DO\nPRINT 1\nLOOP UNTIL 1/0", 3), ] { let (event, _) = run(compile(&[("MAIN", code)]).unwrap()); if !matches!(event, RunEvent::Error { line: found, .. } if found == line) { failures.push(format!("{name}: erwartet Zeile {line}, erhalten {event:?}")); } } let source = SourceUnit { name: "MAIN".into(), segments: vec![ SourceSegment { file: "main.bas".into(), first_line: 1, text: "IF 0 THEN\nPRINT 1\n".into(), }, SourceSegment { file: "cond.bi".into(), first_line: 1, text: "ELSEIF 1/0 THEN\nPRINT 2\n".into(), }, SourceSegment { file: "main.bas".into(), first_line: 4, text: "END IF\n".into(), }, ], }; let mut vm = Vm::new(compile_project("APP", &[source], &FormCatalog::default(), &[]).unwrap()); let event = vm.run(&mut CaptureHost::default()); if vm.current_file() != "cond.bi" { failures.push(format!( "Include: {event:?} aus {} statt cond.bi", vm.current_file() )); } // Das gleiche Problem darf nicht nur durch Umbenennen der Fehlermeldung behoben werden. let mut vm = Vm::new( compile(&[("MAIN", "IF 0 THEN\nPRINT 1\nELSEIF 1 THEN\nPRINT 2\nEND IF")]).unwrap(), ); vm.add_module_breakpoint(0, 3); let event = vm.run(&mut CaptureHost::default()); if event != (RunEvent::Breakpoint { line: 3 }) { failures.push(format!("Breakpoint ELSEIF: {event:?}")); } assert!(failures.is_empty(), "{}", failures.join("\n")); } #[test] fn v2_importierte_deklarationen_behalten_kontext_und_abhaengigkeiten() { let cases = [ ( "DEFINT", "PRINT Doppelt(3)\nEND", "DEFINT A-Z\nFUNCTION Doppelt(x)\nDoppelt=x*2\nEND FUNCTION", " 6 \n", ), ( "CONST", "CONST A=3\nPRINT B\nEND", "CONST A=3\nCONST B=A+1", " 4 \n", ), ( "TYPE", "TYPE Inner\nx AS INTEGER\nEND TYPE\nDIM a AS Outer\na.i.x=3\nPRINT a.i.x\nEND", "TYPE Inner\nx AS INTEGER\nEND TYPE\nTYPE Outer\ni AS Inner\nEND TYPE", " 3 \n", ), ]; let mut failures = vec![]; for (name, main, lib, output) in cases { match compile(&[("MAIN", main), ("LIB", lib)]) { Err(e) => failures.push(format!("{name}: {e:?}")), Ok(module) => { let actual = run(module); if actual != (RunEvent::Ended, output.into()) { failures.push(format!("{name}: {actual:?}")); } } } } assert!(failures.is_empty(), "{}", failures.join("\n")); } #[test] fn v3_form_metabefehle_erzeugen_getrennte_formularobjekte() { let module = compile(&[ ( "A", "'$FORM\nCaption=\"a\"\nCALL SetupB\nPRINT Caption\nEND", ), ("B", "'$FORM\nSUB SetupB\nCaption=\"b\"\nEND SUB"), ]) .unwrap(); let names: Vec<_> = module.objects.iter().map(|o| o.name.clone()).collect(); let actual = run(module); assert_eq!( actual, (RunEvent::Ended, "a\n".into()), "Objekte: {names:?}" ); } #[test] fn v4_common_behaelt_typidentitaet_und_initialisiert_arrays_einmal() { let mut failures = vec![]; for (name, files, want) in [ ( "Suffixe", vec![( "MAIN", "COMMON SHARED c%, c$\nc%=7\nc$=\"ok\"\nPRINT c%;c$\nEND", )], " 7 ok\n", ), ( "Array", vec![ ("MAIN", "COMMON SHARED a%(2)\na%(1)=7\nCALL F\nEND"), ("LIB", "COMMON SHARED a%(2)\nSUB F\nPRINT a%(1)\nEND SUB"), ], " 7 \n", ), ] { match compile(&files) { Err(e) => failures.push(format!("{name}: {e:?}")), Ok(module) => { let actual = run(module); if actual != (RunEvent::Ended, want.into()) { failures.push(format!("{name}: {actual:?}")); } } } } assert!(failures.is_empty(), "{}", failures.join("\n")); } #[test] fn v5_load_und_unload_loesen_den_expliziten_container_auf() { use tb_frontend::forms::ObjectClass; let mut catalog = FormCatalog::default(); for name in ["Form1", "Form2"] { catalog.add(name, ObjectClass::Form, None, false); catalog.add("Text1", ObjectClass::TextBox, Some(name), true); } let source = units(&[("MAIN", "LOAD Form2!Text1(3)\nForm2!Text1(3).Text=\"b\"\nPRINT Form2!Text1(3).Text\nUNLOAD Form2!Text1(3)\nEND")]); let module = compile_project("APP", &source, &catalog, &[]).unwrap(); assert_eq!(run(module), (RunEvent::Ended, "b\n".into())); } #[test] fn v6_linkerdiagnose_nennt_den_urspruenglichen_dateiort() { let errors = compile(&[ ("MAIN", "DECLARE SUB F(x%)\nCALL F(1)\nEND"), ("LIB", "SUB F(x$)\nEND SUB"), ]) .unwrap_err(); assert!( errors .iter() .any(|d| d.message.contains("Parameter type mismatch")), "{errors:?}" ); assert!( errors .iter() .all(|d| d.file.is_some() && d.pos.line > 0 && d.pos.column > 0), "{errors:?}" ); } #[test] fn modulbreakpoint_rundlauf_inspektion_und_numerische_erl_bleiben_getrennt() { let main = format!( "DIM a%(2)\nTYPE T\nx AS INTEGER\nEND TYPE\nDIM r AS T\na%(1)=7\nr.x=8\n{}CALL F\nEND", "\n\nn%=1\n" ); let lib = format!("SUB F\n{}200 ERROR 6\nEND SUB", "\n".repeat(8)); let module = compile(&[("MAIN", &main), ("LIB", &lib)]).unwrap(); let bytes = module.to_tbc(); let loaded = CompiledModule::from_tbc(&bytes).unwrap(); assert_eq!(bytes, loaded.to_tbc()); let mut vm = Vm::new(loaded); vm.add_module_breakpoint(1, 10); let mut host = CaptureHost::default(); assert_eq!(vm.run(&mut host), RunEvent::Breakpoint { line: 10 }); assert_eq!(vm.current_module(), 1); assert_eq!(vm.current_file(), "LIB.bas"); assert!(matches!( vm.inspect_element("MAIN!a%", &[1]), Some(Value::Int(7)) )); assert!(matches!( vm.inspect_field("MAIN!r", &[0]), Some(Value::Int(8)) )); vm.remove_module_breakpoint(1, 10); vm.set_step(true); assert_eq!(vm.run(&mut host), RunEvent::Stepped { line: 10 }); assert_eq!(vm.current_source_pos().column, 5); vm.set_step(false); assert!(matches!( vm.run(&mut host), RunEvent::Error { code: 6, line: 10, .. } )); let source = format!("{}ERROR 6", "\n".repeat(41)); assert!(matches!( run(compile(&[("MAIN", &source)]).unwrap()).0, RunEvent::Error { code: 6, line: 42, .. } )); let module = compile(&[( "MAIN", "ON ERROR RESUME NEXT\n200 ERROR 6\nPRINT ERR;ERL\nEND", )]) .unwrap(); assert_eq!(run(module), (RunEvent::Ended, " 6 200 \n".into())); }