Projektmodule und vollständiges TBC-Kompilat umsetzen und Change archivieren
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
//! 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<SourceUnit> {
|
||||
files
|
||||
.iter()
|
||||
.map(|(name, code)| SourceUnit::new(name, &format!("{name}.bas"), code))
|
||||
.collect()
|
||||
}
|
||||
fn compile(files: &[(&str, &str)]) -> Result<CompiledModule, Vec<tb_frontend::Diagnostic>> {
|
||||
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()));
|
||||
}
|
||||
Reference in New Issue
Block a user