use tb_frontend::{ forms::FormCatalog, source::{SourceSegment, SourceUnit}, }; use tb_ui::frm::{self, FormFile}; use tb_vm::{compile_project, project::ProjectCompiler}; fn unit(name: &str, text: &str) -> SourceUnit { SourceUnit::new(name, &format!("{name}.bas"), text) } fn compare(cache: &mut ProjectCompiler, units: &[SourceUnit], forms: &[FormFile]) -> bool { let mut catalog = FormCatalog::default(); for f in forms { catalog.append(&f.catalog()); } let a = cache.compile("APP", units, &catalog, forms); let b = compile_project("APP", units, &catalog, forms); match (a, b) { (Ok(a), Ok(b)) => { assert_eq!(a.to_tbc(), b.to_tbc()); true } (Err(a), Err(b)) => { assert_eq!( a.iter().map(ToString::to_string).collect::>(), b.iter().map(ToString::to_string).collect::>() ); false } (a, b) => panic!("cache/full mismatch {a:?} / {b:?}"), } } #[test] fn cached_private_edits_parse_and_compile_only_changed_modules() { let mut c = ProjectCompiler::default(); let mut units = vec![ unit("A", "CALL Work\nEND"), unit("B", "SUB Work\nPRINT 1\nEND SUB"), unit("C", "SUB Independent\nPRINT 3\nEND SUB"), ]; assert!(compare(&mut c, &units, &[])); assert_eq!(c.stats.compiled, 3); assert!(compare(&mut c, &units, &[])); assert_eq!( (c.stats.parsed, c.stats.compiled, c.stats.reused), (0, 0, 3) ); units[1].segments[0].text = units[1].segments[0].text.replace("PRINT 1", "PRINT 2"); assert!(compare(&mut c, &units, &[])); assert_eq!( (c.stats.parsed, c.stats.compiled, c.stats.reused), (1, 1, 2) ); units[1].segments[0].text = "SUB Work\nPRINT 2\nEND SUB\nSUB Second\nPRINT 4\nEND SUB".into(); assert!(compare(&mut c, &units, &[])); units[1].segments[0].text = units[1].segments[0] .text .replace("PRINT 2", "PRINT 2\nPRINT 3"); assert!(compare(&mut c, &units, &[])); assert_eq!( (c.stats.compiled, c.stats.reused), (1, 2), "Verschobene fremde Prozedurzeilen ändern keinen Importvertrag" ); units[1].segments[0].text = "SUB Work\nPRINT )\nEND SUB".into(); assert!(!compare(&mut c, &units, &[])); units[1].segments[0].text = "SUB Work\nPRINT 5\nEND SUB".into(); assert!(compare(&mut c, &units, &[])); } #[test] fn constants_types_common_removed_symbols_and_order_match_full_compiler() { let mut c = ProjectCompiler::default(); let mut u = vec![ unit( "A", "CONST A=2\nTYPE T\nx AS INTEGER\nEND TYPE\nCOMMON SHARED N%\n", ), unit("B", "CONST B=A+1\n"), unit( "C", "DIM value AS T\nvalue.x=B\nCOMMON SHARED N%\nN%=B\nPRINT value.x\n", ), ]; assert!(compare(&mut c, &u, &[])); u[0].segments[0].text = u[0].segments[0].text.replace("A=2", "A=4"); assert!(compare(&mut c, &u, &[])); u[0].segments[0].text = u[0].segments[0].text.replace("INTEGER", "LONG"); assert!(compare(&mut c, &u, &[])); u[0].segments[0].text = u[0].segments[0].text.replace("N%", "N$"); compare(&mut c, &u, &[]); u[0].segments[0].text = u[0].segments[0].text.replace("CONST A=4", "' removed"); assert!(!compare(&mut c, &u, &[])); u.swap(0, 2); assert!(!compare(&mut c, &u, &[])); u.remove(0); compare(&mut c, &u, &[]); } #[test] fn includes_source_ids_data_and_forms_relink_without_patching_old_products() { let mut c = ProjectCompiler::default(); let mut u = vec![ unit( "A", "DATA 3,\"Hi\"\nREAD a%, b$\nPRINT a%;b$\nCALL Work\nEND", ), unit("B", "SUB Work\nPRINT 2\nEND SUB"), ]; u[0].segments.insert( 0, SourceSegment { file: "shared.bi".into(), first_line: 7, text: "CONST N=1\n".into(), }, ); assert!(compare(&mut c, &u, &[])); u[0].segments[0].text = "CONST N=2\n".into(); assert!(compare(&mut c, &u, &[])); u.swap(0, 1); assert!(compare(&mut c, &u, &[])); u[0].segments.insert( 0, SourceSegment { file: "extra.bi".into(), first_line: 1, text: "' extra source\n".into(), }, ); assert!(compare(&mut c, &u, &[])); for index in [None, Some(0), Some(1)] { let property = index.map_or(String::new(), |index| format!(" Index = {index}\n")); let text = format!( "VERSION 1.00\nBEGIN Form Main\n BEGIN CommandButton Button\n{property} END\nEND\n" ); let form = frm::read_text("Main.frm", &text).unwrap(); let units = [unit("Main", "'$FORM\n")]; assert!(compare(&mut c, &units, &[form])); } } #[test] fn unused_constants_do_not_invalidate_independent_units_and_declare_changes_do() { let mut c = ProjectCompiler::default(); let mut u = vec![ unit("A", "CONST A=1\n"), unit("B", "CONST B=A+1\n"), unit("C", "PRINT B\n"), unit("D", "PRINT 7\n"), ]; assert!(compare(&mut c, &u, &[])); u[0].segments[0].text = "CONST A=3\n".into(); assert!(compare(&mut c, &u, &[])); assert_eq!((c.stats.compiled, c.stats.reused), (3, 1)); let mut u = vec![ unit("A", "DECLARE SUB Work(n%)\nCALL Work(1)\nEND"), unit("B", "SUB Work(n%)\nPRINT n%\nEND SUB"), ]; assert!(compare(&mut c, &u, &[])); u[1].segments[0].text = "SUB Work(n$)\nPRINT n$\nEND SUB".into(); assert!(!compare(&mut c, &u, &[])); u[0].segments[0].text = "DECLARE SUB Work(n$)\nCALL Work(\"ok\")\nEND".into(); assert!(compare(&mut c, &u, &[])); }