Phase 4: Formularmodell und Objektsprache

This commit is contained in:
2026-09-04 17:30:32 +02:00
parent 5cf5a6582c
commit 54488b5b67
35 changed files with 7195 additions and 627 deletions

View File

@@ -31,6 +31,7 @@ struct Eintrag {
art: String,
status: String,
fundstelle: String,
quelle: String,
zeile: usize,
}
@@ -61,6 +62,7 @@ fn inventar() -> Vec<Eintrag> {
art: spalten[1].to_string(),
status: spalten[3].to_string(),
fundstelle: spalten[4].to_string(),
quelle: spalten[5].to_string(),
zeile: i + 1,
});
}
@@ -155,7 +157,10 @@ fn stand() -> Stand {
signaturen,
verboten: literale(ban),
nicht_verfuegbar,
syntaktisch: literale(syn).into_iter().map(|s| s.to_uppercase()).collect(),
syntaktisch: literale(syn)
.into_iter()
.map(|s| s.to_uppercase())
.collect(),
}
}
@@ -167,7 +172,15 @@ fn statusvokabular_ist_beschraenkt() {
assert!(
matches!(
e.art.as_str(),
"Anweisung" | "Funktion" | "Metabefehl" | "Routine"
"Anweisung"
| "Funktion"
| "Metabefehl"
| "Routine"
| "Eigenschaft"
| "Methode"
| "Ereignis"
| "Datentyp"
| "Operator"
),
"Zeile {}: `{}` trägt unzulässige Art `{}`",
e.zeile,
@@ -185,6 +198,84 @@ fn statusvokabular_ist_beschraenkt() {
}
}
fn pruefe_forms(inv: &[Eintrag]) -> Vec<String> {
use tb_frontend::forms::{events, method_is_implemented, methods, properties, ObjectClass};
let mut erwartet = BTreeMap::new();
for class in ObjectClass::ALL {
for p in properties(class) {
erwartet.insert(
format!("{}.{}", class.name(), p.name),
("Eigenschaft", IMPLEMENTIERT, "tb-ui::forms"),
);
}
for m in methods(class) {
erwartet.insert(
format!("{}.{m}", class.name()),
if method_is_implemented(class, m) {
("Methode", IMPLEMENTIERT, "tb-ui::forms")
} else {
("Methode", OFFEN, "-")
},
);
}
for e in events(class) {
erwartet.insert(
format!("{}_{e}", class.name()),
("Ereignis", IMPLEMENTIERT, "tb-vm::interp"),
);
}
}
let gefunden: BTreeMap<_, _> = inv
.iter()
.filter(|e| matches!(e.art.as_str(), "Eigenschaft" | "Methode" | "Ereignis"))
.map(|e| (e.name.to_uppercase(), e))
.collect();
let mut fehler = Vec::new();
for (name, (art, status, fundstelle)) in &erwartet {
match gefunden.get(name) {
None => fehler.push(format!("`{name}` fehlt")),
Some(e)
if e.art != *art
|| e.status != *status
|| e.fundstelle != *fundstelle
|| e.quelle != "forms-referenz" =>
{
fehler.push(format!("`{name}`: Art/Status/Fundstelle/Quelle falsch"))
}
Some(_) => {}
}
}
for name in gefunden.keys() {
if !erwartet.contains_key(name) {
fehler.push(format!("`{name}` ist nicht in der Klassentabelle"));
}
}
fehler
}
#[test]
fn forms_inventar_stimmt_mit_der_klassentabelle_ueberein() {
let fehler = pruefe_forms(&inventar());
assert!(
fehler.is_empty(),
"Forms-Inventar weicht ab:\n {}",
fehler.join("\n ")
);
}
#[test]
fn falsch_gesetzte_eigenschaft_wird_erkannt() {
let mut inv = inventar();
let e = inv
.iter_mut()
.find(|e| e.name == "TEXTBOX.SELSTART")
.unwrap();
e.status = OFFEN.into();
assert!(pruefe_forms(&inv)
.iter()
.any(|f| f.contains("TEXTBOX.SELSTART")));
}
#[test]
fn non_features_nennen_eine_fundstelle_in_der_sprachreferenz() {
for e in inventar() {
@@ -343,11 +434,7 @@ fn inventar_stimmt_mit_code_ueberein() {
fn abdeckungsstand_wird_ausgewiesen() {
let inv = inventar();
let zaehle = |s: &str| inv.iter().filter(|e| e.status == s).count();
let (i, o, n) = (
zaehle(IMPLEMENTIERT),
zaehle(OFFEN),
zaehle(NON_FEATURE),
);
let (i, o, n) = (zaehle(IMPLEMENTIERT), zaehle(OFFEN), zaehle(NON_FEATURE));
println!(
"Abdeckung: implementiert {i} · offen {o} · Non-Feature {n} · gesamt {}",
inv.len()
@@ -384,10 +471,22 @@ fn dokumentierte_elemente_werden_namentlich_abgewiesen() {
("CALLS Foo", "CALLS", "Feature unavailable"),
// Non-Feature über die Syntax (teilt das Schlüsselwort)
("LINE (1, 1)-(2, 2)", "LINE", "Feature unavailable"),
("GET (1, 1)-(2, 2), A", "GET (Grafik)", "Feature unavailable"),
(r#"OPEN "COM1:9600,N,8,1" AS #1"#, "OPEN COM", "Feature unavailable"),
(
"GET (1, 1)-(2, 2), A",
"GET (Grafik)",
"Feature unavailable",
),
(
r#"OPEN "COM1:9600,N,8,1" AS #1"#,
"OPEN COM",
"Feature unavailable",
),
// Ereignisgerät als Funktionsform
("ON PEN GOSUB Ziel\nZiel:\nRETURN", "ON PEN", "Feature unavailable"),
(
"ON PEN GOSUB Ziel\nZiel:\nRETURN",
"ON PEN",
"Feature unavailable",
),
];
for (quelle, element, erwartet) in faelle {
@@ -411,7 +510,12 @@ fn dokumentierte_elemente_werden_namentlich_abgewiesen() {
/// generischen Syntaxfehler.
#[test]
fn offene_elemente_erzeugen_keinen_syntaxfehler() {
for quelle in ["CLS", "LOCATE 5, 10", "COLOR 14, 1", r#"OPEN "d.txt" FOR OUTPUT AS #1"#] {
for quelle in [
"CLS",
"LOCATE 5, 10",
"COLOR 14, 1",
r#"OPEN "d.txt" FOR OUTPUT AS #1"#,
] {
let a = tb_frontend::analyze_source("probe.bas", quelle);
assert!(
a.diagnostics.is_empty(),