OpenSpec-Review dokumentieren und ISAM-Vertragsabweichungen beheben
This commit is contained in:
@@ -13,6 +13,9 @@
|
||||
//! nur die untere Schranke, nie eine feste Größe, und der Korpus muss das
|
||||
//! nachweisen können.
|
||||
|
||||
#[path = "../../../tests/support/prozessfrist.rs"]
|
||||
mod prozessfrist;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
@@ -354,6 +357,10 @@ impl Drop for TempVerzeichnis {
|
||||
|
||||
/// Korpusdatei ausführen und den Bildschirm-Snapshot liefern.
|
||||
fn run_corpus_file(path: &Path, groesse: Option<(usize, usize)>) -> String {
|
||||
let _frist = prozessfrist::Prozessfrist::neu(
|
||||
&path.display().to_string(),
|
||||
std::time::Duration::from_secs(30),
|
||||
);
|
||||
let raw = std::fs::read_to_string(path).unwrap();
|
||||
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
||||
let form = if path
|
||||
@@ -608,6 +615,51 @@ fn tbc_run_hello() {
|
||||
assert_eq!(String::from_utf8_lossy(&out.stdout), "Hallo, Welt!\n");
|
||||
}
|
||||
|
||||
/// OPEN darf keinen zweiten Writer auf dieselbe Datenbank anfordern.
|
||||
/// Der Unterprozess wird auch bei einer Regression nach fünf Sekunden beendet.
|
||||
#[test]
|
||||
fn tbc_isam_open_in_transaktion_hat_eine_harte_frist() {
|
||||
use std::process::Stdio;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
let dir = std::env::temp_dir().join(format!("tb_isam_open_{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_tbc"))
|
||||
.arg("run")
|
||||
.arg(compat_dir().join("isamopen.bas"))
|
||||
.current_dir(&dir)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("tbc startet");
|
||||
let start = Instant::now();
|
||||
let beendet = loop {
|
||||
match child.try_wait() {
|
||||
Ok(Some(_)) => break true,
|
||||
Ok(None) if start.elapsed() < Duration::from_secs(5) => {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
_ => {
|
||||
let _ = child.kill();
|
||||
break false;
|
||||
}
|
||||
}
|
||||
};
|
||||
let out = child.wait_with_output().unwrap();
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
assert!(
|
||||
beendet,
|
||||
"OPEN innerhalb einer Transaktion hat die Prozessfrist überschritten: {out:?}"
|
||||
);
|
||||
assert!(out.status.success(), "{out:?}");
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&out.stdout),
|
||||
include_str!("../../../tests/compat/isamopen.out")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tbc_run_stop_exitcode() {
|
||||
let exe = env!("CARGO_BIN_EXE_tbc");
|
||||
|
||||
@@ -1283,6 +1283,9 @@ fn bi_open(
|
||||
let nummer = arg_i32(a, 1)?;
|
||||
let modus = fio::Modus::aus_text(&arg_str(a, 2)?).ok_or(RuntimeError::ILLEGAL_FUNCTION_CALL)?;
|
||||
let reclen = arg_i32(a, 3)?.max(1) as usize;
|
||||
if st.isam.ist_isam(nummer) {
|
||||
return Err(RuntimeError::FILE_ALREADY_OPEN);
|
||||
}
|
||||
st.dateien
|
||||
.oeffnen(nummer, &pfad, modus, reclen)
|
||||
.map(|_| None)
|
||||
@@ -1305,7 +1308,7 @@ fn bi_close_all(
|
||||
_: &mut dyn Host,
|
||||
_: &mut [Value],
|
||||
) -> Result<Option<Value>, RuntimeError> {
|
||||
st.isam.alles_schliessen();
|
||||
st.isam.bindungen_schliessen();
|
||||
st.dateien.alle_schliessen().map(|_| None)
|
||||
}
|
||||
|
||||
@@ -1427,7 +1430,10 @@ fn bi_freefile(
|
||||
_: &mut dyn Host,
|
||||
_: &mut [Value],
|
||||
) -> Result<Option<Value>, RuntimeError> {
|
||||
Ok(Some(Value::Int(st.dateien.freie_nummer() as i16)))
|
||||
let nummer = (st.dateien.freie_nummer()..=i16::MAX as i32)
|
||||
.find(|n| !st.dateien.ist_offen(*n) && !st.isam.ist_isam(*n))
|
||||
.ok_or(RuntimeError::TOO_MANY_FILES)?;
|
||||
Ok(Some(Value::Int(nummer as i16)))
|
||||
}
|
||||
|
||||
fn bi_fileattr(
|
||||
@@ -2410,6 +2416,9 @@ fn bi_isam_open(
|
||||
let tabelle = arg_str(a, 2)?;
|
||||
let spalten = arg_str(a, 3)?;
|
||||
let udt = arg_i32(a, 4)? as u16;
|
||||
if st.dateien.ist_offen(nummer) {
|
||||
return Err(RuntimeError::FILE_ALREADY_OPEN);
|
||||
}
|
||||
st.isam
|
||||
.oeffnen(nummer, &datenbank, &tabelle, &spalten, udt)
|
||||
.map(|_| None)
|
||||
|
||||
@@ -148,15 +148,15 @@ struct Bindung {
|
||||
|
||||
// ---- Rücknahmeprotokoll (D6) -----------------------------------------------
|
||||
|
||||
/// Ein Protokolleintrag beschreibt, wie eine Satzoperation rückgängig
|
||||
/// gemacht wird. Er entsteht bei jeder Satzoperation innerhalb einer
|
||||
/// Transaktion.
|
||||
/// Rücknahme einer Satz- oder Strukturänderung. Satzlayouts gehören zum
|
||||
/// Eintrag, damit CLOSE die Rücknahme nicht von einer Bindung abhängig macht.
|
||||
#[derive(Debug, Clone)]
|
||||
enum Undo {
|
||||
/// Der Satz wurde eingefügt — Rücknahme entfernt ihn wieder.
|
||||
Eingefuegt {
|
||||
pfad: PathBuf,
|
||||
tabelle: String,
|
||||
layout: Layout,
|
||||
id: u64,
|
||||
},
|
||||
/// Der Satz wurde geändert oder gelöscht — Rücknahme stellt die
|
||||
@@ -164,9 +164,32 @@ enum Undo {
|
||||
Vorher {
|
||||
pfad: PathBuf,
|
||||
tabelle: String,
|
||||
layout: Layout,
|
||||
id: u64,
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
Index {
|
||||
pfad: PathBuf,
|
||||
tabelle: String,
|
||||
name: String,
|
||||
definitionen: Option<Vec<u8>>,
|
||||
/// None: Der Index wurde erst nach dem Bezugspunkt angelegt.
|
||||
eintraege: Option<Vec<(Vec<u8>, u64)>>,
|
||||
},
|
||||
Tabelle {
|
||||
pfad: PathBuf,
|
||||
tabelle: String,
|
||||
/// tab, idx, seq; tab = None kennzeichnet eine neu angelegte Tabelle.
|
||||
metadaten: [Option<Vec<u8>>; 3],
|
||||
saetze: Vec<(u64, Vec<u8>)>,
|
||||
indizes: Vec<Indexstand>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Indexstand {
|
||||
name: String,
|
||||
eintraege: Vec<(Vec<u8>, u64)>,
|
||||
}
|
||||
|
||||
impl Undo {
|
||||
@@ -174,6 +197,30 @@ impl Undo {
|
||||
match self {
|
||||
Undo::Eingefuegt { .. } => 0,
|
||||
Undo::Vorher { bytes, .. } => bytes.len(),
|
||||
Undo::Index {
|
||||
definitionen,
|
||||
eintraege,
|
||||
..
|
||||
} => {
|
||||
definitionen.as_ref().map_or(0, Vec::len)
|
||||
+ eintraege
|
||||
.as_ref()
|
||||
.map_or(0, |v| v.iter().map(|(k, _)| k.len() + 8).sum())
|
||||
}
|
||||
Undo::Tabelle {
|
||||
metadaten,
|
||||
saetze,
|
||||
indizes,
|
||||
..
|
||||
} => {
|
||||
metadaten.iter().flatten().map(Vec::len).sum::<usize>()
|
||||
+ saetze.iter().map(|(_, b)| b.len() + 8).sum::<usize>()
|
||||
+ indizes
|
||||
.iter()
|
||||
.flat_map(|i| &i.eintraege)
|
||||
.map(|(k, _)| k.len() + 8)
|
||||
.sum::<usize>()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,38 +601,33 @@ impl Isam {
|
||||
spalten: namen.iter().map(|n| n.to_string()).zip(felder).collect(),
|
||||
};
|
||||
|
||||
let pfad = fileio::pfad_normieren(datenbank);
|
||||
self.db_oeffnen(&pfad)?;
|
||||
let pfad = self.db_oeffnen(&datenbank_pfad(datenbank)?)?;
|
||||
|
||||
// Tabelle anlegen oder ihr Layout abgleichen.
|
||||
let db = self.dbs.get(&pfad).ok_or(INKONSISTENT)?;
|
||||
let txn = db.begin_write().map_err(|_| INKONSISTENT)?;
|
||||
let ergebnis = (|| -> Result<(), RuntimeError> {
|
||||
// Auch OPEN muss die bereits laufende Schreibtransaktion verwenden.
|
||||
self.schreiben(&pfad, |txn, log| {
|
||||
let schluessel = format!("tab:{tabelle}");
|
||||
match meta_lesen(&txn, &schluessel)? {
|
||||
match meta_lesen(txn, &schluessel)? {
|
||||
Some(vorhanden) => {
|
||||
if vorhanden != layout.signatur().as_bytes() {
|
||||
return Err(INKONSISTENT);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
meta_schreiben(&txn, &schluessel, layout.signatur().as_bytes())?;
|
||||
// Satztabelle sofort anlegen, damit ein Durchlauf über
|
||||
// eine noch leere Tabelle nicht auf einen Fehler läuft.
|
||||
meta_schreiben(txn, &schluessel, layout.signatur().as_bytes())?;
|
||||
let name = satz_def(tabelle);
|
||||
let def: SatzTab = TableDefinition::new(&name);
|
||||
txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
log.push(Undo::Tabelle {
|
||||
pfad: pfad.clone(),
|
||||
tabelle: tabelle.to_string(),
|
||||
metadaten: [None, None, None],
|
||||
saetze: Vec::new(),
|
||||
indizes: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
match ergebnis {
|
||||
Ok(()) => txn.commit().map_err(|_| INKONSISTENT)?,
|
||||
Err(e) => {
|
||||
let _ = txn.abort();
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
self.bindungen.insert(
|
||||
nummer,
|
||||
@@ -604,14 +646,17 @@ impl Isam {
|
||||
}
|
||||
|
||||
/// Datenbankdatei öffnen bzw. anlegen und ihre Formatversion prüfen.
|
||||
fn db_oeffnen(&mut self, pfad: &Path) -> Result<(), RuntimeError> {
|
||||
fn db_oeffnen(&mut self, pfad: &Path) -> Result<PathBuf, RuntimeError> {
|
||||
if self.dbs.contains_key(pfad) {
|
||||
return Ok(());
|
||||
return Ok(pfad.to_path_buf());
|
||||
}
|
||||
let neu = !pfad.exists();
|
||||
// Jeder Öffnungsfehler ist hier eine strukturell unlesbare Datei —
|
||||
// etwa weil sie gar keine Datenbankdatei ist.
|
||||
let db = Database::create(pfad).map_err(|_| INKONSISTENT)?;
|
||||
// Ein zuvor nicht auflösbarer Symlink kann durch die Anlage jetzt
|
||||
// auf sein Ziel zeigen. Nur diese endgültige Identität weitergeben.
|
||||
let pfad = pfad.canonicalize().map_err(|_| INKONSISTENT)?;
|
||||
{
|
||||
let txn = db.begin_write().map_err(|_| INKONSISTENT)?;
|
||||
let ergebnis = (|| -> Result<(), RuntimeError> {
|
||||
@@ -640,8 +685,8 @@ impl Isam {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dbs.insert(pfad.to_path_buf(), db);
|
||||
Ok(())
|
||||
self.dbs.insert(pfad.clone(), db);
|
||||
Ok(pfad)
|
||||
}
|
||||
|
||||
pub fn ist_isam(&self, nummer: i32) -> bool {
|
||||
@@ -661,7 +706,12 @@ impl Isam {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Programmende bzw. `RESET`: Bindungen lösen und Dateien freigeben.
|
||||
/// `CLOSE` ohne Nummer bzw. `RESET`: nur die Bindungen lösen.
|
||||
pub fn bindungen_schliessen(&mut self) {
|
||||
self.bindungen.clear();
|
||||
}
|
||||
|
||||
/// Programmende: Bindungen lösen und Dateien freigeben.
|
||||
/// Eine nicht festgeschriebene Transaktion verfällt — sie abzubrechen
|
||||
/// ist die Bedeutung von „nicht festgeschrieben".
|
||||
pub fn alles_schliessen(&mut self) {
|
||||
@@ -685,7 +735,9 @@ impl Isam {
|
||||
pfad: &Path,
|
||||
f: impl FnOnce(&WriteTransaction, &mut Vec<Undo>) -> Result<T, RuntimeError>,
|
||||
) -> Result<T, RuntimeError> {
|
||||
let Isam { dbs, trans, .. } = self;
|
||||
let Isam {
|
||||
dbs, trans, belegt, ..
|
||||
} = self;
|
||||
let db = dbs.get(pfad).ok_or(INKONSISTENT)?;
|
||||
match trans {
|
||||
Some(t) => {
|
||||
@@ -695,7 +747,10 @@ impl Isam {
|
||||
}
|
||||
let Transaktion { txns, log, .. } = t;
|
||||
let txn = txns.get(pfad).ok_or(INKONSISTENT)?;
|
||||
f(txn, log)
|
||||
let vorher = log.len();
|
||||
let ergebnis = f(txn, log);
|
||||
*belegt += log[vorher..].iter().map(|e| e.bytes() as i64).sum::<i64>();
|
||||
ergebnis
|
||||
}
|
||||
None => {
|
||||
let txn = db.begin_write().map_err(|_| INKONSISTENT)?;
|
||||
@@ -787,7 +842,9 @@ impl Isam {
|
||||
let layout = b.layout.clone();
|
||||
let tabelle = b.tabelle.clone();
|
||||
let udts = self.udts.clone();
|
||||
self.schreiben(&b.pfad.clone(), move |txn, _log| {
|
||||
let protokollieren = self.trans.is_some();
|
||||
let frei = self.setmem(0) as usize;
|
||||
self.schreiben(&b.pfad.clone(), move |txn, log| {
|
||||
let mut defs = indizes_lesen(txn, &tabelle)?;
|
||||
if defs.iter().any(|d| d.name.eq_ignore_ascii_case(name)) {
|
||||
return Err(RuntimeError::DUPLICATE_DEFINITION);
|
||||
@@ -824,6 +881,16 @@ impl Isam {
|
||||
}
|
||||
}
|
||||
|
||||
let undo = Undo::Index {
|
||||
pfad: b.pfad.clone(),
|
||||
tabelle: tabelle.clone(),
|
||||
name: name.to_string(),
|
||||
definitionen: meta_lesen(txn, &format!("idx:{tabelle}"))?,
|
||||
eintraege: None,
|
||||
};
|
||||
if protokollieren && undo.bytes() > frei {
|
||||
return Err(PUFFER_ZU_KLEIN);
|
||||
}
|
||||
let idx_name = index_def(&tabelle, name);
|
||||
let idx_tab: IndexTab = TableDefinition::new(&idx_name);
|
||||
let mut t = txn.open_table(idx_tab).map_err(|_| INKONSISTENT)?;
|
||||
@@ -832,7 +899,9 @@ impl Isam {
|
||||
}
|
||||
drop(t);
|
||||
defs.push(def);
|
||||
indizes_schreiben(txn, &tabelle, &defs)
|
||||
indizes_schreiben(txn, &tabelle, &defs)?;
|
||||
log.push(undo);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -840,20 +909,42 @@ impl Isam {
|
||||
pub fn index_loeschen(&mut self, nummer: i32, name: &str) -> Result<(), RuntimeError> {
|
||||
let b = self.bindung(nummer)?.clone();
|
||||
let tabelle = b.tabelle.clone();
|
||||
self.schreiben(&b.pfad.clone(), |txn, _| {
|
||||
let protokollieren = self.trans.is_some();
|
||||
let frei = self.setmem(0) as usize;
|
||||
self.schreiben(&b.pfad.clone(), |txn, log| {
|
||||
let mut defs = indizes_lesen(txn, &tabelle)?;
|
||||
let Some(i) = defs.iter().position(|d| d.name.eq_ignore_ascii_case(name)) else {
|
||||
return Err(INDEX_FEHLT);
|
||||
};
|
||||
let undo = if protokollieren {
|
||||
let u = Undo::Index {
|
||||
pfad: b.pfad.clone(),
|
||||
tabelle: tabelle.clone(),
|
||||
name: defs[i].name.clone(),
|
||||
definitionen: meta_lesen(txn, &format!("idx:{tabelle}"))?,
|
||||
eintraege: Some(indexstand_lesen(txn, &tabelle, &defs[i].name)?.eintraege),
|
||||
};
|
||||
if u.bytes() > frei {
|
||||
return Err(PUFFER_ZU_KLEIN);
|
||||
}
|
||||
Some(u)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let idx_name = index_def(&tabelle, &defs[i].name);
|
||||
let idx_tab: IndexTab = TableDefinition::new(&idx_name);
|
||||
txn.delete_table(idx_tab).map_err(|_| INKONSISTENT)?;
|
||||
defs.remove(i);
|
||||
indizes_schreiben(txn, &tabelle, &defs)
|
||||
indizes_schreiben(txn, &tabelle, &defs)?;
|
||||
log.extend(undo);
|
||||
Ok(())
|
||||
})?;
|
||||
// Ein gelöschter Index darf nicht aktiv bleiben.
|
||||
if let Some(bind) = self.bindungen.get_mut(&nummer) {
|
||||
if bind.index.eq_ignore_ascii_case(name) {
|
||||
for bind in self.bindungen.values_mut() {
|
||||
if bind.pfad == b.pfad
|
||||
&& bind.tabelle == tabelle
|
||||
&& bind.index.eq_ignore_ascii_case(name)
|
||||
{
|
||||
bind.index.clear();
|
||||
bind.satz = None;
|
||||
}
|
||||
@@ -905,25 +996,51 @@ impl Isam {
|
||||
if !pfad.exists() {
|
||||
return Err(TABELLE_FEHLT);
|
||||
}
|
||||
self.db_oeffnen(&pfad)?;
|
||||
let tab = tabelle.to_string();
|
||||
self.schreiben(&pfad, move |txn, _| {
|
||||
if meta_lesen(txn, &format!("tab:{tab}"))?.is_none() {
|
||||
let pfad = self.db_oeffnen(&datenbank_pfad(datenbank)?)?;
|
||||
let protokollieren = self.trans.is_some();
|
||||
let frei = self.setmem(0) as usize;
|
||||
self.schreiben(&pfad, |txn, log| {
|
||||
if meta_lesen(txn, &format!("tab:{tabelle}"))?.is_none() {
|
||||
return Err(TABELLE_FEHLT);
|
||||
}
|
||||
for d in indizes_lesen(txn, &tab)? {
|
||||
let name = index_def(&tab, &d.name);
|
||||
let idx: IndexTab = TableDefinition::new(&name);
|
||||
txn.delete_table(idx).map_err(|_| INKONSISTENT)?;
|
||||
}
|
||||
let name = satz_def(&tab);
|
||||
let satz: SatzTab = TableDefinition::new(&name);
|
||||
txn.delete_table(satz).map_err(|_| INKONSISTENT)?;
|
||||
meta_entfernen(txn, &format!("tab:{tab}"))?;
|
||||
meta_entfernen(txn, &format!("idx:{tab}"))?;
|
||||
meta_entfernen(txn, &format!("seq:{tab}"))?;
|
||||
let undo = if protokollieren {
|
||||
let name = satz_def(tabelle);
|
||||
let def: SatzTab = TableDefinition::new(&name);
|
||||
let t = txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
let mut saetze = Vec::new();
|
||||
for e in t.iter().map_err(|_| INKONSISTENT)? {
|
||||
let (id, bytes) = e.map_err(|_| INKONSISTENT)?;
|
||||
saetze.push((id.value(), bytes.value().to_vec()));
|
||||
}
|
||||
let mut indizes = Vec::new();
|
||||
for d in indizes_lesen(txn, tabelle)? {
|
||||
indizes.push(indexstand_lesen(txn, tabelle, &d.name)?);
|
||||
}
|
||||
let u = Undo::Tabelle {
|
||||
pfad: pfad.clone(),
|
||||
tabelle: tabelle.to_string(),
|
||||
metadaten: [
|
||||
meta_lesen(txn, &format!("tab:{tabelle}"))?,
|
||||
meta_lesen(txn, &format!("idx:{tabelle}"))?,
|
||||
meta_lesen(txn, &format!("seq:{tabelle}"))?,
|
||||
],
|
||||
saetze,
|
||||
indizes,
|
||||
};
|
||||
if u.bytes() > frei {
|
||||
return Err(PUFFER_ZU_KLEIN);
|
||||
}
|
||||
Some(u)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
tabelle_entfernen(txn, tabelle)?;
|
||||
log.extend(undo);
|
||||
Ok(())
|
||||
})
|
||||
})?;
|
||||
self.bindungen
|
||||
.retain(|_, b| b.pfad != pfad || b.tabelle != tabelle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---- Satzoperationen ----------------------------------------------------
|
||||
@@ -944,11 +1061,11 @@ impl Isam {
|
||||
log.push(Undo::Eingefuegt {
|
||||
pfad: fuer_log,
|
||||
tabelle: tabelle.clone(),
|
||||
layout,
|
||||
id,
|
||||
});
|
||||
Ok(id)
|
||||
})?;
|
||||
self.belegt += bytes_von(&self.trans);
|
||||
let bind = self.bindungen.get_mut(&nummer).ok_or(INKONSISTENT)?;
|
||||
// Das Vorbild lässt den Cursor beim Einfügen stehen; er zeigt auf
|
||||
// den neuen Satz, damit `INSERT` und `RETRIEVE` zusammenpassen.
|
||||
@@ -989,12 +1106,12 @@ impl Isam {
|
||||
log.push(Undo::Vorher {
|
||||
pfad: fuer_log,
|
||||
tabelle: tabelle.clone(),
|
||||
layout,
|
||||
id,
|
||||
bytes: alt,
|
||||
});
|
||||
Ok(())
|
||||
})?;
|
||||
self.belegt += bytes_von(&self.trans);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1015,18 +1132,22 @@ impl Isam {
|
||||
let pfad = b.pfad.clone();
|
||||
let fuer_log = pfad.clone();
|
||||
let udts = self.udts.clone();
|
||||
let frei = self.setmem(0) as usize;
|
||||
self.schreiben(&pfad, move |txn, log| {
|
||||
let alt = satz_bytes_lesen(txn, &tabelle, id)?.ok_or(KEIN_SATZ)?;
|
||||
if alt.len() > frei {
|
||||
return Err(PUFFER_ZU_KLEIN);
|
||||
}
|
||||
satz_schreiben(txn, &tabelle, &layout, &udts, id, None, Some(&alt))?;
|
||||
log.push(Undo::Vorher {
|
||||
pfad: fuer_log,
|
||||
tabelle: tabelle.clone(),
|
||||
layout,
|
||||
id,
|
||||
bytes: alt,
|
||||
});
|
||||
Ok(())
|
||||
})?;
|
||||
self.belegt += bytes_von(&self.trans);
|
||||
let bind = self.bindungen.get_mut(&nummer).ok_or(INKONSISTENT)?;
|
||||
bind.satz = nachfolger;
|
||||
bind.eof = nachfolger.is_none();
|
||||
@@ -1260,7 +1381,7 @@ impl Isam {
|
||||
let _ = txn.abort();
|
||||
}
|
||||
self.belegt = 0;
|
||||
self.cursor_loesen();
|
||||
self.cursor_loesen()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1282,7 +1403,7 @@ impl Isam {
|
||||
}
|
||||
};
|
||||
self.protokoll_abspielen(ziel)?;
|
||||
self.cursor_loesen();
|
||||
self.cursor_loesen()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1299,8 +1420,12 @@ impl Isam {
|
||||
self.belegt -= eintrag.bytes() as i64;
|
||||
let udts = self.udts.clone();
|
||||
match eintrag {
|
||||
Undo::Eingefuegt { pfad, tabelle, id } => {
|
||||
let layout = self.layout_von(&pfad, &tabelle)?;
|
||||
Undo::Eingefuegt {
|
||||
pfad,
|
||||
tabelle,
|
||||
layout,
|
||||
id,
|
||||
} => {
|
||||
self.schreiben(&pfad, move |txn, _| {
|
||||
let alt = satz_bytes_lesen(txn, &tabelle, id)?;
|
||||
satz_schreiben(txn, &tabelle, &layout, &udts, id, None, alt.as_deref())
|
||||
@@ -1309,10 +1434,10 @@ impl Isam {
|
||||
Undo::Vorher {
|
||||
pfad,
|
||||
tabelle,
|
||||
layout,
|
||||
id,
|
||||
bytes,
|
||||
} => {
|
||||
let layout = self.layout_von(&pfad, &tabelle)?;
|
||||
self.schreiben(&pfad, move |txn, _| {
|
||||
let alt = satz_bytes_lesen(txn, &tabelle, id)?;
|
||||
satz_schreiben(
|
||||
@@ -1326,28 +1451,88 @@ impl Isam {
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Undo::Index {
|
||||
pfad,
|
||||
tabelle,
|
||||
name,
|
||||
definitionen,
|
||||
eintraege,
|
||||
} => {
|
||||
self.schreiben(&pfad, |txn, _| {
|
||||
let tab = index_def(&tabelle, &name);
|
||||
let def: IndexTab = TableDefinition::new(&tab);
|
||||
txn.delete_table(def).map_err(|_| INKONSISTENT)?;
|
||||
if let Some(eintraege) = eintraege {
|
||||
indexstand_schreiben(txn, &tabelle, &Indexstand { name, eintraege })?;
|
||||
}
|
||||
meta_wiederherstellen(
|
||||
txn,
|
||||
&format!("idx:{tabelle}"),
|
||||
definitionen.as_deref(),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Undo::Tabelle {
|
||||
pfad,
|
||||
tabelle,
|
||||
metadaten,
|
||||
saetze,
|
||||
indizes,
|
||||
} => {
|
||||
self.schreiben(&pfad, |txn, _| {
|
||||
tabelle_entfernen(txn, &tabelle)?;
|
||||
if metadaten[0].is_some() {
|
||||
let name = satz_def(&tabelle);
|
||||
let def: SatzTab = TableDefinition::new(&name);
|
||||
let mut t = txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
for (id, bytes) in &saetze {
|
||||
t.insert(*id, bytes.as_slice()).map_err(|_| INKONSISTENT)?;
|
||||
}
|
||||
for index in &indizes {
|
||||
indexstand_schreiben(txn, &tabelle, index)?;
|
||||
}
|
||||
}
|
||||
for (praefix, wert) in ["tab", "idx", "seq"].iter().zip(&metadaten) {
|
||||
meta_wiederherstellen(
|
||||
txn,
|
||||
&format!("{praefix}:{tabelle}"),
|
||||
wert.as_deref(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Layout einer Tabelle über eine bestehende Bindung finden. Die
|
||||
/// Rücknahme läuft nur über Tabellen, die im Programm offen sind.
|
||||
fn layout_von(&self, pfad: &Path, tabelle: &str) -> Result<Layout, RuntimeError> {
|
||||
self.bindungen
|
||||
.values()
|
||||
.find(|b| b.pfad == pfad && b.tabelle == tabelle)
|
||||
.map(|b| b.layout.clone())
|
||||
.ok_or(INKONSISTENT)
|
||||
}
|
||||
|
||||
/// Nach jeder Rücknahme gilt der Cursor als unpositioniert.
|
||||
fn cursor_loesen(&mut self) {
|
||||
for b in self.bindungen.values_mut() {
|
||||
b.satz = None;
|
||||
b.eof = false;
|
||||
b.bof = false;
|
||||
/// Nach Rücknahme keine veralteten Cursor, Indizes oder Layoutbindungen
|
||||
/// behalten. Zurückgenommene Tabellenanlagen geben ihre Nummer wieder frei.
|
||||
fn cursor_loesen(&mut self) -> Result<(), RuntimeError> {
|
||||
for (nummer, b) in self.bindungen.clone() {
|
||||
let (layout, indizes) = self.lesen(&b.pfad, |txn| {
|
||||
Ok((
|
||||
meta_lesen(txn, &format!("tab:{}", b.tabelle))?,
|
||||
indizes_lesen(txn, &b.tabelle)?,
|
||||
))
|
||||
})?;
|
||||
if layout.as_deref() != Some(b.layout.signatur().as_bytes()) {
|
||||
self.bindungen.remove(&nummer);
|
||||
continue;
|
||||
}
|
||||
let bind = self.bindungen.get_mut(&nummer).ok_or(INKONSISTENT)?;
|
||||
bind.satz = None;
|
||||
bind.eof = false;
|
||||
bind.bof = false;
|
||||
if !indizes
|
||||
.iter()
|
||||
.any(|d| d.name.eq_ignore_ascii_case(&bind.index))
|
||||
{
|
||||
bind.index.clear();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---- Pufferverwaltung (D7) ---------------------------------------------
|
||||
@@ -1369,17 +1554,84 @@ impl Isam {
|
||||
/// Sentinel-Kennung für `ROLLBACK ALL` (Gegenstück zu `ast::ROLLBACK_ALL`).
|
||||
pub const ROLLBACK_ALL: i32 = -1;
|
||||
|
||||
/// Wieviel Puffer belegt der zuletzt angelegte Protokolleintrag? Außerhalb
|
||||
/// einer Transaktion verfällt er sofort und belegt nichts.
|
||||
fn bytes_von(trans: &Option<Transaktion>) -> i64 {
|
||||
trans
|
||||
.as_ref()
|
||||
.and_then(|t| t.log.last())
|
||||
.map(|e| e.bytes() as i64)
|
||||
.unwrap_or(0)
|
||||
// ---- Satz- und Indexpflege (freie Funktionen auf einer Transaktion) --------
|
||||
|
||||
/// Stabile Identität auch nach CHDIR und über relative/absolute Aliase.
|
||||
/// Bei Neuanlage existiert nur der Elternpfad, der bereits kanonisch wird.
|
||||
fn datenbank_pfad(name: &str) -> Result<PathBuf, RuntimeError> {
|
||||
let pfad = fileio::pfad_normieren(name);
|
||||
match pfad.canonicalize() {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
let absolut = std::path::absolute(pfad).map_err(|_| INKONSISTENT)?;
|
||||
let eltern = absolut.parent().ok_or(INKONSISTENT)?;
|
||||
Ok(eltern
|
||||
.canonicalize()
|
||||
.map_err(|_| INKONSISTENT)?
|
||||
.join(absolut.file_name().ok_or(INKONSISTENT)?))
|
||||
}
|
||||
Err(_) => Err(INKONSISTENT),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Satz- und Indexpflege (freie Funktionen auf einer Transaktion) --------
|
||||
fn meta_wiederherstellen(
|
||||
txn: &WriteTransaction,
|
||||
key: &str,
|
||||
wert: Option<&[u8]>,
|
||||
) -> Result<(), RuntimeError> {
|
||||
match wert {
|
||||
Some(bytes) => meta_schreiben(txn, key, bytes),
|
||||
None => meta_entfernen(txn, key),
|
||||
}
|
||||
}
|
||||
|
||||
fn indexstand_lesen(
|
||||
txn: &WriteTransaction,
|
||||
tabelle: &str,
|
||||
name: &str,
|
||||
) -> Result<Indexstand, RuntimeError> {
|
||||
let tab = index_def(tabelle, name);
|
||||
let def: IndexTab = TableDefinition::new(&tab);
|
||||
let t = txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
let mut eintraege = Vec::new();
|
||||
for e in t.iter().map_err(|_| INKONSISTENT)? {
|
||||
let (k, id) = e.map_err(|_| INKONSISTENT)?;
|
||||
eintraege.push((k.value().to_vec(), id.value()));
|
||||
}
|
||||
Ok(Indexstand {
|
||||
name: name.to_string(),
|
||||
eintraege,
|
||||
})
|
||||
}
|
||||
|
||||
fn indexstand_schreiben(
|
||||
txn: &WriteTransaction,
|
||||
tabelle: &str,
|
||||
stand: &Indexstand,
|
||||
) -> Result<(), RuntimeError> {
|
||||
let name = index_def(tabelle, &stand.name);
|
||||
let def: IndexTab = TableDefinition::new(&name);
|
||||
let mut t = txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
for (k, id) in &stand.eintraege {
|
||||
t.insert(k.as_slice(), *id).map_err(|_| INKONSISTENT)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tabelle_entfernen(txn: &WriteTransaction, tabelle: &str) -> Result<(), RuntimeError> {
|
||||
for d in indizes_lesen(txn, tabelle)? {
|
||||
let name = index_def(tabelle, &d.name);
|
||||
let def: IndexTab = TableDefinition::new(&name);
|
||||
txn.delete_table(def).map_err(|_| INKONSISTENT)?;
|
||||
}
|
||||
let name = satz_def(tabelle);
|
||||
let def: SatzTab = TableDefinition::new(&name);
|
||||
txn.delete_table(def).map_err(|_| INKONSISTENT)?;
|
||||
for praefix in ["tab", "idx", "seq"] {
|
||||
meta_entfernen(txn, &format!("{praefix}:{tabelle}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn satz_bytes_lesen(
|
||||
txn: &WriteTransaction,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
//! Cursor und Rücknahmeprotokoll.
|
||||
|
||||
use super::*;
|
||||
#[path = "../../../../tests/support/prozessfrist.rs"]
|
||||
mod prozessfrist;
|
||||
use crate::value::RecordObj;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
@@ -176,6 +178,7 @@ fn satz(nummer: i32, name: &str) -> Value {
|
||||
|
||||
struct TempDb {
|
||||
dir: PathBuf,
|
||||
_frist: prozessfrist::Prozessfrist,
|
||||
}
|
||||
|
||||
impl TempDb {
|
||||
@@ -187,7 +190,10 @@ impl TempDb {
|
||||
));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
TempDb { dir }
|
||||
TempDb {
|
||||
dir,
|
||||
_frist: prozessfrist::Prozessfrist::neu(name, std::time::Duration::from_secs(30)),
|
||||
}
|
||||
}
|
||||
fn pfad(&self, datei: &str) -> String {
|
||||
self.dir.join(datei).to_string_lossy().to_string()
|
||||
@@ -506,3 +512,319 @@ fn gescheitertes_createindex_hinterlaesst_keinen_index() {
|
||||
"der gescheiterte Index darf nicht als Torso zurückbleiben"
|
||||
);
|
||||
}
|
||||
|
||||
fn bestand(i: &mut Isam, nummer: i32) -> Vec<(i32, String)> {
|
||||
let mut out = Vec::new();
|
||||
i.bewegen(nummer, Richtung::Erster).unwrap();
|
||||
while !i.eof(nummer) {
|
||||
let Value::Rec(r) = i.satz_lesen(nummer).unwrap() else {
|
||||
panic!()
|
||||
};
|
||||
let r = r.borrow();
|
||||
let (Value::Lng(n), Value::Str(name)) = (&r.fields[0], &r.fields[1]) else {
|
||||
panic!()
|
||||
};
|
||||
out.push((*n, name.trim_end().to_string()));
|
||||
i.bewegen(nummer, Richtung::Naechster).unwrap();
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn savepoints_nehmen_index_und_tabellenstrukturen_zurueck() {
|
||||
let temp = TempDb::neu("struktur_savepoints");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.index_anlegen(1, "Nummer", true, &["Nummer".into()])
|
||||
.unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.einfuegen(1, &satz(2, "Berta")).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
let a = i.sicherungspunkt().unwrap();
|
||||
i.index_anlegen(1, "Name", false, &["-Name".into()])
|
||||
.unwrap();
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
let b = i.sicherungspunkt().unwrap();
|
||||
i.index_loeschen(1, "nummer").unwrap();
|
||||
i.einfuegen(1, &satz(1, "Doppelt")).unwrap();
|
||||
i.schliessen(1).unwrap();
|
||||
i.ruecknahme(b.into()).unwrap();
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[(2, "Berta".into()), (1, "Anton".into())]
|
||||
);
|
||||
assert_eq!(
|
||||
i.einfuegen(1, &satz(1, "Doppelt")),
|
||||
Err(DOPPELTER_SCHLUESSEL)
|
||||
);
|
||||
|
||||
// Auch ohne offene Bindung muss DELETETABLE alle Daten sichern können.
|
||||
i.schliessen(1).unwrap();
|
||||
i.tabelle_loeschen(&pfad, "Kunden").unwrap();
|
||||
// Den gleichen Namen mit einem anderen Layout wiederverwenden.
|
||||
i.udts.push(UdtLayout {
|
||||
name: "NEU".into(),
|
||||
fields: vec![TypeInit::Int],
|
||||
});
|
||||
i.oeffnen(2, &pfad, "Kunden", "AndereSpalte", 1).unwrap();
|
||||
let neu = Value::Rec(Rc::new(RefCell::new(RecordObj {
|
||||
fields: vec![Value::Int(7)],
|
||||
})));
|
||||
i.einfuegen(2, &neu).unwrap();
|
||||
i.ruecknahme(a.into()).unwrap();
|
||||
assert!(
|
||||
!i.ist_isam(2),
|
||||
"Bindung mit zurückgenommenem Layout darf nicht bleiben"
|
||||
);
|
||||
assert_eq!(i.setmem(0), PUFFER_VORGABE);
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.index_setzen(1, "Name"), Err(INDEX_FEHLT));
|
||||
i.index_setzen(1, "Nummer").unwrap();
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[(1, "Anton".into()), (2, "Berta".into())]
|
||||
);
|
||||
i.einfuegen(1, &satz(3, "Clara")).unwrap();
|
||||
// Auch der gescheiterte INSERT reservierte eine ID; sie wird nicht wiederverwendet.
|
||||
assert_eq!(
|
||||
i.satznummer(1).unwrap(),
|
||||
5,
|
||||
"ID-Zähler der gelöschten Tabelle bleibt erhalten"
|
||||
);
|
||||
i.trans_festschreiben().unwrap();
|
||||
i.alles_schliessen();
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
assert_eq!(i.index_setzen(1, "Name"), Err(INDEX_FEHLT));
|
||||
i.index_setzen(1, "Nummer").unwrap();
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[
|
||||
(1, "Anton".into()),
|
||||
(2, "Berta".into()),
|
||||
(3, "Clara".into())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saetze_nach_close_aller_bindungen_zuruecknehmen() {
|
||||
let temp = TempDb::neu("saetze_close_savepoint");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.index_anlegen(1, "Name", true, &["Name".into()]).unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.einfuegen(1, &satz(2, "Berta")).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
i.einfuegen(1, &satz(3, "Clara")).unwrap();
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.aendern(1, &satz(3, "Neu")).unwrap();
|
||||
i.bewegen(1, Richtung::Erster).unwrap();
|
||||
i.satz_loeschen(1).unwrap();
|
||||
i.einfuegen(1, &satz(4, "Dora")).unwrap();
|
||||
i.bindungen_schliessen();
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satznummer(1).unwrap(), 0);
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[
|
||||
(1, "Anton".into()),
|
||||
(2, "Berta".into()),
|
||||
(3, "Clara".into())
|
||||
]
|
||||
);
|
||||
i.ruecknahme(ROLLBACK_ALL).unwrap();
|
||||
assert_eq!(i.satznummer(1).unwrap(), 0);
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[(1, "Anton".into()), (2, "Berta".into())]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neue_tabellen_und_aktive_indizes_verschwinden_bei_ruecknahme() {
|
||||
let temp = TempDb::neu("anlagen_rollback");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
for kennung in [0, ROLLBACK_ALL] {
|
||||
i.trans_beginn().unwrap();
|
||||
i.index_anlegen(1, "Neu", false, &["Name".into()]).unwrap();
|
||||
i.index_setzen(1, "Neu").unwrap();
|
||||
i.oeffnen(2, &pfad, "Neu", "Nummer,Name", 0).unwrap();
|
||||
i.einfuegen(2, &satz(1, "Anton")).unwrap();
|
||||
i.ruecknahme(kennung).unwrap();
|
||||
assert!(!i.ist_isam(2));
|
||||
assert_eq!(i.index_name(1).unwrap(), "");
|
||||
assert_eq!(i.index_setzen(1, "Neu"), Err(INDEX_FEHLT));
|
||||
assert_eq!(i.tabelle_loeschen(&pfad, "Neu"), Err(TABELLE_FEHLT));
|
||||
i.trans_festschreiben().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struktur_undo_prueft_puffer_vor_dem_loeschen() {
|
||||
let temp = TempDb::neu("struktur_puffer");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.index_anlegen(1, "Name", true, &["Name".into()]).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
i.setmem(-PUFFER_VORGABE);
|
||||
assert_eq!(i.index_loeschen(1, "Name"), Err(PUFFER_ZU_KLEIN));
|
||||
assert_eq!(i.tabelle_loeschen(&pfad, "Kunden"), Err(PUFFER_ZU_KLEIN));
|
||||
assert_eq!(
|
||||
i.index_anlegen(1, "Neu", false, &["Nummer".into()]),
|
||||
Err(PUFFER_ZU_KLEIN)
|
||||
);
|
||||
assert_eq!(i.index_setzen(1, "Neu"), Err(INDEX_FEHLT));
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
assert_eq!(bestand(&mut i, 1), [(1, "Anton".into())]);
|
||||
assert_eq!(i.trans.as_ref().unwrap().log.len(), 0);
|
||||
i.setmem(PUFFER_VORGABE);
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.index_loeschen(1, "Name").unwrap();
|
||||
assert!(i.setmem(0) < PUFFER_VORGABE);
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
assert_eq!(i.setmem(0), PUFFER_VORGABE);
|
||||
i.ruecknahme(ROLLBACK_ALL).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pfadaliase_teilen_transaktion_und_ruecknahme() {
|
||||
let temp = TempDb::neu("pfadaliase");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
std::fs::create_dir(temp.dir.join("unter")).unwrap();
|
||||
let alias = temp.pfad("unter/../db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.trans_beginn().unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.oeffnen(2, &alias, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(2).unwrap(), 1);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let link = temp.pfad("link.isam");
|
||||
std::os::unix::fs::symlink(&pfad, &link).unwrap();
|
||||
i.oeffnen(3, &link, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(3).unwrap(), 1);
|
||||
}
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.tabelle_loeschen(&alias, "Kunden").unwrap();
|
||||
assert!(!i.ist_isam(1));
|
||||
assert!(!i.ist_isam(2));
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 1);
|
||||
i.ruecknahme(ROLLBACK_ALL).unwrap();
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletetable_loest_alle_betroffenen_bindungen() {
|
||||
let temp = TempDb::neu("deletetable_bindungen");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.oeffnen(2, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
i.oeffnen(3, &pfad, "Andere", "Nummer,Name", 0).unwrap();
|
||||
i.index_anlegen(1, "Name", true, &["Name".into()]).unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.tabelle_loeschen(&pfad, "Kunden").unwrap();
|
||||
for n in [1, 2] {
|
||||
assert_eq!(
|
||||
i.einfuegen(n, &satz(9, "Geist")),
|
||||
Err(RuntimeError::BAD_FILE_NAME_OR_NUMBER)
|
||||
);
|
||||
}
|
||||
assert!(i.ist_isam(3));
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
assert!(
|
||||
!i.ist_isam(1),
|
||||
"Rücknahme öffnet keine geschlossenen Bindungen"
|
||||
);
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
assert_eq!(bestand(&mut i, 1), [(1, "Anton".into())]);
|
||||
assert_eq!(i.einfuegen(1, &satz(9, "Anton")), Err(DOPPELTER_SCHLUESSEL));
|
||||
i.trans_festschreiben().unwrap();
|
||||
i.tabelle_loeschen(&pfad, "Kunden").unwrap();
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 0);
|
||||
assert_eq!(i.index_setzen(1, "Name"), Err(INDEX_FEHLT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_pufferfehler_erhaelt_saetze_indizes_und_cursor() {
|
||||
let temp = TempDb::neu("delete_puffer");
|
||||
let mut i = isam_mit_tabelle(&temp.pfad("db.isam"));
|
||||
i.index_anlegen(1, "Name", true, &["Name".into()]).unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.einfuegen(1, &satz(2, "Berta")).unwrap();
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
let id = i.satznummer(1).unwrap();
|
||||
i.setmem(-PUFFER_VORGABE);
|
||||
assert_eq!(i.satz_loeschen(1), Err(PUFFER_ZU_KLEIN));
|
||||
assert_eq!(i.satznummer(1).unwrap(), id);
|
||||
assert_eq!(i.index_name(1).unwrap(), "Name");
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 2);
|
||||
assert!(!i.eof(1));
|
||||
assert!(!i.bof(1));
|
||||
i.trans_beginn().unwrap();
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
assert_eq!(i.satz_loeschen(1), Err(PUFFER_ZU_KLEIN));
|
||||
assert!(i.trans.as_ref().unwrap().log.is_empty());
|
||||
// Ein LONG und sechs UTF-32-Zeichen: genau ein Undo-Satz passt.
|
||||
i.setmem(28);
|
||||
i.satz_loeschen(1).unwrap();
|
||||
assert_eq!(i.setmem(0), 0);
|
||||
let nachfolger = i.satznummer(1).unwrap();
|
||||
assert_eq!(i.satz_loeschen(1), Err(PUFFER_ZU_KLEIN));
|
||||
assert_eq!(i.satznummer(1).unwrap(), nachfolger);
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 1);
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
assert_eq!(i.setmem(0), 28);
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[(1, "Anton".into()), (2, "Berta".into())]
|
||||
);
|
||||
i.trans_festschreiben().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn symlink_neuanlage_behaelt_die_dateiidentitaet() {
|
||||
for absolut in [false, true] {
|
||||
let temp = TempDb::neu(if absolut {
|
||||
"neuer_link_absolut"
|
||||
} else {
|
||||
"neuer_link_relativ"
|
||||
});
|
||||
let ziel = temp.pfad("ziel.isam");
|
||||
let alias = temp.pfad("alias.isam");
|
||||
std::os::unix::fs::symlink(if absolut { &ziel } else { "ziel.isam" }, &alias).unwrap();
|
||||
assert!(!Path::new(&ziel).exists());
|
||||
let mut i = isam_mit_tabelle(&alias);
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
i.einfuegen(1, &satz(2, "Berta")).unwrap();
|
||||
i.oeffnen(2, &alias, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
i.oeffnen(3, &ziel, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(2).unwrap(), 2);
|
||||
assert_eq!(i.satzzahl(3).unwrap(), 2);
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.tabelle_loeschen(&alias, "Kunden").unwrap();
|
||||
for n in 1..=3 {
|
||||
assert!(!i.ist_isam(n));
|
||||
}
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
i.oeffnen(1, &ziel, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 2);
|
||||
i.ruecknahme(ROLLBACK_ALL).unwrap();
|
||||
i.alles_schliessen();
|
||||
let mut neu = isam_mit_tabelle(&alias);
|
||||
assert_eq!(bestand(&mut neu, 1), [(1, "Anton".into())]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,54 @@ An jeden Indexschlüssel sind acht Bytes Satz-ID (Big-Endian) angehängt.
|
||||
Das macht auch Einträge eines mehrdeutigen Index eindeutig, hält Dubletten
|
||||
in Einfügereihenfolge und macht die Präfixsuche zur Bereichsabfrage.
|
||||
|
||||
**Datenbankidentität und Tabellenlöschung.** ISAM löst bestehende Datenbankpfade
|
||||
kanonisch auf; bei einer Neuanlage wird zuerst der Elternpfad aufgelöst.
|
||||
Nach der Anlage wird die endgültige Dateiidentität erneut bestimmt, sodass
|
||||
auch Symlinks auf zuvor fehlende Zieldateien korrekt gebunden bleiben.
|
||||
Relative Pfade beziehen sich auf das aktuelle Arbeitsverzeichnis. Ein späteres
|
||||
`CHDIR` ändert bestehende Bindungen nicht. Relative, absolute und über Symlinks
|
||||
auflösbare Aliase derselben vorhandenen Datei teilen einen Schreibkontext.
|
||||
`DELETETABLE` schließt alle Bindungen der entfernten Tabelle. Zugriffe über die
|
||||
alten Nummern melden Fehler 52. Nach Rücknahme der Löschung ist der Bestand
|
||||
wieder vorhanden, muss aber mit `OPEN` neu gebunden werden.
|
||||
|
||||
**Transaktionen und Sicherungspunkte.** Außerhalb von `BEGINTRANS` wird
|
||||
jede Änderung einzeln festgeschrieben. Innerhalb einer Transaktion teilen
|
||||
alle Bindungen derselben Datenbank einen Schreibkontext; auch ein weiteres
|
||||
`OPEN` sieht die bisherigen Änderungen und schreibt sie nicht fest.
|
||||
`CLOSE #n`, `CLOSE` und `RESET` lösen nur Bindungen. `COMMITTRANS` schreibt
|
||||
fest, `ROLLBACK ALL` bricht die Transaktion ab; eine beim Programmende
|
||||
offene Transaktion verfällt. Bei mehreren Datenbankdateien erfolgt die
|
||||
Festschreibung je Datei, ohne datenbankübergreifende Atomaritätsgarantie.
|
||||
|
||||
`SAVEPOINT` liefert eine Kennung. `ROLLBACK kennung` nimmt die danach
|
||||
erfolgten Satzänderungen, Indexanlagen/-löschungen und Tabellenanlagen/-löschungen
|
||||
einschließlich Layout, Indexdefinitionen und gelöschtem Tabellenbestand zurück.
|
||||
Die Transaktion bleibt offen; der gewählte und alle jüngeren Sicherungspunkte
|
||||
werden verbraucht. `ROLLBACK` ohne Kennung verwendet den letzten
|
||||
Sicherungspunkt oder, wenn keiner besteht, den Transaktionsbeginn.
|
||||
Die Rücknahme funktioniert auch nach dem Schließen aller Bindungen.
|
||||
Bei Satz-Rücknahmen bleibt die ID-Vergabe monoton; das Wiederherstellen
|
||||
einer gelöschten Tabelle stellt auch ihren gespeicherten ID-Zähler wieder her.
|
||||
|
||||
Nach einer Rücknahme sind Cursor unpositioniert; nicht mehr vorhandene aktive
|
||||
Indizes werden auf den NULL-Index zurückgesetzt. Bindungen an zurückgenommene
|
||||
Tabellenanlagen oder an ein zurückgenommenes Layout werden gelöst. Ein erneutes
|
||||
`OPEN` bindet den wiederhergestellten Bestand beziehungsweise legt eine fehlende
|
||||
Tabelle neu an. Die von `FREEFILE` gelieferten Nummern berücksichtigen gemeinsam
|
||||
gewöhnliche Dateien und ISAM-Bindungen; doppelte Nummern werden mit Fehler 55
|
||||
abgewiesen.
|
||||
|
||||
Das Rücknahmeprotokoll hält Layouts unabhängig von offenen Bindungen.
|
||||
Für Strukturänderungen speichert es die betroffenen Metadaten und, bei Löschungen,
|
||||
den gelöschten Index beziehungsweise Tabellenbestand, keine vollständige
|
||||
Datenbankkopie je Sicherungspunkt. Diese gespeicherten Daten zählen zum
|
||||
ISAM-Puffer (`SETMEM`); reicht er nicht, scheitert die Strukturänderung vor
|
||||
dem Schreiben mit Fehler 89. Rücknahme und Transaktionsende geben diesen
|
||||
Puffer wieder frei. Auch DELETE prüft seine Satzbytes vor der Änderung gegen
|
||||
die verfügbare Grenze; Fehler 89 lässt Sätze, Indizes und Cursor unverändert.
|
||||
Das Dateiformat bleibt Version 1.
|
||||
|
||||
## Kompilat: `.tbc` (neu, eigenes Format)
|
||||
|
||||
Container für TBVM-Bytecode, Entwurf in [tbvm-design.md](tbvm-design.md).
|
||||
|
||||
460
docs/reviews/2026-09-05-openspec-code-review.md
Normal file
460
docs/reviews/2026-09-05-openspec-code-review.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# OpenSpec–Code-Review vom 05.09.2026
|
||||
|
||||
Geprüfter Code: `19804e0e2de9eb5a8b0ef256b5be76c741560e07` in TerminalBasic. Der Arbeitsbaum war zu Beginn sauber. Gegenstand waren alle 15 Hauptspezifikationen mit 121 Requirements und 263 Szenarien; es gab zu Beginn keine aktiven Changes. Archivierte Entscheidungen und die lokalen Referenzdokumente wurden zur Einordnung herangezogen. Die nach diesem Review angelegten Changes ändern diese Bestandsaufnahme nicht.
|
||||
|
||||
Die Implementierung deckt breite Grundpfade ab, erfüllt den gesamten Vertrag aber nicht. **25 Befunde**: 10 mit hoher, 12 mit mittlerer Priorität, 2 Dokumentationsabweichungen und 1 Vertragsklärung. Die Priorität bewertet die Auswirkung, nicht den Aufwand. Befunde können mehrere Requirements betreffen.
|
||||
|
||||
## Prüfmethode und Grenzen
|
||||
|
||||
- Alle Requirements/Szenarien gelesen und gegen Einstiegspunkte, Implementierung und vorhandene Tests abgeglichen. Die Matrix unten enthält jeden Requirement-Titel einmal.
|
||||
- `openspec validate --all --strict`: 15/15 Hauptspecs gültig; nur Hinweise zu langen Requirement-Texten.
|
||||
- `cargo test --workspace --quiet`: **380 bestanden, 0 fehlgeschlagen, 1 ignoriert**. Der ignorierte Test erzeugt Golden Files und wurde nicht ausgeführt.
|
||||
- `cargo bench -p tb-vm --bench compile`: Einzelmodul 508 Zeilen **0,48 ms** bei Budget 50 ms; 49.760 Zeilen **61 ms** bei Budget 1.000 ms. Gemessen auf diesem Rechner im Release-Profil; der Projektbenchmark übersetzt 20 Module einzeln und ersetzt keinen Mehrmodul-Linktest.
|
||||
- Zusätzliche CLI-Proben und ein temporärer Rust-Treiber gegen die aktuell gebauten Bibliotheken. Die beobachteten Ausgaben stehen bei den Befunden. Es wurde kein Anwendungscode geändert und keine Sollausgabe neu erzeugt.
|
||||
- Kein erneuter interaktiver Fremdprogramm-/Terminalvergleich, kein erneuter vollständiger Lauf über externe 56 Binärformulare, keine Windows-/Linux-Matrix, kein neuer Vergleich sämtlicher Original-Hilfe-Themen. Die lokalen Importtests liefen mit. Solche Nachweise sind nicht durch eine grüne Unit-Testsuite ersetzt.
|
||||
- „Grundpfad belegt“ in der Matrix bedeutet: zuordenbarer Code und vorhandene Tests, keine zusätzliche konkrete Abweichung in dieser Prüfung. Es bedeutet **nicht**, dass jede Eingabekombination, jedes Szenario oder die vollständige historische Kompatibilität bewiesen wurde.
|
||||
|
||||
## Befunde
|
||||
|
||||
### F01 — ISAM-OPEN blockiert in laufender Transaktion (hoch)
|
||||
|
||||
Nach OPEN, BEGINTRANS und INSERT blockiert ein zweites OPEN derselben Datenbank. Die CLI-Probe wurde nach 3 Sekunden beendet. oeffnen startet eine weitere Schreibtransaktion, während die eigene Transaktion bereits den exklusiven Schreibzugriff hält. Erwartet: zusätzliche Tabellenbindung ohne Selbstblockade und ohne vorzeitigen Commit.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/isam.rs:560](../../crates/tb-runtime/src/isam.rs#L560). Vertrag: [openspec/specs/isam-datenbank/spec.md:11](../../openspec/specs/isam-datenbank/spec.md#L11), [openspec/specs/isam-datenbank/spec.md:146](../../openspec/specs/isam-datenbank/spec.md#L146).
|
||||
|
||||
Umsetzung: [isam-transaktionen-und-dateinummern](../../openspec/changes/archive/2026-09-05-isam-transaktionen-und-dateinummern/proposal.md).
|
||||
|
||||
### F02 — SAVEPOINT nimmt Indexänderungen nicht zurück (hoch)
|
||||
|
||||
BEGINTRANS; s%=SAVEPOINT; CREATEINDEX; ROLLBACK s% lässt den neuen Index bestehen: SETINDEX gelingt und GETINDEX$ liefert new. Das Undo-Protokoll kennt nur Satzänderungen. Erwartet: Indexbestand und Sätze entsprechen dem Sicherungspunkt.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/isam.rs:1253](../../crates/tb-runtime/src/isam.rs#L1253). Vertrag: [openspec/specs/isam-datenbank/spec.md:43](../../openspec/specs/isam-datenbank/spec.md#L43), [openspec/specs/isam-datenbank/spec.md:146](../../openspec/specs/isam-datenbank/spec.md#L146).
|
||||
|
||||
Umsetzung: [isam-transaktionen-und-dateinummern](../../openspec/changes/archive/2026-09-05-isam-transaktionen-und-dateinummern/proposal.md).
|
||||
|
||||
### F03 — FREEFILE übersieht ISAM-Bindungen (mittel)
|
||||
|
||||
Mit einer offenen ISAM-Tabelle auf #1 liefert FREEFILE erneut 1. Nur der gewöhnliche Dateispeicher wird durchsucht. Erwartet: kleinste in beiden Dateiverwaltungen freie Nummer.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/builtins.rs:1434](../../crates/tb-runtime/src/builtins.rs#L1434). Vertrag: [openspec/specs/datei-eio/spec.md:12](../../openspec/specs/datei-eio/spec.md#L12).
|
||||
|
||||
Umsetzung: [isam-transaktionen-und-dateinummern](../../openspec/changes/archive/2026-09-05-isam-transaktionen-und-dateinummern/proposal.md).
|
||||
|
||||
### F04 — SIGNAL(2) erreicht seinen Trap nicht (hoch)
|
||||
|
||||
Ein eingespeistes Signal(2) bei definiertem und eingeschaltetem Trap ergibt keinen anstehenden Trap. Wegen des Kurzschlusses n == 1 wird melden für SIGTERM gar nicht aufgerufen. Erwartet: beide spezifizierten Signale folgen derselben Zustellung.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/builtins.rs:121](../../crates/tb-runtime/src/builtins.rs#L121). Vertrag: [openspec/specs/ereignissteuerung/spec.md:186](../../openspec/specs/ereignissteuerung/spec.md#L186).
|
||||
|
||||
Umsetzung: [ereigniszustellung-und-hostgrenzen](../../openspec/changes/ereigniszustellung-und-hostgrenzen/proposal.md).
|
||||
|
||||
### F05 — KEY STOP entfernt die Taste aus dem Eingabestrom (hoch)
|
||||
|
||||
Bei KEY(1) STOP wird F1 zwar für das spätere ON gemerkt, aber aus dem normalen Tastenpuffer entfernt. Probe: Eingabepuffer leer; nach ON ist der Trap anstehend. Die Spec verlangt unter STOP beides: höchstens ein gemerktes Ereignis und weiterhin lesbare Taste.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/traps.rs:215](../../crates/tb-runtime/src/traps.rs#L215). Vertrag: [openspec/specs/ereignissteuerung/spec.md:81](../../openspec/specs/ereignissteuerung/spec.md#L81), [openspec/specs/ereignissteuerung/spec.md:143](../../openspec/specs/ereignissteuerung/spec.md#L143).
|
||||
|
||||
Umsetzung: [ereigniszustellung-und-hostgrenzen](../../openspec/changes/ereigniszustellung-und-hostgrenzen/proposal.md).
|
||||
|
||||
### F06 — Forms-Handler und Eingaben verlieren ihre Reihenfolge (hoch)
|
||||
|
||||
Der Fokuswechsel Text1 nach Text2 ergibt in der VM GL statt LG: GotFocus läuft vor LostFocus. Taste vor Maus ergibt MK statt KM. Tasten und Maus werden getrennt gesammelt; zusätzlich wird am ersten Statement eines bereits ausgewählten Handlers der nächste Handler auf dessen Frame gestapelt. Erwartet: FIFO-Zustellung einschließlich der beobachtbaren Handlerwirkungen.
|
||||
|
||||
Beleg: [crates/tb-vm/src/interp.rs:570](../../crates/tb-vm/src/interp.rs#L570). Vertrag: [openspec/specs/ereignissteuerung/spec.md:32](../../openspec/specs/ereignissteuerung/spec.md#L32), [openspec/specs/forms-steuerelemente/spec.md:30](../../openspec/specs/forms-steuerelemente/spec.md#L30), [openspec/specs/forms-objektmodell/spec.md:106](../../openspec/specs/forms-objektmodell/spec.md#L106).
|
||||
|
||||
Umsetzung: [ereigniszustellung-und-hostgrenzen](../../openspec/changes/ereigniszustellung-und-hostgrenzen/proposal.md).
|
||||
|
||||
### F07 — Offenes Menü verhindert Traps nicht an allen Zustellpunkten (hoch)
|
||||
|
||||
Ein UEVENT-Trap wird während eines geöffneten Menüs an einer gewöhnlichen Anweisungsgrenze ausgeführt: Zähler 11 statt 1, Menü weiterhin offen. zustellen prüft das Menü; der separate Stmt-Pfad umgeht diese Prüfung. Erwartet: gleiche Menüsperre an jedem Zustellpunkt.
|
||||
|
||||
Beleg: [crates/tb-vm/src/interp.rs:1139](../../crates/tb-vm/src/interp.rs#L1139). Vertrag: [openspec/specs/forms-steuerelemente/spec.md:102](../../openspec/specs/forms-steuerelemente/spec.md#L102), [openspec/specs/ereignissteuerung/spec.md:59](../../openspec/specs/ereignissteuerung/spec.md#L59).
|
||||
|
||||
Umsetzung: [ereigniszustellung-und-hostgrenzen](../../openspec/changes/ereigniszustellung-und-hostgrenzen/proposal.md).
|
||||
|
||||
### F08 — Zeit- und Wartepfade verletzen die Hostgrenze (mittel)
|
||||
|
||||
Drei einfache Anweisungen ohne Trap/Form-Timer lesen die Hostzeit sechsmal. forms_zustellen fragt sie bedingungslos zweimal ab. SLEEP bei eingefrorener Hostzeit endet außerdem nach Realzeit (Probe SLEEP 0.001: ca. 6 ms), weil die VM Instant benutzt. Im SLEEP-Pfad wird zudem nur der klassische Trap-Dispatch aufgerufen, nicht forms_zustellen. Erwartet: keine Uhrabfrage ohne Zeitbedarf; Warten und Forms-Zustellung folgen der Hostzeit.
|
||||
|
||||
Beleg: [crates/tb-vm/src/interp.rs:602](../../crates/tb-vm/src/interp.rs#L602). Vertrag: [openspec/specs/vm-ausfuehrung/spec.md:118](../../openspec/specs/vm-ausfuehrung/spec.md#L118), [openspec/specs/ereignissteuerung/spec.md:15](../../openspec/specs/ereignissteuerung/spec.md#L15), [openspec/specs/ereignissteuerung/spec.md:59](../../openspec/specs/ereignissteuerung/spec.md#L59), [openspec/specs/forms-objektmodell/spec.md:106](../../openspec/specs/forms-objektmodell/spec.md#L106).
|
||||
|
||||
Umsetzung: [ereigniszustellung-und-hostgrenzen](../../openspec/changes/ereigniszustellung-und-hostgrenzen/proposal.md).
|
||||
|
||||
### F09 — Control-Namen sind faktisch global statt formularlokal (hoch)
|
||||
|
||||
Zwei Formulare mit je Text1 lassen sich laden, aber Form2!Text1.Text erzeugt Control TEXT1 not found in form FORM2. Die Suche nimmt zuerst den global ersten Namen und prüft erst danach dessen Container. Erwartet: Namensauflösung innerhalb des angegebenen Formulars.
|
||||
|
||||
Beleg: [crates/tb-frontend/src/sema.rs:949](../../crates/tb-frontend/src/sema.rs#L949). Vertrag: [openspec/specs/forms-objektmodell/spec.md:14](../../openspec/specs/forms-objektmodell/spec.md#L14), [openspec/specs/forms-objektmodell/spec.md:36](../../openspec/specs/forms-objektmodell/spec.md#L36), [openspec/specs/sprach-frontend/spec.md:251](../../openspec/specs/sprach-frontend/spec.md#L251).
|
||||
|
||||
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md).
|
||||
|
||||
### F10 — Quellorte und Debuggerziele verlieren die Modulidentität (hoch)
|
||||
|
||||
Ein Overflow in lib.bas:2 wird über app.mak als Overflow in line 5 ohne Moduldatei gemeldet. Include-Expansion und Projektverkettung liefern einen einzigen String; Stmt und Breakpoints speichern nur eine Zahl. Erwartet: ursprüngliche Datei/Zeile und Breakpoints auf (Modul, Zeile).
|
||||
|
||||
Beleg: [crates/tb-cli/src/main.rs:184](../../crates/tb-cli/src/main.rs#L184). Vertrag: [openspec/specs/bytecode-kompilat/spec.md:46](../../openspec/specs/bytecode-kompilat/spec.md#L46), [openspec/specs/sprach-frontend/spec.md:96](../../openspec/specs/sprach-frontend/spec.md#L96), [openspec/specs/vm-ausfuehrung/spec.md:89](../../openspec/specs/vm-ausfuehrung/spec.md#L89), [openspec/specs/vm-fehlerbehandlung/spec.md:11](../../openspec/specs/vm-fehlerbehandlung/spec.md#L11).
|
||||
|
||||
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md).
|
||||
|
||||
### F11 — Formularwerte fehlen im serialisierten Kompilat (hoch)
|
||||
|
||||
Mit FormFile.apply liest die VM den initialen Text hello; nach Serialisierung und Laden desselben Kompilats liest sie einen leeren String. build verwirft die FormFile-Werte; OBJS speichert nur Namen, Klasse, Elternname und Arrayflag. Erwartet: ein aus einer FRM gebautes Kompilat trägt auch deren Anfangszustand und läuft ohne erneutes Anwenden der Quelldatei gleich.
|
||||
|
||||
Beleg: [crates/tb-vm/src/bytecode.rs:484](../../crates/tb-vm/src/bytecode.rs#L484). Vertrag: [openspec/specs/bytecode-kompilat/spec.md:28](../../openspec/specs/bytecode-kompilat/spec.md#L28).
|
||||
|
||||
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md).
|
||||
|
||||
### F12 — ListIndex = -1 wird abgewiesen (mittel)
|
||||
|
||||
Das Setzen auf -1 liefert Fehler 5, auch wenn die Liste Einträge besitzt. Die negative Zahl wird als usize verglichen. Erwartet: -1 hebt die Auswahl auf und hält Text/ListIndex/ListCount konsistent.
|
||||
|
||||
Beleg: [crates/tb-ui/src/forms.rs:409](../../crates/tb-ui/src/forms.rs#L409). Vertrag: [openspec/specs/forms-steuerelemente/spec.md:72](../../openspec/specs/forms-steuerelemente/spec.md#L72).
|
||||
|
||||
Umsetzung: [forms-zustand-und-bedienung](../../openspec/changes/forms-zustand-und-bedienung/proposal.md).
|
||||
|
||||
### F13 — Forms-Timer holen Zeit vor ihrer Aktivierung nach (mittel)
|
||||
|
||||
Wird Interval=100 erst bei Hostzeit 1000 ms gesetzt, entstehen sofort zehn TIMER-Ereignisse. timer_last wird entfernt und anschließend mit 0 initialisiert. Erwartet: erstes Ereignis nach dem neuen Intervall ab Aktivierung, kein Nachholen früherer inaktiver Zeit.
|
||||
|
||||
Beleg: [crates/tb-ui/src/forms.rs:1780](../../crates/tb-ui/src/forms.rs#L1780). Vertrag: [openspec/specs/forms-steuerelemente/spec.md:87](../../openspec/specs/forms-steuerelemente/spec.md#L87).
|
||||
|
||||
Umsetzung: [forms-zustand-und-bedienung](../../openspec/changes/forms-zustand-und-bedienung/proposal.md).
|
||||
|
||||
### F14 — INPUT # verwirft Felder und ersetzt fehlende Daten still (hoch)
|
||||
|
||||
Nach WRITE #1, "a", "b" liest INPUT #1, a$ die ganze Zeile und verwirft b; das zweite INPUT endet mit Fehler 62. Umgekehrt endet INPUT #1, a$, b$ bei nur einem gespeicherten Feld erfolgreich mit b$="". Erwartet: Felder werden fortlaufend gelesen; fehlende Daten erzeugen Fehler 62 statt eines erfundenen leeren Felds.
|
||||
|
||||
Beleg: [crates/tb-vm/src/interp.rs:2529](../../crates/tb-vm/src/interp.rs#L2529). Vertrag: [openspec/specs/datei-eio/spec.md:35](../../openspec/specs/datei-eio/spec.md#L35).
|
||||
|
||||
Umsetzung: [laufzeit-eingabe-und-systemstatus](../../openspec/changes/laufzeit-eingabe-und-systemstatus/proposal.md).
|
||||
|
||||
### F15 — INPUT$ zählt weder durchgehend Zeichen noch die verlangte Länge (mittel)
|
||||
|
||||
INPUT$(1) mit F1 liefert zwei Zeichen (Nullzeichen und Scancode). INPUT$(1,#1) aus einer UTF-8-Datei mit ä liest ein Byte und liefert das Ersatzzeichen <20>. Erwartet: exakt n Unicode-Codepoints; Rest einer Sondertastenfolge bzw. UTF-8-Sequenz bleibt für die nächste Leseoperation erhalten.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/builtins.rs:2116](../../crates/tb-runtime/src/builtins.rs#L2116). Vertrag: [openspec/specs/textbildschirm/spec.md:144](../../openspec/specs/textbildschirm/spec.md#L144).
|
||||
|
||||
Umsetzung: [laufzeit-eingabe-und-systemstatus](../../openspec/changes/laufzeit-eingabe-und-systemstatus/proposal.md).
|
||||
|
||||
### F16 — CV-Funktionen akzeptieren zu lange Byte-Strings (mittel)
|
||||
|
||||
CVI("abc") läuft erfolgreich und liefert 25185. Die Implementierung prüft nur die Mindestbreite; die Spec verlangt Fehler 5 bei nicht passender Länge. Erwartet: exakt 2/4/8 Zeichen entsprechend dem CV-Typ.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/builtins.rs:1640](../../crates/tb-runtime/src/builtins.rs#L1640). Vertrag: [openspec/specs/datei-eio/spec.md:68](../../openspec/specs/datei-eio/spec.md#L68).
|
||||
|
||||
Umsetzung: [laufzeit-eingabe-und-systemstatus](../../openspec/changes/laufzeit-eingabe-und-systemstatus/proposal.md).
|
||||
|
||||
### F17 — Fehlender Elternpfad wird als fehlende Datei gemeldet (mittel)
|
||||
|
||||
OPEN "absentdir/file.txt" FOR INPUT liefert Fehler 53; der Vertrag unterscheidet fehlende Datei (53) von fehlendem Pfad (76). MKDIR behandelt dies bereits gesondert, OPEN benutzt die allgemeine I/O-Abbildung.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/fileio.rs:69](../../crates/tb-runtime/src/fileio.rs#L69). Vertrag: [openspec/specs/datei-eio/spec.md:135](../../openspec/specs/datei-eio/spec.md#L135).
|
||||
|
||||
Umsetzung: [laufzeit-eingabe-und-systemstatus](../../openspec/changes/laufzeit-eingabe-und-systemstatus/proposal.md).
|
||||
|
||||
### F18 — Umschalt-Tab wird am Terminal verworfen (mittel)
|
||||
|
||||
Statisch belegt: Der Terminal-Mapper behandelt KeyCode::Tab, aber nicht KeyCode::BackTab; letzterer fällt in _ => None. Das Forms-Modell unterstützt Rückwärtstab, erhält diesen Terminalfall jedoch nicht. Erwartet: BackTab erreicht die Fokussteuerung als Tab mit Shift. Kein zusätzlicher interaktiver Terminalversuch in diesem Review.
|
||||
|
||||
Beleg: [crates/tb-ui/src/host.rs:179](../../crates/tb-ui/src/host.rs#L179). Vertrag: [openspec/specs/forms-steuerelemente/spec.md:30](../../openspec/specs/forms-steuerelemente/spec.md#L30).
|
||||
|
||||
Umsetzung: [ereigniszustellung-und-hostgrenzen](../../openspec/changes/ereigniszustellung-und-hostgrenzen/proposal.md).
|
||||
|
||||
### F19 — Der Zonenzustand ist für BASIC nicht abfragbar (mittel)
|
||||
|
||||
Zeitzone::Unbekannt ist auf Rust-Ebene vorhanden und der CLI-Runner schreibt eine Warnung auf stderr. Eine BASIC-Signatur zum Abfragen dieses Zustands fehlt; bei später scheiternder Zonenermittlung benutzt versatz still 0. Die Spec verlangt eine für das Programm erkennbare Rückfallebene.
|
||||
|
||||
Beleg: [crates/tb-runtime/src/datetime.rs:147](../../crates/tb-runtime/src/datetime.rs#L147). Vertrag: [openspec/specs/konsolen-basisbibliothek/spec.md:260](../../openspec/specs/konsolen-basisbibliothek/spec.md#L260).
|
||||
|
||||
Umsetzung: [laufzeit-eingabe-und-systemstatus](../../openspec/changes/laufzeit-eingabe-und-systemstatus/proposal.md).
|
||||
|
||||
### F20 — VM hängt transitiv von Terminalbibliotheken ab (mittel)
|
||||
|
||||
tb-vm hängt unmittelbar von tb-ui ab; dort sind ratatui, crossterm und signal-hook unbedingte Dependencies. Der Zellenpuffer selbst ist terminalfrei, die Ausführungsschicht insgesamt erfüllt die explizite Abhängigkeitsgrenze nicht mehr. Erwartet: separat baubare VM samt Forms ohne Terminalbackend.
|
||||
|
||||
Beleg: [crates/tb-vm/Cargo.toml:15](../../crates/tb-vm/Cargo.toml#L15). Vertrag: [openspec/specs/textbildschirm/spec.md:92](../../openspec/specs/textbildschirm/spec.md#L92).
|
||||
|
||||
Umsetzung: [ereigniszustellung-und-hostgrenzen](../../openspec/changes/ereigniszustellung-und-hostgrenzen/proposal.md).
|
||||
|
||||
### F21 — Inventarstatus beweist kein vorhandenes Laufzeitverhalten (mittel)
|
||||
|
||||
Alle Eigenschaften/Ereignisse aus der Klassentabelle werden im Inventartest pauschal als implementiert erwartet; Parseranweisungen werden ausdrücklich schwächer geprüft. Konkretes Gegenbeispiel: FORM_KEYPRESS und FORM_KEYUP stehen auf implementiert, aber handle_key erzeugt im Formular-Fallback nur KEYDOWN; die beiden Ereignisse werden nur für aktive Controls erzeugt. Erwartet: Verhaltensnachweise statt Selbstauskunft derselben Tabelle.
|
||||
|
||||
Beleg: [crates/tb-frontend/tests/inventar.rs:206](../../crates/tb-frontend/tests/inventar.rs#L206). Vertrag: [openspec/specs/sprachinventar/spec.md:12](../../openspec/specs/sprachinventar/spec.md#L12), [openspec/specs/sprachinventar/spec.md:56](../../openspec/specs/sprachinventar/spec.md#L56), [openspec/specs/forms-objektmodell/spec.md:106](../../openspec/specs/forms-objektmodell/spec.md#L106).
|
||||
|
||||
Umsetzung: [spezifikationsabgleich-und-regressionsnachweise](../../openspec/changes/spezifikationsabgleich-und-regressionsnachweise/proposal.md).
|
||||
|
||||
### F22 — Der Korpus deckt die verlangten Kombinationen nicht ab (mittel)
|
||||
|
||||
traps.bas prüft STOP nur für TIMER, nicht für KEY und UEVENT; formular.frm hat keinen Alt-Access-Key auf ein Control (Alt+D öffnet das Menü) und keinen Rückwärtstab. Einige Nachweise existieren als Unit-Test, aber die ausdrücklich verlangte Korpusabdeckung fehlt. Fremdprogramme wurden in diesem Review nicht erneut interaktiv bedient; vbdos-befunde.md ist ein historischer Nachweis.
|
||||
|
||||
Beleg: [tests/compat/traps.bas:1](../../tests/compat/traps.bas#L1). Vertrag: [openspec/specs/kompat-testkorpus/spec.md:114](../../openspec/specs/kompat-testkorpus/spec.md#L114), [openspec/specs/kompat-testkorpus/spec.md:132](../../openspec/specs/kompat-testkorpus/spec.md#L132), [openspec/specs/kompat-testkorpus/spec.md:149](../../openspec/specs/kompat-testkorpus/spec.md#L149).
|
||||
|
||||
Umsetzung: [spezifikationsabgleich-und-regressionsnachweise](../../openspec/changes/spezifikationsabgleich-und-regressionsnachweise/proposal.md).
|
||||
|
||||
### F23 — Die pauschale Grafik-Abweisung ist überholt (Dokumentation)
|
||||
|
||||
sprach-frontend nennt Grafik-Anweisungen pauschal als Non-Features. LINE, PAINT, VIEW und SCREEN 0–13 werden inzwischen ausgeführt und sind in der Sprachreferenz ausdrücklich erlaubt; tbc check der Probe liefert 0. Die Hauptspec muss die bereits dokumentierte Untermenge nennen.
|
||||
|
||||
Beleg: [docs/sprachreferenz.md:526](../../docs/sprachreferenz.md#L526). Vertrag: [openspec/specs/sprach-frontend/spec.md:83](../../openspec/specs/sprach-frontend/spec.md#L83).
|
||||
|
||||
Umsetzung: [spezifikationsabgleich-und-regressionsnachweise](../../openspec/changes/spezifikationsabgleich-und-regressionsnachweise/proposal.md).
|
||||
|
||||
### F24 — Container- und Opcode-Beschreibung sind nicht aktuell (Dokumentation)
|
||||
|
||||
Die Hauptspec verlangt separate CODE/LINES-Abschnitte und vollständige Prozedursignaturen. Implementiert sind MODN/CONS/TYPS/GLOB/PROC/DATA/JMPT/OBJS mit Inline-Code und Inline-Zeilen. docs/tbvm-design.md spricht noch von Version 1 und künftigem FORMS, der Code von Version 3 und OBJS. Die Opcode-Tabelle lässt unter anderem die Forms-/Trap-Instruktionen aus. Die Strukturabweichung allein ist nicht der funktionale Fehler F11.
|
||||
|
||||
Beleg: [docs/tbvm-design.md:69](../../docs/tbvm-design.md#L69). Vertrag: [openspec/specs/bytecode-kompilat/spec.md:12](../../openspec/specs/bytecode-kompilat/spec.md#L12), [openspec/specs/bytecode-kompilat/spec.md:28](../../openspec/specs/bytecode-kompilat/spec.md#L28).
|
||||
|
||||
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md).
|
||||
|
||||
### F25 — FRM-Schreibvertrag enthält zwei unvereinbare Universalregeln (Klärung)
|
||||
|
||||
write_text gibt unveränderte gelesene Dateien bytegleich zurück, einschließlich expliziter Defaultwerte. Bei kanonischer Neuausgabe werden Defaults ausgelassen. Die Spec verlangt beides ohne Unterscheidung. Für nichtkanonische Eingaben mit expliziten Defaults ist das logisch nicht gleichzeitig erfüllbar. Das bestehende Verhalten sollte präzisiert werden: Erhaltungsmodus gegenüber kanonischem Schreiben.
|
||||
|
||||
Beleg: [crates/tb-ui/src/frm.rs:348](../../crates/tb-ui/src/frm.rs#L348). Vertrag: [openspec/specs/forms-dateiformat/spec.md:31](../../openspec/specs/forms-dateiformat/spec.md#L31).
|
||||
|
||||
Umsetzung: [spezifikationsabgleich-und-regressionsnachweise](../../openspec/changes/spezifikationsabgleich-und-regressionsnachweise/proposal.md).
|
||||
|
||||
## Gebündelte Changes und Reihenfolge
|
||||
|
||||
| Reihenfolge | Change | Befunde | Abhängigkeit |
|
||||
|---|---|---|---|
|
||||
| 1 | [isam-transaktionen-und-dateinummern](../../openspec/changes/archive/2026-09-05-isam-transaktionen-und-dateinummern/proposal.md) | F01, F02, F03 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
|
||||
| 2 | [ereigniszustellung-und-hostgrenzen](../../openspec/changes/ereigniszustellung-und-hostgrenzen/proposal.md) | F04, F05, F06, F07, F08, F18, F20 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
|
||||
| 3 | [forms-zustand-und-bedienung](../../openspec/changes/forms-zustand-und-bedienung/proposal.md) | F12, F13 | Nach Ereigniszustellung |
|
||||
| 4 | [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md) | F09, F10, F11, F24 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
|
||||
| 5 | [laufzeit-eingabe-und-systemstatus](../../openspec/changes/laufzeit-eingabe-und-systemstatus/proposal.md) | F14, F15, F16, F17, F19 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
|
||||
| 6 | [spezifikationsabgleich-und-regressionsnachweise](../../openspec/changes/spezifikationsabgleich-und-regressionsnachweise/proposal.md) | F21, F22, F23, F25 | Abschließende Gesamtabnahme nach den fünf Korrektur-Changes |
|
||||
|
||||
## Vollständige Requirement-Matrix
|
||||
|
||||
### bytecode-kompilat
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-vm/src/codegen.rs:1](../../crates/tb-vm/src/codegen.rs#L1), [crates/tb-vm/src/bytecode.rs:1](../../crates/tb-vm/src/bytecode.rs#L1), [crates/tb-vm/benches/compile.rs:1](../../crates/tb-vm/benches/compile.rs#L1).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Monomorpher Opcode-Satz](../../openspec/specs/bytecode-kompilat/spec.md#L12) | F24 |
|
||||
| [`.tbc`-Containerformat](../../openspec/specs/bytecode-kompilat/spec.md#L28) | F11, F24 |
|
||||
| [Zeilentabelle für Fehlerortung](../../openspec/specs/bytecode-kompilat/spec.md#L46) | F10 |
|
||||
| [Instant-Compile-Budget](../../openspec/specs/bytecode-kompilat/spec.md#L55) | Grundpfad belegt |
|
||||
|
||||
### datei-eio
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-runtime/src/fileio.rs:1](../../crates/tb-runtime/src/fileio.rs#L1), [crates/tb-runtime/src/builtins.rs:1260](../../crates/tb-runtime/src/builtins.rs#L1260), [crates/tb-vm/src/interp.rs:2343](../../crates/tb-vm/src/interp.rs#L2343).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Dateinummern und Öffnen in drei Zugriffsarten](../../openspec/specs/datei-eio/spec.md#L12) | F03 |
|
||||
| [Sequenzielle Datenübertragung](../../openspec/specs/datei-eio/spec.md#L35) | F14 |
|
||||
| [Wahlfreier Zugriff mit Record-Puffern](../../openspec/specs/datei-eio/spec.md#L51) | Grundpfad belegt |
|
||||
| [Record-Konvertierungsfunktionen](../../openspec/specs/datei-eio/spec.md#L68) | F16 |
|
||||
| [Festtextfelder als UTF-32 mit fester Recordlänge](../../openspec/specs/datei-eio/spec.md#L88) | Grundpfad belegt |
|
||||
| [Binärzugriff auf Byteebene](../../openspec/specs/datei-eio/spec.md#L100) | Grundpfad belegt |
|
||||
| [Positions- und Statusfunktionen](../../openspec/specs/datei-eio/spec.md#L110) | Grundpfad belegt |
|
||||
| [Satzsperren](../../openspec/specs/datei-eio/spec.md#L125) | Grundpfad belegt |
|
||||
| [Dateisystem-Anweisungen mit plattformübergreifender Pfadsemantik](../../openspec/specs/datei-eio/spec.md#L135) | F17 |
|
||||
| [LPRINT als Druckausgabe](../../openspec/specs/datei-eio/spec.md#L162) | Grundpfad belegt |
|
||||
|
||||
### ereignissteuerung
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-runtime/src/traps.rs:1](../../crates/tb-runtime/src/traps.rs#L1), [crates/tb-runtime/src/builtins.rs:105](../../crates/tb-runtime/src/builtins.rs#L105), [crates/tb-vm/src/interp.rs:592](../../crates/tb-vm/src/interp.rs#L592).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Ereignisquellen mit Zeit vom Host](../../openspec/specs/ereignissteuerung/spec.md#L15) | F08 |
|
||||
| [Mausereignisse in Zellenkoordinaten](../../openspec/specs/ereignissteuerung/spec.md#L32) | F06 |
|
||||
| [Abschließende Liste der Zustellpunkte](../../openspec/specs/ereignissteuerung/spec.md#L59) | F07, F08 |
|
||||
| [Maskierung je Quelle mit drei Zuständen](../../openspec/specs/ereignissteuerung/spec.md#L81) | F05 |
|
||||
| [EVENT klammert Abschnitte ohne Ereignisprüfung](../../openspec/specs/ereignissteuerung/spec.md#L102) | Grundpfad belegt |
|
||||
| [Zustellung als GOSUB ohne Wiedereintritt](../../openspec/specs/ereignissteuerung/spec.md#L121) | Grundpfad belegt |
|
||||
| [Getrappte Tasten verlassen den Eingabestrom](../../openspec/specs/ereignissteuerung/spec.md#L143) | F05 |
|
||||
| [Benutzerdefiniertes Ereignis](../../openspec/specs/ereignissteuerung/spec.md#L157) | Grundpfad belegt |
|
||||
| [Reihenfolge bei mehreren fälligen Ereignissen](../../openspec/specs/ereignissteuerung/spec.md#L166) | Grundpfad belegt |
|
||||
| [Fehler in einem Handler](../../openspec/specs/ereignissteuerung/spec.md#L176) | Grundpfad belegt |
|
||||
| [Signal-Trap auf Betriebssystemsignalen](../../openspec/specs/ereignissteuerung/spec.md#L186) | F04 |
|
||||
|
||||
### forms-dateiformat
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-ui/src/frm.rs:1](../../crates/tb-ui/src/frm.rs#L1), [crates/tb-ui/src/frm_pcode.rs:1](../../crates/tb-ui/src/frm_pcode.rs#L1), [crates/tb-cli/tests/convert_frm.rs:1](../../crates/tb-cli/tests/convert_frm.rs#L1).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Aufbau des Textformats](../../openspec/specs/forms-dateiformat/spec.md#L13) | Grundpfad belegt |
|
||||
| [Schreiben ist die Umkehrung des Lesens](../../openspec/specs/forms-dateiformat/spec.md#L31) | F25 |
|
||||
| [Fehlerhafte Dateien werden benannt](../../openspec/specs/forms-dateiformat/spec.md#L45) | Grundpfad belegt |
|
||||
| [Konvertierung binärer Formulardateien](../../openspec/specs/forms-dateiformat/spec.md#L59) | Grundpfad belegt |
|
||||
|
||||
### forms-objektmodell
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-frontend/src/forms.rs:1](../../crates/tb-frontend/src/forms.rs#L1), [crates/tb-ui/src/forms.rs:151](../../crates/tb-ui/src/forms.rs#L151), [crates/tb-vm/tests/vm.rs:1165](../../crates/tb-vm/tests/vm.rs#L1165).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Formulare und Steuerelemente als benannte Objekte](../../openspec/specs/forms-objektmodell/spec.md#L14) | F09 |
|
||||
| [Container-Zugriff über den Bang-Operator](../../openspec/specs/forms-objektmodell/spec.md#L36) | F09 |
|
||||
| [Lade- und Entladezyklus](../../openspec/specs/forms-objektmodell/spec.md#L50) | Grundpfad belegt |
|
||||
| [Modales und nicht-modales Anzeigen](../../openspec/specs/forms-objektmodell/spec.md#L70) | Grundpfad belegt |
|
||||
| [Steuerelement-Arrays](../../openspec/specs/forms-objektmodell/spec.md#L86) | Grundpfad belegt |
|
||||
| [Ereignisdispatch an Name_Ereignis](../../openspec/specs/forms-objektmodell/spec.md#L106) | F06, F08, F21 |
|
||||
| [SCREEN-Objekt](../../openspec/specs/forms-objektmodell/spec.md#L125) | Grundpfad belegt |
|
||||
|
||||
### forms-steuerelemente
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-ui/src/forms.rs:848](../../crates/tb-ui/src/forms.rs#L848), [crates/tb-ui/src/forms.rs:1780](../../crates/tb-ui/src/forms.rs#L1780), [tests/compat/formular.frm:1](../../tests/compat/formular.frm#L1).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Darstellung im Zellenpuffer](../../openspec/specs/forms-steuerelemente/spec.md#L12) | Grundpfad belegt |
|
||||
| [Fokus, Tabreihenfolge und Access-Keys](../../openspec/specs/forms-steuerelemente/spec.md#L30) | F06, F18 |
|
||||
| [Maussteuerung mit Trefferprüfung](../../openspec/specs/forms-steuerelemente/spec.md#L51) | Grundpfad belegt |
|
||||
| [Steuerelemente mit Listeninhalt](../../openspec/specs/forms-steuerelemente/spec.md#L72) | F12 |
|
||||
| [Timer-Steuerelement](../../openspec/specs/forms-steuerelemente/spec.md#L87) | F13 |
|
||||
| [Menüsystem](../../openspec/specs/forms-steuerelemente/spec.md#L102) | F07 |
|
||||
| [Vordefinierte Dialoge](../../openspec/specs/forms-steuerelemente/spec.md#L119) | Grundpfad belegt |
|
||||
|
||||
### isam-datenbank
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-runtime/src/isam.rs:524](../../crates/tb-runtime/src/isam.rs#L524), [crates/tb-runtime/src/isam/tests.rs:1](../../crates/tb-runtime/src/isam/tests.rs#L1).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Datenbank- und Tabellenbindung an eine Dateinummer](../../openspec/specs/isam-datenbank/spec.md#L11) | F01 |
|
||||
| [Indexverwaltung](../../openspec/specs/isam-datenbank/spec.md#L43) | F02 |
|
||||
| [Cursorbewegung in der Ordnung des aktiven Index](../../openspec/specs/isam-datenbank/spec.md#L73) | Grundpfad belegt |
|
||||
| [Schlüsselsuche](../../openspec/specs/isam-datenbank/spec.md#L89) | Grundpfad belegt |
|
||||
| [Satzoperationen auf dem aktuellen Satz](../../openspec/specs/isam-datenbank/spec.md#L112) | Grundpfad belegt |
|
||||
| [Transaktionen mit Rücknahme](../../openspec/specs/isam-datenbank/spec.md#L146) | F01, F02 |
|
||||
| [Vergleichsreihenfolge für Indizes](../../openspec/specs/isam-datenbank/spec.md#L170) | Grundpfad belegt |
|
||||
| [Pufferverwaltung mit benanntem Fehler](../../openspec/specs/isam-datenbank/spec.md#L182) | Grundpfad belegt |
|
||||
| [Eigenes Datenbankdateiformat](../../openspec/specs/isam-datenbank/spec.md#L196) | Grundpfad belegt |
|
||||
|
||||
### kompat-testkorpus
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-cli/tests/compat.rs:1](../../crates/tb-cli/tests/compat.rs#L1), [crates/tb-frontend/tests/corpus.rs:1](../../crates/tb-frontend/tests/corpus.rs#L1), [tests/compat/README.md:1](../../tests/compat/README.md#L1).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Korpusdateien mit byte-genauer Sollausgabe](../../openspec/specs/kompat-testkorpus/spec.md#L11) | Grundpfad belegt |
|
||||
| [Frontend-Meilenstein über den Korpus](../../openspec/specs/kompat-testkorpus/spec.md#L35) | Grundpfad belegt |
|
||||
| [Laufzeitvergleich über den Korpus](../../openspec/specs/kompat-testkorpus/spec.md#L45) | Grundpfad belegt |
|
||||
| [Korpusabdeckung der Phase-2-Semantik](../../openspec/specs/kompat-testkorpus/spec.md#L67) | Grundpfad belegt |
|
||||
| [Korpusabdeckung der Phase-3-Semantik](../../openspec/specs/kompat-testkorpus/spec.md#L78) | Grundpfad belegt |
|
||||
| [Nachweis der Größenunabhängigkeit](../../openspec/specs/kompat-testkorpus/spec.md#L96) | Grundpfad belegt |
|
||||
| [Korpusabdeckung der Ereignis-Traps](../../openspec/specs/kompat-testkorpus/spec.md#L114) | F22 |
|
||||
| [Korpusabdeckung der Formularprogramme](../../openspec/specs/kompat-testkorpus/spec.md#L132) | F22 |
|
||||
| [Kompatibilitätsnachweis an Fremdprogrammen](../../openspec/specs/kompat-testkorpus/spec.md#L149) | F22 |
|
||||
|
||||
### konsolen-basisbibliothek
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-runtime/src/builtins.rs:1](../../crates/tb-runtime/src/builtins.rs#L1), [crates/tb-runtime/src/using.rs:1](../../crates/tb-runtime/src/using.rs#L1), [crates/tb-runtime/src/finanz.rs:1](../../crates/tb-runtime/src/finanz.rs#L1), [crates/tb-runtime/src/datetime.rs:1](../../crates/tb-runtime/src/datetime.rs#L1).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Builtin-Aufrufschnittstelle](../../openspec/specs/konsolen-basisbibliothek/spec.md#L11) | Grundpfad belegt |
|
||||
| [Host-Abstraktion für Konsolen-E/A](../../openspec/specs/konsolen-basisbibliothek/spec.md#L21) | Grundpfad belegt |
|
||||
| [PRINT-Formatierung](../../openspec/specs/konsolen-basisbibliothek/spec.md#L47) | Grundpfad belegt |
|
||||
| [Vollständige Stringbibliothek](../../openspec/specs/konsolen-basisbibliothek/spec.md#L65) | Grundpfad belegt |
|
||||
| [PRINT USING und FORMAT$](../../openspec/specs/konsolen-basisbibliothek/spec.md#L92) | Grundpfad belegt |
|
||||
| [Datums- und Zeitbibliothek](../../openspec/specs/konsolen-basisbibliothek/spec.md#L119) | Grundpfad belegt |
|
||||
| [Kompatibler Zufallszahlengenerator](../../openspec/specs/konsolen-basisbibliothek/spec.md#L140) | Grundpfad belegt |
|
||||
| [Mathematikbibliothek mit Überlaufverhalten](../../openspec/specs/konsolen-basisbibliothek/spec.md#L158) | Grundpfad belegt |
|
||||
| [Finanzmathematische Funktionen](../../openspec/specs/konsolen-basisbibliothek/spec.md#L179) | Grundpfad belegt |
|
||||
| [System- und Umgebungsanweisungen](../../openspec/specs/konsolen-basisbibliothek/spec.md#L208) | Grundpfad belegt |
|
||||
| [Zeitfunktionen liefern Ortszeit](../../openspec/specs/konsolen-basisbibliothek/spec.md#L234) | Grundpfad belegt |
|
||||
| [Zonenermittlung mit Rückfallebene](../../openspec/specs/konsolen-basisbibliothek/spec.md#L260) | F19 |
|
||||
| [Programmversatz wirkt auf die Ortszeit](../../openspec/specs/konsolen-basisbibliothek/spec.md#L276) | Grundpfad belegt |
|
||||
|
||||
### laufzeitfehler
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-runtime/src/errors.rs:1](../../crates/tb-runtime/src/errors.rs#L1).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Klassischer Fehlerkatalog](../../openspec/specs/laufzeitfehler/spec.md#L10) | Grundpfad belegt |
|
||||
| [Unbelegte Codes](../../openspec/specs/laufzeitfehler/spec.md#L26) | Grundpfad belegt |
|
||||
|
||||
### sprach-frontend
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-frontend/src/lexer.rs:324](../../crates/tb-frontend/src/lexer.rs#L324), [crates/tb-frontend/src/parser.rs:194](../../crates/tb-frontend/src/parser.rs#L194), [crates/tb-frontend/src/sema.rs:399](../../crates/tb-frontend/src/sema.rs#L399).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Lexikalische Analyse nach Sprachreferenz](../../openspec/specs/sprach-frontend/spec.md#L10) | Grundpfad belegt |
|
||||
| [Literal-Typisierung](../../openspec/specs/sprach-frontend/spec.md#L26) | Grundpfad belegt |
|
||||
| [Vollständige Anweisungs-Grammatik](../../openspec/specs/sprach-frontend/spec.md#L38) | Grundpfad belegt |
|
||||
| [Semantische Prüfung](../../openspec/specs/sprach-frontend/spec.md#L63) | Grundpfad belegt |
|
||||
| [Compile-Zeit-Abweisung deklarierter Non-Features](../../openspec/specs/sprach-frontend/spec.md#L83) | F23 |
|
||||
| [Diagnostik mit Vorbild-Meldungen](../../openspec/specs/sprach-frontend/spec.md#L96) | F10 |
|
||||
| [Typisiertes HIR als Sema-Ausgabe](../../openspec/specs/sprach-frontend/spec.md#L106) | Grundpfad belegt |
|
||||
| [Jedes dokumentierte Element besitzt eine Signatur](../../openspec/specs/sprach-frontend/spec.md#L124) | Grundpfad belegt |
|
||||
| [Modulweites ON ERROR ist aus Prozeduren ansprechbar](../../openspec/specs/sprach-frontend/spec.md#L144) | Grundpfad belegt |
|
||||
| [DATA-Elemente behalten ihren Rohtext](../../openspec/specs/sprach-frontend/spec.md#L161) | Grundpfad belegt |
|
||||
| [ISAM-Anweisungen und -Funktionen in Grammatik und Signaturprüfung](../../openspec/specs/sprach-frontend/spec.md#L177) | Grundpfad belegt |
|
||||
| [Ereignis-Traps in Grammatik und Signaturprüfung](../../openspec/specs/sprach-frontend/spec.md#L203) | Grundpfad belegt |
|
||||
| [Trap-Ziele sind Sprungziele des Moduls](../../openspec/specs/sprach-frontend/spec.md#L242) | Grundpfad belegt |
|
||||
| [Objektzugriffe in Grammatik und Semantik](../../openspec/specs/sprach-frontend/spec.md#L251) | F09 |
|
||||
| [Bang-Operator neben dem SINGLE-Suffix](../../openspec/specs/sprach-frontend/spec.md#L278) | Grundpfad belegt |
|
||||
| [Ereignisprozeduren mit festen Signaturen](../../openspec/specs/sprach-frontend/spec.md#L287) | Grundpfad belegt |
|
||||
|
||||
### sprachinventar
|
||||
|
||||
Implementierungs-/Testpfade: [docs/inventar.md:25](../../docs/inventar.md#L25), [crates/tb-frontend/tests/inventar.rs:1](../../crates/tb-frontend/tests/inventar.rs#L1).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Vollständiges Inventar der Original-Hilfe](../../openspec/specs/sprachinventar/spec.md#L12) | F21; vollständiger Originalquellenabgleich nicht wiederholt |
|
||||
| [Festes Statusvokabular](../../openspec/specs/sprachinventar/spec.md#L40) | Grundpfad belegt |
|
||||
| [Automatischer Abgleich Inventar gegen Implementierung](../../openspec/specs/sprachinventar/spec.md#L56) | F21 |
|
||||
| [Keine stillen Lücken bei dokumentierten Elementen](../../openspec/specs/sprachinventar/spec.md#L79) | Grundpfad belegt |
|
||||
|
||||
### textbildschirm
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-runtime/src/screen.rs:1](../../crates/tb-runtime/src/screen.rs#L1), [crates/tb-ui/src/screen.rs:1](../../crates/tb-ui/src/screen.rs#L1), [crates/tb-vm/tests/vm.rs:590](../../crates/tb-vm/tests/vm.rs#L590).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Dynamische Terminalgröße mit Mindestmaß](../../openspec/specs/textbildschirm/spec.md#L11) | Grundpfad belegt |
|
||||
| [PRINT-Semantik mit Umbruch und Scrollen](../../openspec/specs/textbildschirm/spec.md#L30) | Grundpfad belegt |
|
||||
| [1-basierte Cursor-API mit Bereichsprüfung](../../openspec/specs/textbildschirm/spec.md#L41) | Grundpfad belegt |
|
||||
| [Klassische Farbpalette und Blink-Simulation](../../openspec/specs/textbildschirm/spec.md#L57) | Grundpfad belegt |
|
||||
| [Unicode-Zellenmodell](../../openspec/specs/textbildschirm/spec.md#L68) | Grundpfad belegt |
|
||||
| [Zellenpuffer ohne Terminalabhängigkeit](../../openspec/specs/textbildschirm/spec.md#L92) | F20 |
|
||||
| [Bildschirmanweisungen des Dialekts](../../openspec/specs/textbildschirm/spec.md#L105) | Grundpfad belegt |
|
||||
| [Bildschirm-Abfragefunktionen](../../openspec/specs/textbildschirm/spec.md#L128) | Grundpfad belegt |
|
||||
| [Tastatureingabe ohne Zeilenmodell](../../openspec/specs/textbildschirm/spec.md#L144) | F15 |
|
||||
| [Keine feste Bildschirmgröße im Verhalten](../../openspec/specs/textbildschirm/spec.md#L159) | Grundpfad belegt |
|
||||
| [Größenänderung zur Laufzeit](../../openspec/specs/textbildschirm/spec.md#L179) | Grundpfad belegt |
|
||||
|
||||
### vm-ausfuehrung
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-vm/src/interp.rs:708](../../crates/tb-vm/src/interp.rs#L708), [crates/tb-vm/tests/vm.rs:117](../../crates/tb-vm/tests/vm.rs#L117).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Zahlenkonvertierungs-Matrix](../../openspec/specs/vm-ausfuehrung/spec.md#L11) | Grundpfad belegt |
|
||||
| [Kontrollfluss](../../openspec/specs/vm-ausfuehrung/spec.md#L34) | Grundpfad belegt |
|
||||
| [GOSUB/RETURN neben Prozeduraufrufen](../../openspec/specs/vm-ausfuehrung/spec.md#L49) | Grundpfad belegt |
|
||||
| [Prozeduraufrufe mit BYREF-Semantik](../../openspec/specs/vm-ausfuehrung/spec.md#L59) | Grundpfad belegt |
|
||||
| [DATA/READ/RESTORE](../../openspec/specs/vm-ausfuehrung/spec.md#L77) | Grundpfad belegt |
|
||||
| [Unterbrechbare step()-Ausführung](../../openspec/specs/vm-ausfuehrung/spec.md#L89) | F10 |
|
||||
| [Programmende im Runner](../../openspec/specs/vm-ausfuehrung/spec.md#L106) | Grundpfad belegt |
|
||||
| [Ereigniszustellung an Anweisungsgrenzen](../../openspec/specs/vm-ausfuehrung/spec.md#L118) | F08 |
|
||||
| [Jede Schleife trägt eine Anweisungsgrenze im Kreis](../../openspec/specs/vm-ausfuehrung/spec.md#L135) | Grundpfad belegt |
|
||||
| [DOEVENTS gibt die Kontrolle ab](../../openspec/specs/vm-ausfuehrung/spec.md#L150) | Grundpfad belegt |
|
||||
|
||||
### vm-fehlerbehandlung
|
||||
|
||||
Implementierungs-/Testpfade: [crates/tb-vm/src/interp.rs:870](../../crates/tb-vm/src/interp.rs#L870), [crates/tb-vm/tests/vm.rs:348](../../crates/tb-vm/tests/vm.rs#L348).
|
||||
|
||||
| Requirement | Ergebnis |
|
||||
|---|---|
|
||||
| [Handler-Scoping nach Vorbild](../../openspec/specs/vm-fehlerbehandlung/spec.md#L11) | F10 |
|
||||
| [ERR und ERL](../../openspec/specs/vm-fehlerbehandlung/spec.md#L33) | Grundpfad belegt |
|
||||
| [RESUME-Semantik](../../openspec/specs/vm-fehlerbehandlung/spec.md#L48) | Grundpfad belegt |
|
||||
| [Fehler im Handler ist fatal](../../openspec/specs/vm-fehlerbehandlung/spec.md#L64) | Grundpfad belegt |
|
||||
|
||||
## Bewusst erlaubte Abweichungen
|
||||
|
||||
Unicode/UTF-32, LPRINT nach LPT1.TXT, dokumentiertes LOCK/UNLOCK-Ersatzverhalten, FILEATTR ohne OS-Handle, CHDRIVE ohne Laufwerksbuchstaben und die dokumentierten Hardware-Non-Features wurden nicht pauschal als fehlende Implementierung gezählt. Die noch nicht implementierte IDE ist kein eigenständiger Sollumfang der 15 vorliegenden Hauptspecs.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-05
|
||||
@@ -0,0 +1,33 @@
|
||||
## Context
|
||||
|
||||
oeffnen ruft direkt Database::begin_write auf, während schreiben/lesen laufende Transaktionen wiederverwenden. Undo enthält nur Eingefuegt/Vorher für Sätze. FREEFILE fragt ausschließlich Dateien ab. Siehe proposal.md und die Delta-Specs.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** Die bestehende redb-Schicht und die vorhandenen Bindungen behalten; Transaktions- und Nummernlogik an ihren gemeinsamen Einstiegspunkten berichtigen.
|
||||
|
||||
**Non-Goals:** Kein neues Speichersystem, keine verteilte Atomarität über mehrere Datenbankdateien, keine Performance-Umschreibung der Indizes.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 — Gemeinsame Transaktion verwenden
|
||||
|
||||
OPEN und Layoutprüfung benutzen bei aktiver Transaktion deren vorhandenen Schreibkontext; außerhalb bleiben sie einzelne Transaktionen. Das vermeidet die Selbstblockade. OPEN und DELETETABLE verwenden kanonische absolute Pfade als Schlüssel für Datenbanken, Transaktionen und Bindungen; bei neuen Dateien wird zunächst der vorhandene Elternpfad kanonisiert. Nach erfolgreicher Dateianlage wird der vollständige Pfad erneut kanonisiert und als endgültige Identität an alle Aufrufer zurückgegeben, damit neu auflösbare Symlinks keinen zweiten Cache-Schlüssel erzeugen. CHDIR ändert damit weder bestehende Bindungen noch die Bedeutung der Cache-Schlüssel. Eine zweite Verbindung oder ein impliziter Commit würde Isolation und ROLLBACK brechen. CLOSE ohne Nummer und RESET lösen wie CLOSE #n nur Bindungen; die Freigabe der Datenbankdateien bleibt vom Transaktionsende getrennt.
|
||||
|
||||
### D2 — Sicherungspunkte umfassen Struktur und Inhalt
|
||||
|
||||
Das vorhandene Undo-Protokoll um die tatsächlich unterstützten Index-/Tabellenstrukturänderungen und deren Metadaten erweitern; in umgekehrter Operationsreihenfolge zurücknehmen. Layoutinformationen für Rücknahme auch nach CLOSE behalten. ROLLBACK ALL bleibt der Abbruch der redb-Transaktion. Keine Kopie der gesamten Datenbank je Savepoint. Struktur-Undo speichert alte Metadaten sowie beim Löschen die betroffenen rohen Satz-/Indexeinträge; diese Nutzdaten zählen zum SETMEM-Puffer und werden vor der Mutation gegen die Grenze geprüft. Satz-Undo trägt sein Layout selbst. Nach Rücknahme werden ungültige aktive Indizes gelöscht und Bindungen an verschwundene Tabellen oder zurückgenommene Layouts gelöst; gültige Bindungen behalten einen unpositionierten Cursor. Erfolgreiches DELETETABLE löst alle Bindungen dieser Tabelle; die Rücknahme stellt den Bestand, aber keine geschlossenen Bindungen wieder her. DELETE prüft die alten Satzbytes vor der Mutation gegen den verfügbaren Puffer, auch außerhalb einer expliziten Transaktion.
|
||||
|
||||
### D3 — Dateinummern gemeinsam prüfen
|
||||
|
||||
FREEFILE fragt beide bestehenden Belegungsprüfungen ab. Kein dritter Nummernspeicher, der von den Bindungen abweichen kann. Beide OPEN-Einstiege prüfen auch die andere Verwaltung und weisen Doppelbelegung mit Fehler 55 zurück. Ist keine als INTEGER darstellbare freie Nummer übrig, meldet FREEFILE Fehler 67.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Versehentlicher Commit durch OPEN → nach Öffnen/Schließen weiterer Bindungen ROLLBACK ALL und erneutes Öffnen zur Persistenzprüfung.
|
||||
- Fehlendes Undo für Strukturänderungen → CREATEINDEX, DELETEINDEX, DELETETABLE und Tabellenneuanlage vor/nach mehreren Savepoints prüfen.
|
||||
- Hängender Regressionstest → Der CLI-Unterprozess hat fünf Sekunden Frist. Jeder Runtime-Datenbanktest und jede Korpusausführung werden zusätzlich über einen unabhängigen Watchdog nach 30 Sekunden mit Prozessstatus 124 beendet. Der ganze Testprozess darf beim Timeout scheitern, damit blockierende Bibliotheksaufrufe keine unendlichen Testläufe hinterlassen. Ein absichtlich blockierter Kind-Test beweist den Abbruch; der Elternprozess hat eine separate Kill-Frist.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Eigenständig. Gemeinsame Änderungen in builtins.rs mit laufzeit-eingabe-und-systemstatus sequenziell integrieren. Bestehende Spec-/Codeverträge erst nach erfolgreicher Umsetzung synchronisieren und archivieren. Die Tasks werden nach ihrer jeweiligen Abnahme abgeschlossen; Spec-Synchronisation und Archivierung folgen getrennt. Änderungen als zusammenhängenden Commit je Change integrieren; bei Fehlschlag auf den vorherigen Code zurückgehen und neue Datenformatversionen nicht mit alten Lesern öffnen.
|
||||
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
ISAM kann beim Öffnen einer weiteren Tabelle innerhalb einer Transaktion blockieren, Sicherungspunkte lassen Indexänderungen stehen und FREEFILE liefert bereits belegte Nummern. F01–F03 des Reviews zeigen diese Fälle mit reproduzierbaren CLI-Programmen. Siehe [Review vom 05.09.2026](../../../../docs/reviews/2026-09-05-openspec-code-review.md).
|
||||
|
||||
## What Changes
|
||||
|
||||
- Zusätzliche ISAM-Bindungen innerhalb einer Transaktion ohne Selbstblockade und ohne impliziten Commit erlauben.
|
||||
- Sicherungspunkte auf Satz- und Indexänderungen einschließlich Metadaten anwenden.
|
||||
- FREEFILE über gewöhnliche und ISAM-Dateinummern gemeinsam bestimmen.
|
||||
- Datenbankpfade über Verzeichniswechsel und Aliase korrekt zuordnen, Bindungen bei DELETETABLE lösen und die Puffergrenze vor DELETE prüfen.
|
||||
- Blockierende Regressionen durch Prozessfristen für Runtime-Datenbanktests und Korpusausführungen absichern.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Keine neuen Capability-Pfade.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `isam-datenbank`: Transaktionales Öffnen, Pfadidentität, vollständige Rücknahme über Sicherungspunkte, Bindungen nach Tabellenlöschung und Puffergrenzen präzisieren.
|
||||
- `datei-eio`: Freie Dateinummern ausdrücklich über alle Zugriffsarten bestimmen.
|
||||
|
||||
## Impact
|
||||
|
||||
crates/tb-runtime/src/isam.rs, builtins.rs und fileio.rs; ISAM-Unit-Tests, CLI-Korpus und docs/dateiformate.md. Keine neue Bibliothek und kein neues Datenbankformat.
|
||||
|
||||
Eigenständig. Gemeinsame Änderungen in builtins.rs mit laufzeit-eingabe-und-systemstatus sequenziell integrieren.
|
||||
@@ -0,0 +1,30 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Dateinummern und Öffnen in drei Zugriffsarten
|
||||
`OPEN` SHALL in beiden Syntaxen des Vorbilds (Klausel- und Kurzform) die
|
||||
Zugriffsarten sequenziell (`INPUT`, `OUTPUT`, `APPEND`), wahlfrei
|
||||
(`RANDOM`) und binär (`BINARY`) unterstützen und die Datei an eine
|
||||
Dateinummer binden. `FREEFILE` SHALL die kleinste freie Dateinummer
|
||||
liefern. `CLOSE` ohne Argument SHALL alle offenen Dateien schließen,
|
||||
`RESET` ebenso. Eine bereits belegte Dateinummer MUST Laufzeitfehler 55
|
||||
„File already open" auslösen, eine nicht geöffnete Nummer Fehler 52
|
||||
„Bad file name or number", eine fehlende Datei bei `INPUT` Fehler 53
|
||||
„File not found".
|
||||
|
||||
FREEFILE SHALL die gemeinsame Belegung durch sequenzielle, wahlfreie, binäre und ISAM-Bindungen berücksichtigen.
|
||||
|
||||
#### Scenario: Sequenzielles Schreiben und Lesen
|
||||
- **WHEN** eine Datei mit `OPEN "d.txt" FOR OUTPUT AS #1` beschrieben, geschlossen und mit `FOR INPUT` wieder geöffnet wird
|
||||
- **THEN** liefert das Lesen genau die geschriebenen Zeilen
|
||||
|
||||
#### Scenario: Doppeltes Öffnen derselben Nummer
|
||||
- **WHEN** `OPEN` eine bereits offene Dateinummer verwendet
|
||||
- **THEN** tritt Laufzeitfehler 55 auf
|
||||
|
||||
#### Scenario: Freie Dateinummer
|
||||
- **WHEN** `#1` offen ist und `FREEFILE` aufgerufen wird
|
||||
- **THEN** liefert `FREEFILE` eine Nummer, die noch nicht belegt ist
|
||||
|
||||
#### Scenario: ISAM belegt eine Dateinummer
|
||||
- **WHEN** eine ISAM-Tabelle auf #1 und sonst keine Datei offen ist
|
||||
- **THEN** liefert FREEFILE den Wert 2
|
||||
@@ -0,0 +1,111 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Datenbank- und Tabellenbindung an eine Dateinummer
|
||||
`OPEN datenbank$ FOR ISAM typname tabellenname AS #n` SHALL eine Tabelle
|
||||
einer Datenbankdatei an eine Dateinummer binden. Das Satzlayout SHALL
|
||||
sich aus dem benannten benutzerdefinierten Typ ergeben; dessen Felder
|
||||
sind die Spalten der Tabelle. Existiert die Tabelle noch nicht, SHALL sie
|
||||
mit diesem Layout angelegt werden; existiert sie mit abweichendem Layout,
|
||||
MUST Laufzeitfehler 88 „ISAM - Database inconsistent" auftreten. Ein
|
||||
ungültiger Tabellen- oder Datenbankname MUST Fehler 81 „ISAM - Invalid
|
||||
name" auslösen. `CLOSE` SHALL die Bindung lösen; ausstehende Änderungen
|
||||
sind dabei bereits festgeschrieben, weil jede Operation außerhalb einer
|
||||
Transaktion für sich eine Transaktion ist. Eine laufende Transaktion MUST
|
||||
`CLOSE` unberührt lassen — über ihr Ende entscheiden allein `COMMITTRANS`
|
||||
und `ROLLBACK ALL`. `DELETETABLE` SHALL eine Tabelle samt ihren
|
||||
Indizes entfernen; eine nicht vorhandene Tabelle MUST Fehler 82 „ISAM -
|
||||
Table not found" auslösen.
|
||||
|
||||
Eine weitere Bindung derselben Datenbank innerhalb einer laufenden Transaktion SHALL deren bisherigen Zustand sehen und MUST ohne implizites Festschreiben oder Selbstblockade entstehen.
|
||||
|
||||
OPEN und DELETETABLE SHALL relative Datenbankpfade im aktuellen Arbeitsverzeichnis auflösen und kanonisch auflösbare Aliase derselben Datei gemeinsam verwalten. Bereits offene Bindungen MUST nach CHDIR weiterhin auf ihre ursprünglich geöffnete Datenbank zeigen. DELETETABLE SHALL bei Erfolg alle Bindungen der entfernten Tabelle lösen; weitere Zugriffe über diese Nummern MUST Fehler 52 melden. Eine Rücknahme SHALL die gelöschte Tabelle wiederherstellen, ohne geschlossene Bindungen erneut zu öffnen.
|
||||
|
||||
#### Scenario: Gleicher relativer Name nach Verzeichniswechsel
|
||||
- **WHEN** nach OPEN in Verzeichnis A alle Bindungen geschlossen werden und derselbe relative Name in Verzeichnis B geöffnet wird
|
||||
- **THEN** betreffen Lesen und Schreiben die Datei in B; die Datei in A bleibt unverändert
|
||||
|
||||
#### Scenario: Pfadalias in einer Transaktion
|
||||
- **WHEN** dieselbe vorhandene Datenbank nach BEGINTRANS und INSERT über einen relativen oder absoluten Pfadalias erneut geöffnet wird
|
||||
- **THEN** sieht die neue Bindung die bisherigen Änderungen ohne zweiten Writer; ROLLBACK ALL nimmt die Änderungen zurück
|
||||
|
||||
#### Scenario: Neuanlage über einen noch nicht auflösbaren Symlink
|
||||
- **WHEN** OPEN eine Datenbank über einen Symlink mit bislang fehlender Zieldatei anlegt und die Datenbank danach innerhalb einer Transaktion erneut über Alias oder Zielpfad geöffnet wird
|
||||
- **THEN** teilen alle Bindungen denselben Transaktionszustand; Sicherungspunkt-Rücknahme und ROLLBACK ALL funktionieren über beide Pfade
|
||||
|
||||
#### Scenario: Bindungen nach Tabellenlöschung und Rücknahme
|
||||
- **WHEN** eine mehrfach gebundene Tabelle nach SAVEPOINT mit DELETETABLE gelöscht und anschließend zurückgenommen wird
|
||||
- **THEN** sind die alten Bindungen geschlossen; ein erneutes OPEN sieht wieder die ursprünglichen Sätze und Indizes
|
||||
|
||||
#### Scenario: Tabelle wird beim ersten Öffnen angelegt
|
||||
- **WHEN** eine Datenbank ohne die Tabelle `Kunden` mit `OPEN "db" FOR ISAM KundeTyp "Kunden" AS #1` geöffnet wird
|
||||
- **THEN** entsteht die Tabelle mit den Feldern des Typs und die Dateinummer ist nutzbar
|
||||
|
||||
#### Scenario: Abweichendes Satzlayout
|
||||
- **WHEN** eine bestehende Tabelle mit einem Typ geöffnet wird, dessen Felder nicht zu ihren Spalten passen
|
||||
- **THEN** tritt Laufzeitfehler 88 auf
|
||||
|
||||
#### Scenario: CLOSE lässt eine laufende Transaktion stehen
|
||||
- **WHEN** innerhalb einer Transaktion eine andere Dateinummer mit `CLOSE` geschlossen wird
|
||||
- **THEN** läuft die Transaktion weiter und ein folgendes `ROLLBACK ALL` nimmt ihre Änderungen zurück
|
||||
|
||||
#### Scenario: Entfernte Tabelle
|
||||
- **WHEN** `DELETETABLE` für eine nicht vorhandene Tabelle aufgerufen wird
|
||||
- **THEN** tritt Laufzeitfehler 82 auf
|
||||
|
||||
#### Scenario: Weitere Tabelle während einer Transaktion
|
||||
- **WHEN** nach BEGINTRANS und INSERT eine zweite Tabelle derselben Datenbank geöffnet und danach ROLLBACK ALL ausgeführt wird
|
||||
- **THEN** kehrt OPEN zurück; die eingefügten Daten sind nach Wiederöffnung nicht festgeschrieben
|
||||
|
||||
### Requirement: Transaktionen mit Rücknahme
|
||||
`BEGINTRANS` SHALL eine Transaktion eröffnen, `COMMITTRANS` alle
|
||||
Änderungen seit ihrem Beginn festschreiben und `ROLLBACK` sie
|
||||
zurücknehmen. `SAVEPOINT` SHALL als **Funktion** einen Sicherungspunkt
|
||||
setzen und dessen Kennung liefern; `ROLLBACK` mit einer solchen Kennung
|
||||
SHALL auf diesen Punkt zurücksetzen, ohne die Transaktion aufzugeben.
|
||||
`ROLLBACK ALL` SHALL alle Änderungen seit `BEGINTRANS` zurücknehmen und
|
||||
die Transaktion beenden. Nach jeder Rücknahme MUST der Satzbestand
|
||||
einschließlich aller Indizes dem Zustand zum Bezugspunkt entsprechen und
|
||||
der Cursor als unpositioniert gelten. Eine beim Programmende nicht
|
||||
festgeschriebene Transaktion MUST wirkungslos bleiben.
|
||||
|
||||
Die Rücknahme auf einen Sicherungspunkt SHALL auch seitdem angelegte oder gelöschte Indizes und Tabellenstrukturen einschließlich ihrer Metadaten zurücknehmen. CLOSE einer Bindung MUST die spätere Rücknahme ihrer Änderungen weiterhin erlauben.
|
||||
|
||||
#### Scenario: Rücknahme stellt den Bestand wieder her
|
||||
- **WHEN** nach `BEGINTRANS` Sätze eingefügt und gelöscht werden und dann `ROLLBACK` folgt
|
||||
- **THEN** entspricht der Bestand beim erneuten Durchlauf dem Zustand vor `BEGINTRANS`
|
||||
|
||||
#### Scenario: Rücknahme auf einen Sicherungspunkt
|
||||
- **WHEN** innerhalb einer Transaktion `s = SAVEPOINT` ausgewertet, danach geändert und mit `ROLLBACK s` zurückgesetzt wird
|
||||
- **THEN** bleiben die Änderungen vor dem Sicherungspunkt erhalten und die danach sind zurückgenommen
|
||||
|
||||
#### Scenario: ROLLBACK ALL beendet die Transaktion
|
||||
- **WHEN** nach mehreren Sicherungspunkten `ROLLBACK ALL` ausgeführt wird
|
||||
- **THEN** entspricht der Bestand dem Zustand vor `BEGINTRANS` und die Transaktion ist beendet
|
||||
|
||||
#### Scenario: Indexanlage nach Sicherungspunkt
|
||||
- **WHEN** nach SAVEPOINT ein Index angelegt und auf den Sicherungspunkt zurückgesetzt wird
|
||||
- **THEN** ist der neue Index nicht vorhanden; SETINDEX mit seinem Namen löst Fehler 83 aus
|
||||
|
||||
#### Scenario: Geschlossene Tabelle rücknehmen
|
||||
- **WHEN** eine geänderte Tabellenbindung vor ROLLBACK auf einen Sicherungspunkt geschlossen wird
|
||||
- **THEN** gelingt die Rücknahme und der bei Wiederöffnung sichtbare Satz-/Indexbestand entspricht dem Sicherungspunkt
|
||||
|
||||
### Requirement: Pufferverwaltung mit benanntem Fehler
|
||||
`SETMEM` SHALL als **Funktion** die für ISAM verfügbare Puffergröße um
|
||||
den übergebenen Betrag ändern und die danach verfügbare Größe liefern.
|
||||
Reicht
|
||||
der verfügbare Puffer für eine Operation nicht aus, MUST Laufzeitfehler 89
|
||||
„ISAM - Insufficient ISAM buffers" auftreten statt eines generischen
|
||||
Fehlers oder eines stillen Abbruchs. Weicht die Bedeutung von `SETMEM`
|
||||
mangels DOS-Speichermodell vom Vorbild ab, MUST die Abweichung in
|
||||
docs/sprachreferenz.md dokumentiert sein.
|
||||
|
||||
DELETE SHALL den benötigten Satz-/Undo-Puffer vor der Änderung prüfen. Bei Fehler 89 MUST Satzbestand, Indexbestand und Cursor unverändert bleiben.
|
||||
|
||||
#### Scenario: Pufferfehler wird benannt
|
||||
- **WHEN** eine Operation den verfügbaren ISAM-Puffer überschreitet
|
||||
- **THEN** tritt Laufzeitfehler 89 auf
|
||||
|
||||
#### Scenario: DELETE bei ausgeschöpftem Puffer
|
||||
- **WHEN** DELETE auf einem gültigen aktuellen Satz bei unzureichendem ISAM-Puffer ausgeführt wird
|
||||
- **THEN** meldet es Fehler 89 ohne den Satz zu löschen, den Cursor zu verschieben oder Undo hinzuzufügen; eine spätere Rücknahme bleibt möglich
|
||||
@@ -0,0 +1,46 @@
|
||||
## 1. Transaktionspfade
|
||||
|
||||
- [x] 1.1 OPEN/Layoutanlage auf den bestehenden Transaktionskontext führen; verifizieren mit OPEN → BEGINTRANS → INSERT → zweites OPEN derselben Datenbank unter harter Prozessfrist und anschließendem ROLLBACK ALL.
|
||||
- [x] 1.2 Strukturänderungen am bestehenden Undo-Protokoll rücknehmbar machen und benötigte Layouts über CLOSE erhalten; verifizieren mit verschachtelten Savepoints, CREATEINDEX/DELETEINDEX/DELETETABLE, geschlossenen Bindungen und unverändertem Bestand nach Wiederöffnung.
|
||||
|
||||
## 2. Dateinummern und Abnahme
|
||||
|
||||
- [x] 2.1 FREEFILE gegen beide Dateiverwaltungen prüfen; verifizieren mit gemischten OPEN-Modi, ISAM #1, Lücken nach CLOSE und wiederverwendeten Nummern.
|
||||
- [x] 2.2 ISAM-Korpus um die regressiven Ablaufkombinationen erweitern; verifizieren mit cargo test -p tb-runtime und cargo test -p tb-cli --test compat.
|
||||
- [x] 2.3 Transaktions-/Sicherungspunktgrenzen dokumentieren und Delta-Specs prüfen; verifizieren mit git diff --check und openspec validate isam-transaktionen-und-dateinummern --strict.
|
||||
|
||||
## Abnahme am 05.09.2026
|
||||
|
||||
- `cargo test -p tb-runtime --quiet`: 130 bestanden.
|
||||
- `cargo test -p tb-cli --test compat --quiet`: 15 bestanden; der Golden-Generator bleibt ignoriert. Drei neue Korpusprogramme mit unabhängig festgelegten Sollausgaben.
|
||||
- `cargo test --workspace --quiet`: 385 bestanden, 0 fehlgeschlagen, 1 bewusst ignoriert.
|
||||
- Der CLI-Test für OPEN innerhalb einer Transaktion läuft mit einer harten Fünf-Sekunden-Frist und prüft Sichtbarkeit sowie ROLLBACK ALL nach CLOSE.
|
||||
- Formatprüfung der vier geänderten Rust-Dateien, `git diff --check` und `openspec validate isam-transaktionen-und-dateinummern --strict` bestanden.
|
||||
|
||||
## 3. Befunde der Verifikation
|
||||
|
||||
- [x] 3.1 V01: Datenbankpfade eindeutig auflösen; Verzeichniswechsel, relative/absolute Aliase und DELETETABLE über Aliase prüfen.
|
||||
- [x] 3.2 V02: Alle gefährdeten Runtime-/Korpusläufe mit einer wirksamen Prozessfrist schützen und einen absichtlich blockierten Testprozess nachweislich beenden.
|
||||
- [x] 3.3 V03: Bindungen gelöschter Tabellen lösen; Zugriffe danach, Wiederanlage und Rücknahme der Löschung prüfen.
|
||||
- [x] 3.4 V04: DELETE vor Mutation gegen die Puffergrenze prüfen; Bestand, Indizes und Cursor bei Fehler 89 unverändert erhalten.
|
||||
- [x] 3.5 Alle Befunde erneut verifizieren, Dokumentation und Delta-Specs abgleichen sowie Workspace-Tests und OpenSpec-Validierung abschließen.
|
||||
|
||||
## Nachprüfung der Befunde am 05.09.2026
|
||||
|
||||
- Runtime: 134 Tests bestanden; CLI-Kompatibilität: 16 bestanden, Golden-Generator ignoriert.
|
||||
- Workspace: 390 bestanden, 0 fehlgeschlagen, 1 bewusst ignoriert.
|
||||
- V01: Neuer Korpusfall `isamidentitaet.bas`, Unit-Test für Pfad-/Symlinkaliase und separate CLI-Prozesse bestätigen getrennte Daten in a/db und b/db.
|
||||
- V02: Jeder Runtime-Datenbanktest und Korpuslauf hat eine harte Prozessfrist; der Selbsttest beendet einen absichtlich blockierten Kindprozess mit Status 124.
|
||||
- V03: Zwei alte Bindungen sind nach DELETETABLE unbenutzbar; Rücknahme und Wiederöffnung stellen Sätze und Eindeutigkeit wieder her.
|
||||
- V04: DELETE meldet bei vollem Puffer Fehler 89; Satzbestand, Indizes, Cursor und Undo bleiben unverändert. Genau passende Puffergröße und anschließende Rücknahme sind geprüft.
|
||||
|
||||
## 4. Letzter Aliasbefund
|
||||
|
||||
- [x] 4.1 V05: Nach Neuanlage die tatsächliche kanonische Dateiidentität an Cache und Bindungen weitergeben; Symlink-Neuanlage mit erneutem OPEN über Alias und Ziel, DELETETABLE, Sicherungspunkt und ROLLBACK ALL prüfen.
|
||||
|
||||
## Schlussabnahme V05
|
||||
|
||||
- `symlink_neuanlage_behaelt_die_dateiidentitaet`: bestanden für relative und absolute Symlinkziele, inklusive Alias-/Zielbindung, DELETETABLE, Sicherungspunkt, ROLLBACK ALL und erneutem Öffnen.
|
||||
- Ursprüngliche CLI-Reproduktion: Alias und Ziel liefern innerhalb der Transaktion je einen Satz; nach ROLLBACK ALL ist der Bestand leer.
|
||||
- `cargo test --workspace --quiet`: 391 bestanden, 0 fehlgeschlagen, 1 bewusst ignoriert.
|
||||
- Format-/Diffprüfung und strikte OpenSpec-Validierung bestanden; vier Anforderungen in zwei Hauptspecs synchronisiert und alle 15 Hauptspecs validiert.
|
||||
@@ -0,0 +1,162 @@
|
||||
# Verifikation: isam-transaktionen-und-dateinummern
|
||||
|
||||
Nachprüfung am 05.09.2026, aktueller uncommitteter Arbeitsbaum auf `19804e0`.
|
||||
Geprüft: Proposal, Design, Tasks, Delta-Specs, betroffene Hauptspec-Verträge,
|
||||
Implementierung, Regressionstests und ursprüngliche Fehlerreproduktionen.
|
||||
|
||||
**Schlussabnahme: V01–V05 behoben und nachgeprüft. Keine offenen Befunde.
|
||||
Die Hauptspecs sind synchronisiert; alle 11 Tasks sind abgeschlossen.**
|
||||
|
||||
| Dimension | Ergebnis |
|
||||
|---|---|
|
||||
| Vollständigkeit | 11/11 Tasks abgeschlossen |
|
||||
| Korrektheit | 4 Anforderungen, 20 Szenarien zugeordnet; Neuanlage über Symlink zusätzlich abgesichert |
|
||||
| Kohärenz | D1–D3 eingehalten; Dokumentation und Delta-Specs um die Korrekturen ergänzt |
|
||||
| Befunde | 0 offen; V01–V05 behoben |
|
||||
|
||||
## V05 — Neuanlage über einen Symlink: behoben
|
||||
|
||||
**Vorher:** Bei `alias.db → target.db` mit noch fehlender Zieldatei gelang
|
||||
das erste OPEN; das zweite OPEN derselben Datei meldete Fehler 88. Die erste
|
||||
Anlage speicherte den Alias als Cache-Schlüssel, die nächste Auflösung ergab
|
||||
den Zielpfad.
|
||||
|
||||
**Korrektur:** `db_oeffnen` bestimmt nach erfolgreicher Dateianlage die
|
||||
endgültige kanonische Identität, speichert den Datenbankhandle darunter und
|
||||
gibt diesen Pfad an OPEN beziehungsweise DELETETABLE zurück. Bindungen,
|
||||
Transaktionen und Undo verwenden damit von Anfang an den Zielpfad.
|
||||
|
||||
**Nachweise:**
|
||||
|
||||
- `symlink_neuanlage_behaelt_die_dateiidentitaet` in
|
||||
[isam/tests.rs](../../../../crates/tb-runtime/src/isam/tests.rs): relative und
|
||||
absolute Symlinkziele, erneutes OPEN über Alias und Ziel, gemeinsamer
|
||||
Transaktionsbestand, DELETETABLE über Alias, Sicherungspunkt-Rücknahme,
|
||||
ROLLBACK ALL und persistierter Ausgangsbestand nach erneutem Öffnen.
|
||||
- Ursprüngliche CLI-Reproduktion in einem frischen temporären Verzeichnis,
|
||||
mit fünf Sekunden Frist: beide zusätzlichen Bindungen sehen einen Satz;
|
||||
nach ROLLBACK ALL liefert LOF den Wert 0. Prozessstatus 0, keine Fehler 88/52.
|
||||
- Vollständige Workspace-Suite: **391 bestanden, 0 fehlgeschlagen,
|
||||
1 bewusst ignoriert**. Davon 135 Runtime-Tests und 16 aktive CLI-Korpus-/Harness-Tests.
|
||||
|
||||
## V01 — Datenbankidentität: behoben
|
||||
|
||||
**Vorher:** Nach CLOSE und CHDIR konnte OPEN mit demselben relativen Namen
|
||||
in die alte Datei schreiben. OPEN über `./db` erkannte eine bereits laufende
|
||||
Transaktion derselben Datei nicht und meldete Fehler 88.
|
||||
|
||||
**Korrektur:** [datenbank_pfad](../../../../crates/tb-runtime/src/isam.rs#L1560)
|
||||
löst vorhandene Dateien kanonisch auf. Bei neuen Dateien wird ihr vorhandener
|
||||
Elternpfad kanonisiert. OPEN und DELETETABLE verwenden diesen Schlüssel
|
||||
durchgängig für Datenbanken, Transaktionen, Bindungen und Rücknahme.
|
||||
|
||||
**Nachweise:**
|
||||
|
||||
- [isamidentitaet.bas](../../../../tests/compat/isamidentitaet.bas): Nach Wechsel
|
||||
von a nach b ist die zweite Datenbank leer; der Alias `./db` sieht innerhalb
|
||||
einer Transaktion den gleichen Bestand. DELETETABLE über `../a/db` und
|
||||
Rücknahme funktionieren ebenfalls.
|
||||
- [pfadaliase_teilen_transaktion_und_ruecknahme](../../../../crates/tb-runtime/src/isam/tests.rs#L696):
|
||||
absolute Pfade mit `..` und auf Unix auch ein Symlink teilen denselben Writer.
|
||||
- Separate CLI-Prozesse lesen anschließend genau einen Satz mit Nr=1 aus
|
||||
`a/db` und einen Satz mit Nr=2 aus `b/db`. Beide Dateien existieren.
|
||||
|
||||
## V02 — Prozessfristen: behoben
|
||||
|
||||
**Vorher:** Nur der besondere CLI-Unterprozess war geschützt; die direkten
|
||||
ISAM-Aufrufe im Korpustest und in Runtime-Tests konnten weiterhin hängen.
|
||||
|
||||
**Korrektur:** [Prozessfrist](../../../../tests/support/prozessfrist.rs) überwacht
|
||||
jeden Runtime-Datenbanktest über dessen TempDb sowie jeden Aufruf von
|
||||
[run_corpus_file](../../../../crates/tb-cli/tests/compat.rs#L359), einschließlich
|
||||
beider Korpusdurchläufe und des bewusst ignorierten Golden-Generators.
|
||||
Nach 30 Sekunden beendet ein unabhängiger Watchdog den gesamten betroffenen
|
||||
Testprozess mit Status 124. Beim regulären Abschluss wird die Frist aufgehoben.
|
||||
Der spezielle CLI-Unterprozess behält seine eigene Fünf-Sekunden-Frist.
|
||||
|
||||
**Nachweis:** `frist_beendet_blockierten_testprozess` startet einen Kind-Test,
|
||||
der absichtlich unbegrenzt blockiert. Dessen Watchdog beendet ihn mit Status 124
|
||||
und benannter Fehlermeldung; der Elternprozess prüft beides und besitzt zusätzlich
|
||||
eine unabhängige Fünf-Sekunden-Kill-Frist. Dieser Test besteht sowohl im Runtime-
|
||||
als auch im CLI-Testbinary. Keine Änderung am Produktcode zur Simulation nötig.
|
||||
|
||||
## V03 — Bindungen nach DELETETABLE: behoben
|
||||
|
||||
**Vorher:** Ein alter Handle konnte nach DELETETABLE unbemerkt eine interne
|
||||
Satztabelle ohne Layoutmetadaten wieder anlegen.
|
||||
|
||||
**Korrektur:** [tabelle_loeschen](../../../../crates/tb-runtime/src/isam.rs#L988)
|
||||
löst nach erfolgreicher Löschung alle Bindungen genau dieser Tabelle und
|
||||
Datenbank. Eine Rücknahme stellt den Bestand wieder her, öffnet aber keine
|
||||
zuvor geschlossenen Bindungen. Ein neues OPEN bindet den restaurierten Bestand.
|
||||
|
||||
**Nachweise:**
|
||||
|
||||
- [deletetable_loest_alle_betroffenen_bindungen](../../../../crates/tb-runtime/src/isam/tests.rs#L725):
|
||||
Beide alten Handles melden Fehler 52, eine andere Tabelle bleibt gebunden.
|
||||
Rücknahme und Wiederöffnung stellen Sätze und eindeutigen Index wieder her.
|
||||
Erneute Löschung und Neuanlage ergeben eine leere Tabelle ohne alten Index.
|
||||
- Die ursprüngliche CLI-Reproduktion meldet jetzt Fehler 52 beim INSERT über
|
||||
die alte Nummer und LOF=0 nach Neuanlage. RETRIEVE auf der leeren Tabelle
|
||||
meldet erwartungsgemäß Fehler 85; es gibt keinen Geistersatz.
|
||||
|
||||
## V04 — DELETE bei ausgeschöpftem Puffer: behoben
|
||||
|
||||
**Vorher:** DELETE speicherte Undo und entfernte den Satz trotz Puffergröße 0.
|
||||
|
||||
**Korrektur:** [satz_loeschen](../../../../crates/tb-runtime/src/isam.rs#L1123)
|
||||
prüft die alten Satzbytes gegen den noch verfügbaren Puffer, bevor Satz oder
|
||||
Indizes verändert beziehungsweise Undo hinzugefügt werden.
|
||||
|
||||
**Nachweise:**
|
||||
|
||||
- [delete_pufferfehler_erhaelt_saetze_indizes_und_cursor](../../../../crates/tb-runtime/src/isam/tests.rs#L760):
|
||||
Fehler 89 außerhalb und innerhalb einer Transaktion lässt Bestand, aktiven
|
||||
Index, Cursor und Undo unverändert. Bei exakt passender Größe gelingt ein
|
||||
DELETE; der nächste scheitert ohne Änderung. Rücknahme restauriert beide
|
||||
Sätze und gibt den Puffer wieder frei.
|
||||
- Ursprüngliche CLI-Reproduktion: `free=0`, `ERR=89`, `LOF=1`, `restored=1`.
|
||||
|
||||
## Szenarionachweise
|
||||
|
||||
| Delta-Szenario | Nachweis |
|
||||
|---|---|
|
||||
| Sequenzielles Schreiben und Lesen | `tests/compat/dateien.bas` |
|
||||
| Doppeltes Öffnen derselben Nummer | `isamdateinummern.bas`: Fehler 55 in beiden Richtungen |
|
||||
| Freie Dateinummer | `isamdateinummern.bas`: gemischte Modi und wiederbelegte Lücken |
|
||||
| ISAM belegt eine Dateinummer | `isamdateinummern.bas`: FREEFILE=2 |
|
||||
| Gleicher relativer Name nach Verzeichniswechsel | `isamidentitaet.bas` und separate Persistenzprüfung |
|
||||
| Neuanlage über einen noch nicht auflösbaren Symlink | `symlink_neuanlage_behaelt_die_dateiidentitaet` und CLI-Reproduktion |
|
||||
| Pfadalias in einer Transaktion | `isamidentitaet.bas`, Runtime-Pfadaliastest |
|
||||
| Bindungen nach Tabellenlöschung und Rücknahme | Runtime-DELETETABLE-Bindungstest |
|
||||
| Tabelle wird beim ersten Öffnen angelegt | `isamtabellen.bas` |
|
||||
| Abweichendes Satzlayout | `isamtabellen.bas`: Fehler 88 |
|
||||
| CLOSE lässt eine laufende Transaktion stehen | `close_beendet_keine_transaktion`, `isamopen.bas` |
|
||||
| Entfernte Tabelle | `isamtabellen.bas`, `isamopen.bas`: Fehler 82 |
|
||||
| Weitere Tabelle während einer Transaktion | `isamopen.bas` und CLI-Test mit Prozessfrist |
|
||||
| Rücknahme stellt den Bestand wieder her | CLI-Probe der Erstprüfung: INSERT/DELETE und bare ROLLBACK ergeben LOF=1 und LOC=0; Rücknahmepfad unverändert |
|
||||
| Rücknahme auf einen Sicherungspunkt | `isamtrans.bas`, `isamstruktur.bas`, Runtime-Strukturtests |
|
||||
| ROLLBACK ALL beendet die Transaktion | `isamtrans.bas`; CLI-Probe der Erstprüfung mit zwei Savepoints und anschließendem Fehler 5 |
|
||||
| Indexanlage nach Sicherungspunkt | `isamstruktur.bas`: Fehler 83; Runtime-Strukturtests |
|
||||
| Geschlossene Tabelle rücknehmen | `isamstruktur.bas`, Runtime-Tests für CLOSE und Struktur-Rücknahme |
|
||||
| Pufferfehler wird benannt | `isampuffer.bas`, `struktur_undo_prueft_puffer_vor_dem_loeschen` |
|
||||
| DELETE bei ausgeschöpftem Puffer | Neuer Runtime-DELETE-Puffertest und ursprüngliche CLI-Reproduktion |
|
||||
|
||||
## Prüfungen einschließlich erneuter Verifikation
|
||||
|
||||
- `cargo test -p tb-runtime --quiet`: 135 bestanden.
|
||||
- `cargo test -p tb-cli --test compat --quiet`: 16 bestanden, Golden-Generator ignoriert.
|
||||
- Erneut `cargo test --workspace --quiet`: **391 bestanden, 0 fehlgeschlagen,
|
||||
1 bewusst ignoriert**; einschließlich beider Watchdog-Selbsttests.
|
||||
- Formatprüfung aller geänderten Rust-Dateien und des neuen Testhelfers bestanden.
|
||||
- `git diff --check` und Whitespace-Prüfung der neuen Dateien bestanden.
|
||||
- `openspec validate isam-transaktionen-und-dateinummern --strict`: bestanden.
|
||||
- Ursprüngliche Reproduktionen und positive Zusatzproben erneut ausgeführt:
|
||||
Index-Neuanlage mit geändertem Aufbau und Rücknahme, RESET/COMMIT,
|
||||
Rücknahme über zwei Datenbankdateien, Programmende ohne COMMIT sowie
|
||||
Persistenz über neue CLI-Prozesse. Alle externen Proben mit fünf Sekunden Frist.
|
||||
|
||||
Keine GUI-, Windows-/Linux-Abnahme, Speichererschöpfung oder simulierten
|
||||
redb-I/O-Ausfälle ausgeführt. Keine neue Abhängigkeit, kein neues Datenbankformat.
|
||||
Die vier geänderten Anforderungen wurden in die Hauptspecs datei-eio und
|
||||
isam-datenbank synchronisiert; alle 15 Hauptspecs bestehen die strikte Validierung.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-05
|
||||
@@ -0,0 +1,41 @@
|
||||
## Context
|
||||
|
||||
RtState hält getrennte Tasten-/Mauspuffer; forms_zustellen setzt weitere Handlerframes auf, auch vor der ersten Anweisung eines gerade begonnenen Handlers. Der Stmt-Pfad dupliziert die Trap-Zustellung. SLEEP benutzt eine zweite reale Uhr. tb-vm zieht tb-ui samt unbedingten Terminaldependencies ein. Siehe proposal.md und die Delta-Specs.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** Eine gemeinsame Zustellentscheidung nutzen und Ereignisordnung vom Host bis zum sichtbaren Handlerablauf bewahren.
|
||||
|
||||
**Non-Goals:** Keine präemptiven Threads, kein neuer Scheduler und keine Änderung der festgelegten Priorität gleichzeitig fälliger klassischer Trap-Quellen.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 — Reihenfolge einmal festhalten
|
||||
|
||||
Geordnete Hostereignisse bis zur Annahme durch Trap, Control oder Konsoleneingabe erhalten. KEY STOP merkt höchstens ein Ereignis, lässt aber die Taste normal lesbar. Keine spätere Rekonstruktion der Reihenfolge aus zwei bereits getrennten Queues.
|
||||
|
||||
### D2 — Zustellung und Handlerbeginn koordinieren
|
||||
|
||||
Die vorhandenen Zustellpunkte auf eine gemeinsame Entscheidung führen. Der erste Statement-Eintritt eines zugestellten Handlers darf nicht durch das nächste bereits wartende Ereignis überholt werden; rekursive Zustellung derselben Forms-Ereignisprozedur verhindern und Rückkehr geordnet abarbeiten. Explizites DOEVENTS/Modalität bleiben kooperative Zustellpunkte. Menüsperre gilt überall und verändert keine expliziten ON/OFF/STOP-Zustände.
|
||||
|
||||
### D3 — Host besitzt Zeit und Warten
|
||||
|
||||
Instant-Fallback aus der VM entfernen. Für blockierende Wartephasen eine Host-Wartemöglichkeit mit Deadline vorsehen, die Terminal/Pipe in Realzeit und Testhosts durch virtuelle Zeit umsetzen; SLEEP darf weder mit stehender virtueller Zeit scheinbar erfolgreich enden noch busy-spinnen. Ohne Trap, Timer oder zeitabhängige Mausauswertung keine Zeitabfrage.
|
||||
|
||||
### D4 — Backend als vorhandene Paketgrenze
|
||||
|
||||
Terminalmodule und deren Dependencies in tb-ui durch ein terminal-Feature abtrennen; tb-vm nutzt tb-ui ohne dieses Feature, der CLI-Runner aktiviert es ausdrücklich. Modelle/FRM/Dialogs im terminalfreien Teil verwenden nur Host und Zellenpuffer. Eine zusätzliche Forms-Crate ist dafür nicht erforderlich.
|
||||
|
||||
### D5 — BackTab normalisieren
|
||||
|
||||
KeyCode::BackTab auf denselben Tab-Eingabepfad mit gesetztem Shiftbit abbilden. Kein eigener Fokusmechanismus.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Neue Zustellung kann Referenzen/Fehlerzustand eines wartenden Frames beschädigen → bestehende Trap-, RESUME-, BYREF- und Modalitätstests mit ausführen.
|
||||
- Cargo-Feature-Unifikation kann Terminalabhängigkeiten verdecken → terminalfreien VM-Build über einen isolierten Einbetter und dessen aufgelösten Dependency-Graph prüfen.
|
||||
- Virtuelle Hosts können bisher SLEEP ohne Zeitfortschritt verwenden → diese Hosts ausdrücklich auf die neue Wartevereinbarung umstellen, keine reale Rückfallebene beibehalten.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Vor forms-zustand-und-bedienung umsetzen. F06 bleibt Eigentum dieses Changes; Forms-State-Change verändert diese Requirement-Blöcke nicht. Bestehende Spec-/Codeverträge erst nach erfolgreicher Umsetzung synchronisieren und archivieren. Bis dahin bleiben alle Tasks offen. Änderungen als zusammenhängenden Commit je Change integrieren; bei Fehlschlag auf den vorherigen Code zurückgehen und neue Datenformatversionen nicht mit alten Lesern öffnen.
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
Ereignisse gehen verloren, laufen in falscher Reihenfolge oder umgehen Menüsperren. F04–F08, F18 und F20 zeigen außerdem Zeitabfragen ohne Bedarf und eine unerwünschte Terminalabhängigkeit der VM. Siehe [Review vom 05.09.2026](../../../docs/reviews/2026-09-05-openspec-code-review.md).
|
||||
|
||||
## What Changes
|
||||
|
||||
- Signal 1/2, KEY STOP und die FIFO-Reihenfolge von Tasten, Maus und Forms-Handlern durchgängig erhalten.
|
||||
- Menüsperre und Zustellpunkte für Statements, DOEVENTS, SLEEP und blockierende Eingaben vereinheitlichen.
|
||||
- Zeit ausschließlich bedarfsgerecht über den Host verwenden und Forms-Ereignisse während SLEEP zustellen.
|
||||
- BackTab in den vorhandenen Rückwärtstab-Pfad übersetzen.
|
||||
- VM und Forms ohne Terminalbackend separat baubar machen.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Keine neuen Capability-Pfade.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `ereignissteuerung`: Reihenfolge, KEY-STOP-Verhalten, Signalzustellung und Hostzeit durch ergänzende Grenzfallszenarien absichern.
|
||||
- `vm-ausfuehrung`: Den Pfad ohne Zeitbedarf und die Fortsetzung nach Ereignissen präzisieren.
|
||||
- `forms-steuerelemente`: Fokusreihenfolge, BackTab und Menüsperre an allen Zustellpunkten festhalten.
|
||||
- `textbildschirm`: Terminalfreie Einbettung der vollständigen VM einschließlich Forms präzisieren.
|
||||
|
||||
## Impact
|
||||
|
||||
RtState/Traps, Vm::exec und Zustell-/Wartepfade, tb-ui TerminalHost und Cargo-Features; Unit-, VM- und CLI-Tests. Keine Änderung des BASIC-Sprachumfangs.
|
||||
|
||||
Vor forms-zustand-und-bedienung umsetzen. F06 bleibt Eigentum dieses Changes; Forms-State-Change verändert diese Requirement-Blöcke nicht.
|
||||
@@ -0,0 +1,139 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Ereignisquellen mit Zeit vom Host
|
||||
Die Laufzeit SHALL Ereignisse aus vier Quellen führen: Tastatur, Maus,
|
||||
Zeit und benutzerdefinierte Ereignisse. Die Zeit SHALL vom Host bezogen
|
||||
werden;
|
||||
die Laufzeit MUST NOT für die Ereigniszustellung selbst auf die
|
||||
Systemuhr zugreifen. Ein Host ohne Terminal MUST die Zeit frei stellen
|
||||
können, sodass zeitgesteuerte Programme ohne Wartezeit und mit
|
||||
reproduzierbarem Ergebnis prüfbar sind.
|
||||
|
||||
Auch Wartefristen der VM SHALL ausschließlich von der Hostzeit abhängen. Ein Host mit stehender virtueller Zeit MUST NOT durch reale verstrichene Zeit eine Frist scheinbar erreichen.
|
||||
|
||||
#### Scenario: Zeitgesteuerter Trap ohne echte Wartezeit
|
||||
- **WHEN** ein Testhost die Zeit um 5 Sekunden vorstellt und ein Programm `ON TIMER(5) GOSUB Marke` mit `TIMER ON` aktiv hat
|
||||
- **THEN** wird der Trap zugestellt, ohne dass der Test tatsächlich wartet
|
||||
|
||||
#### Scenario: Wiederholter Lauf liefert dasselbe Ergebnis
|
||||
- **WHEN** dasselbe zeitgesteuerte Korpusprogramm zweimal mit demselben Zeitverlauf ausgeführt wird
|
||||
- **THEN** ist die Ausgabe beide Male identisch
|
||||
|
||||
#### Scenario: Stehende virtuelle Zeit
|
||||
- **WHEN** ein Host während SLEEP seine Zeit unverändert hält
|
||||
- **THEN** endet SLEEP nicht wegen realer Zeit; eine vom Host gemeldete Frist oder ein zugestelltes Ereignis beendet das Warten
|
||||
|
||||
### Requirement: Mausereignisse in Zellenkoordinaten
|
||||
Die Darstellungsschicht SHALL Mausereignisse der Ausführungsumgebung an
|
||||
die Laufzeit weitergeben: Drücken, Loslassen und Bewegung, jeweils mit
|
||||
gedrückter Taste, Umschaltzustand und Position. Die Position SHALL in
|
||||
Zellen des Textbildschirms angegeben werden, gezählt wie bei `LOCATE`,
|
||||
also 1-basiert. Ereignisse außerhalb der aktuellen Bildschirmfläche MUST
|
||||
verworfen werden. Die Reihenfolge zwischen Maus- und Tastenereignissen
|
||||
SHALL der Reihenfolge ihres Eintreffens entsprechen. Solange kein
|
||||
Verbraucher ein Mausereignis annimmt, MUST es am Zustellpunkt verworfen
|
||||
werden, damit die Warteschlange nicht unbegrenzt wächst.
|
||||
|
||||
Die Reihenfolge SHALL bis zur beobachtbaren Ausführung der zugehörigen Handler erhalten bleiben; getrennte Quellarten MUST NOT nachträglich nach ihrer Art umgeordnet werden.
|
||||
|
||||
#### Scenario: Position als Zelle
|
||||
- **WHEN** in der linken oberen Ecke der Darstellungsfläche die linke Maustaste gedrückt wird
|
||||
- **THEN** trägt das Ereignis Zeile 1 und Spalte 1
|
||||
|
||||
#### Scenario: Reihenfolge bleibt erhalten
|
||||
- **WHEN** eine Taste gedrückt und danach die Maus geklickt wird
|
||||
- **THEN** liefert die Warteschlange erst das Tasten-, dann das Mausereignis
|
||||
|
||||
#### Scenario: Ereignis außerhalb der Fläche
|
||||
- **WHEN** ein Mausereignis eine Position außerhalb der aktuellen Bildschirmgröße nennt
|
||||
- **THEN** wird es verworfen und erreicht die Warteschlange nicht
|
||||
|
||||
#### Scenario: Kein Verbraucher, keine Anhäufung
|
||||
- **WHEN** ein Programm ohne Verbraucher für Mausereignisse läuft und die Maus dauerhaft bewegt wird
|
||||
- **THEN** wächst die Warteschlange nicht über ihre Grenze und das Programm läuft unverändert weiter
|
||||
|
||||
#### Scenario: Taste vor Maus bis zum Handler
|
||||
- **WHEN** eine Taste vor einem Mausklick eintrifft und beide Handler eine Kennung anhängen
|
||||
- **THEN** steht die Tastenkennung vor der Mauskennung
|
||||
|
||||
#### Scenario: Maus vor Taste bis zum Handler
|
||||
- **WHEN** ein Mausklick vor einer Taste eintrifft
|
||||
- **THEN** wird zuerst der Maushandler wirksam
|
||||
|
||||
### Requirement: Abschließende Liste der Zustellpunkte
|
||||
Ereignisse SHALL ausschließlich an folgenden Punkten zugestellt werden
|
||||
— das Vorbild prüft die Ereignismarke „before executing the next
|
||||
statement":
|
||||
an einer Anweisungsgrenze, bei `DOEVENTS`, während `SLEEP` und vor einer
|
||||
blockierenden Eingabe. Zwischen zwei Zustellpunkten MUST NOT ein
|
||||
Trap-Handler beginnen; insbesondere MUST NOT eine Anweisung in ihrer
|
||||
Mitte unterbrochen werden. Ein zugestelltes Ereignis SHALL ein laufendes
|
||||
`SLEEP` beenden.
|
||||
|
||||
Forms-Ereignisse SHALL auch während SLEEP an dessen bestehenden Zustellpunkten verarbeitet werden. Eine für das Menü geltende Zustellsperre SHALL an jedem Zustellpunkt gleichermaßen gelten.
|
||||
|
||||
#### Scenario: Keine Zustellung innerhalb einer Anweisung
|
||||
- **WHEN** ein Ereignis fällig wird, während eine mehrteilige Anweisung ausgewertet wird
|
||||
- **THEN** läuft die Anweisung zu Ende und der Handler beginnt erst an der folgenden Anweisungsgrenze
|
||||
|
||||
#### Scenario: SLEEP endet durch Ereignis
|
||||
- **WHEN** ein Programm `SLEEP 60` ausführt und nach 2 Sekunden ein aktiver Trap fällig wird
|
||||
- **THEN** wird der Handler ausgeführt und `SLEEP` kehrt danach zurück, ohne die vollen 60 Sekunden abzuwarten
|
||||
|
||||
#### Scenario: DOEVENTS stellt zu
|
||||
- **WHEN** ein anstehendes Ereignis vorliegt und `DOEVENTS` ausgewertet wird
|
||||
- **THEN** läuft der zugehörige Handler, bevor `DOEVENTS` einen Wert liefert
|
||||
|
||||
#### Scenario: Forms-Timer beendet SLEEP
|
||||
- **WHEN** während SLEEP ein aktiver Forms-Timer fällig wird
|
||||
- **THEN** läuft dessen Handler und SLEEP kehrt danach zurück
|
||||
|
||||
### Requirement: Getrappte Tasten verlassen den Eingabestrom
|
||||
Eine Taste, für die ein aktiver `ON KEY`-Trap besteht, SHALL dem Trap
|
||||
zugestellt werden und MUST NOT zusätzlich über `INKEY$`, `INPUT`,
|
||||
`LINE INPUT` oder `INPUT$` sichtbar werden. Bei `OFF` oder `STOP` der
|
||||
Quelle SHALL die Taste dem normalen Eingabestrom erhalten bleiben.
|
||||
|
||||
Unter STOP SHALL die Taste normal lesbar bleiben und zugleich höchstens ein Ereignis für das spätere ON gemerkt werden.
|
||||
|
||||
#### Scenario: Getrappte Taste erscheint nicht bei INKEY$
|
||||
- **WHEN** `ON KEY(1) GOSUB Marke` mit `KEY(1) ON` gilt und F1 gedrückt wird
|
||||
- **THEN** läuft der Handler und ein anschließendes `INKEY$` liefert den leeren String
|
||||
|
||||
#### Scenario: Nicht getrappte Taste bleibt im Strom
|
||||
- **WHEN** derselbe Trap mit `KEY(1) OFF` gilt und F1 gedrückt wird
|
||||
- **THEN** liefert `INKEY$` die Sondertastenfolge für F1
|
||||
|
||||
#### Scenario: STOP merkt und erhält Eingabe
|
||||
- **WHEN** unter KEY(1) STOP F1 gedrückt, per INKEY$ gelesen und danach KEY(1) ON ausgeführt wird
|
||||
- **THEN** liefert INKEY$ die Sondertastenfolge und der Handler läuft nach ON genau einmal
|
||||
|
||||
### Requirement: Signal-Trap auf Betriebssystemsignalen
|
||||
`ON SIGNAL(n%) GOSUB` und die Steueranweisung `SIGNAL(n%)` SHALL
|
||||
denselben Regeln für Maskierung, Zustellung und Wiedereintritt
|
||||
unterliegen wie die übrigen Quellen. `n%` SHALL auf
|
||||
Betriebssystemsignale abgebildet werden, und zwar ausschließlich auf die
|
||||
Menge, die auf allen Zielplattformen besteht: 1 auf den
|
||||
Unterbrechungswunsch (`SIGINT`), 2 auf den Beendigungswunsch
|
||||
(`SIGTERM`). Ein anderer Wert MUST namentlich abgewiesen werden — zur
|
||||
Übersetzungszeit, wenn er konstant ist, sonst mit Laufzeitfehler 5. Ein
|
||||
Signal MUST NOT im Signalkontext verarbeitet werden; es SHALL über den
|
||||
Host als Ereignis in die Warteschlange gelangen. Die Abweichung zur
|
||||
OS/2-gebundenen Quelle des Vorbilds SHALL in der Sprachreferenz
|
||||
ausgewiesen sein.
|
||||
|
||||
#### Scenario: Signal-Trap folgt der Maskierung
|
||||
- **WHEN** `ON SIGNAL(1) GOSUB Marke` mit `SIGNAL(1) STOP` gilt und ein `SIGINT` eintrifft
|
||||
- **THEN** läuft der Handler erst nach `SIGNAL(1) ON`, und zwar genau einmal
|
||||
|
||||
#### Scenario: Unzulässige Signalnummer
|
||||
- **WHEN** ein Modul `ON SIGNAL(7) GOSUB Marke` enthält
|
||||
- **THEN** wird es namentlich abgewiesen und die Meldung nennt den zulässigen Bereich
|
||||
|
||||
#### Scenario: Signal ohne Trap bleibt Abbruch
|
||||
- **WHEN** ein `SIGINT` eintrifft, ohne dass ein `SIGNAL(1)`-Trap aktiv ist
|
||||
- **THEN** bleibt es beim bisherigen Abbruchverhalten
|
||||
|
||||
#### Scenario: SIGTERM wird zugestellt
|
||||
- **WHEN** SIGNAL(2) ON gilt und ein SIGTERM über den Host eintrifft
|
||||
- **THEN** wird das Ziel von ON SIGNAL(2) GOSUB am nächsten erlaubten Zustellpunkt ausgeführt
|
||||
@@ -0,0 +1,53 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Fokus, Tabreihenfolge und Access-Keys
|
||||
Der Fokus SHALL mit Tab in aufsteigender `TabIndex`-Folge und mit
|
||||
Umschalt-Tab rückwärts wechseln; Elemente mit `TabStop = 0` oder
|
||||
`Enabled = 0` MUST übersprungen werden. Ein `&` im Text SHALL den
|
||||
folgenden Buchstaben zum Access-Key machen, der mit Alt das Element
|
||||
auslöst oder ihm den Fokus gibt. Enter SHALL die `Default`-Schaltfläche
|
||||
auslösen, Esc die `Cancel`-Schaltfläche. Fokuswechsel MUST `LostFocus`
|
||||
am alten und `GotFocus` am neuen Element auslösen, in dieser Reihenfolge.
|
||||
|
||||
Die über das Terminal gelieferte Rückwärtstab-Taste SHALL denselben Fokuswechsel wie Tab mit Umschalt auslösen. Die Reihenfolge LostFocus vor GotFocus SHALL auch an den Wirkungen der BASIC-Handler sichtbar bleiben.
|
||||
|
||||
#### Scenario: Tab überspringt
|
||||
- **WHEN** das mittlere von drei Elementen `TabStop = 0` trägt und Tab gedrückt wird
|
||||
- **THEN** erhält das dritte Element den Fokus
|
||||
|
||||
#### Scenario: Access-Key
|
||||
- **WHEN** eine Schaltfläche `Caption = "&OK"` trägt und Alt+O gedrückt wird
|
||||
- **THEN** wird ihr `Click`-Ereignis ausgelöst
|
||||
|
||||
#### Scenario: Reihenfolge der Fokusereignisse
|
||||
- **WHEN** der Fokus von `Text1` auf `Text2` wechselt
|
||||
- **THEN** läuft erst `Text1_LostFocus`, danach `Text2_GotFocus`
|
||||
|
||||
#### Scenario: Handlerwirkungen in Fokusreihenfolge
|
||||
- **WHEN** LostFocus den Text L und GotFocus den Text G an dieselbe Variable anhängen
|
||||
- **THEN** lautet das Ergebnis LG
|
||||
|
||||
#### Scenario: Rückwärtstab vom Terminal
|
||||
- **WHEN** das Terminal eine Rückwärtstab-Taste liefert
|
||||
- **THEN** wechselt der Fokus zum vorherigen zulässigen TabIndex
|
||||
|
||||
### Requirement: Menüsystem
|
||||
Ein Formular SHALL eine Menüleiste mit bis zu sechs Ebenen tragen.
|
||||
Menüeinträge SHALL Access-Keys (`&`), Shortcuts, `Checked`, `Enabled`,
|
||||
`Visible` und Separatoren (`-`) unterstützen; ein Separator MUST NOT
|
||||
`Checked`, deaktiviert oder mit Shortcut versehen sein, ein Menütitel
|
||||
MUST NOT einen Shortcut tragen. Solange ein Menü geöffnet ist, MUST die
|
||||
Zustellung von Zeitereignissen und klassischen Traps ruhen und danach
|
||||
fortgesetzt werden.
|
||||
|
||||
#### Scenario: Menüauswahl löst Click aus
|
||||
- **WHEN** ein Menüeintrag über seinen Access-Key gewählt wird
|
||||
- **THEN** läuft seine `Click`-Prozedur
|
||||
|
||||
#### Scenario: Traps ruhen im geöffneten Menü
|
||||
- **WHEN** ein Menü geöffnet ist und ein Zeit-Trap fällig wird
|
||||
- **THEN** läuft sein Handler erst, nachdem das Menü geschlossen wurde
|
||||
|
||||
#### Scenario: UEVENT an gewöhnlicher Anweisungsgrenze
|
||||
- **WHEN** ein Menü offen ist und UEVENT vor einer Zuweisung ansteht
|
||||
- **THEN** läuft dessen Handler erst nach dem Schließen des Menüs
|
||||
@@ -0,0 +1,20 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Zellenpuffer ohne Terminalabhängigkeit
|
||||
Der Zellenpuffer mit der vollständigen Bildschirmsemantik (Cursor,
|
||||
Farbattribute, Umbruch, Scrollen, Scrollbereich, Größenänderung) SHALL
|
||||
ohne Terminal instanziierbar, veränderbar und auslesbar sein. Die
|
||||
Anbindung an ein konkretes Terminal SHALL ausschließlich in der
|
||||
Darstellungsschicht liegen. Damit MUST jedes Bildschirmverhalten in
|
||||
automatischen Tests ohne Terminal prüfbar sein, und die Ausführungsschicht
|
||||
MUST NOT von einer Terminal-Bibliothek abhängen.
|
||||
|
||||
Ein separater Einbetter SHALL die VM einschließlich Forms ohne Terminalbackend bauen und ausführen können. Der CLI-Runner SHALL das Backend ausdrücklich zuschalten.
|
||||
|
||||
#### Scenario: Bildschirmverhalten im Test ohne Terminal
|
||||
- **WHEN** ein Testprogramm in einer Umgebung ohne Terminal `LOCATE 5, 10 : PRINT "x"` ausführt
|
||||
- **THEN** trägt die Zelle (5, 10) das Zeichen `x` und der Test benötigt kein Terminal
|
||||
|
||||
#### Scenario: Terminalfreier Einbetter
|
||||
- **WHEN** ein separates Programm ausschließlich die VM mit einem Capture-Host einbindet
|
||||
- **THEN** enthält sein aufgelöster Abhängigkeitsbaum keine Terminalbibliothek und die Forms-Tests können darin laufen
|
||||
@@ -0,0 +1,24 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Ereigniszustellung an Anweisungsgrenzen
|
||||
Die VM SHALL an Anweisungsgrenzen prüfen, ob ein Ereignis zuzustellen
|
||||
ist, und in diesem Fall das `GOSUB`-Ziel des zugehörigen Traps
|
||||
ausführen. Der unterbrochene Zustand — Aufrufkette, Schleifen, Locals,
|
||||
`GOSUB`-Stapel und Fehlerzustand — MUST dabei erhalten bleiben; mit
|
||||
`RETURN` SHALL die Ausführung an der unterbrochenen Stelle fortfahren.
|
||||
Die Prüfung MUST NOT pro Opcode erfolgen; solange kein Trap definiert ist,
|
||||
MUST sie auf einen einzelnen Test hinauslaufen und MUST NOT die Uhr lesen.
|
||||
|
||||
Ohne klassische Traps, aktive Forms-Timer oder zeitabhängige Mausauswertung SHALL auch die Forms-Integration keine Hostzeit abfragen.
|
||||
|
||||
#### Scenario: Trap aus einer Prozedur heraus
|
||||
- **WHEN** ein Ereignis zugestellt wird, während eine `SUB` läuft, und das Trap-Ziel im Modulrumpf liegt
|
||||
- **THEN** läuft der Handler und nach `RETURN` setzt die `SUB` an der unterbrochenen Anweisung fort
|
||||
|
||||
#### Scenario: Ohne aktiven Trap wird die Uhr nicht gelesen
|
||||
- **WHEN** ein Programm ohne jede `ON …`-Trap-Definition ausgeführt wird
|
||||
- **THEN** fragt die Ausführung an keiner Anweisungsgrenze die Zeit ab, und der Durchsatz bleibt im Rahmen der Messgenauigkeit unverändert
|
||||
|
||||
#### Scenario: Einfaches Programm liest keine Ereigniszeit
|
||||
- **WHEN** ein Programm nur Zuweisungen und END ausführt und ein Testhost Zeitabfragen zählt
|
||||
- **THEN** ist der Zähler am Ende 0
|
||||
15
openspec/changes/ereigniszustellung-und-hostgrenzen/tasks.md
Normal file
15
openspec/changes/ereigniszustellung-und-hostgrenzen/tasks.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## 1. Hosteingabe und Maskierung
|
||||
|
||||
- [ ] 1.1 SIGNAL(2), KEY STOP und BackTab am gemeinsamen Eingang berichtigen; verifizieren durch Tests für beide Signale, alle drei KEY-Zustände mit INKEY$/INPUT und den Terminal-Key-Mapper.
|
||||
- [ ] 1.2 Geordnete Tasten-/Mausannahme implementieren; verifizieren mit K→M, M→K und gemischten Folgen einschließlich ungetrappter und getrappter Tasten.
|
||||
|
||||
## 2. Dispatch und Warten
|
||||
|
||||
- [ ] 2.1 Handlerbeginn/Rückkehr und Menüsperre an allen Zustellpunkten vereinheitlichen; verifizieren mit LostFocus→GotFocus, gleichzeitig wartenden Handlern, UEVENT bei offenem Menü, DOEVENTS und den bestehenden Fehler-/Modalitätstests.
|
||||
- [ ] 2.2 Hostgesteuertes Warten und bedarfsgerechte Zeitabfrage implementieren; verifizieren mit einem Host, der unerwartete Uhrabfragen zählt/abweist, eingefrorener und vorrückender Zeit sowie einem Forms-Timer, der SLEEP beendet.
|
||||
- [ ] 2.3 Terminal-Feature in tb-ui abgrenzen und CLI ausdrücklich anschließen; verifizieren durch isolierten terminalfreien VM-Einbetter, Dependency-Graph ohne crossterm/ratatui/signal-hook und normalen CLI-Build.
|
||||
|
||||
## 3. Abnahme
|
||||
|
||||
- [ ] 3.1 Gezielte geordnete Ereignisprogramme zum Korpus ergänzen; verifizieren mit cargo test --workspace und Vergleich der bestehenden Snapshots ohne automatisches Neuaufzeichnen.
|
||||
- [ ] 3.2 Zeit-/Zustellverträge und Backend-Feature dokumentieren; verifizieren mit Compile-/VM-Benchmarks und openspec validate ereigniszustellung-und-hostgrenzen --strict.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-05
|
||||
32
openspec/changes/forms-zustand-und-bedienung/design.md
Normal file
32
openspec/changes/forms-zustand-und-bedienung/design.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Context
|
||||
|
||||
Die LISTINDEX-Prüfung konvertiert -1 vor dem oberen Grenzvergleich nach usize. Änderungen an INTERVAL/ENABLED löschen timer_last, dessen nächster Default 0 ist. Timerereignisse werden bereits über die bestehende Queue zugestellt. Siehe proposal.md und die Delta-Specs.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** Gültige Übergänge an den bestehenden Property-/Listenmethoden korrigieren und Timerzustand an die Hostzeit koppeln.
|
||||
|
||||
**Non-Goals:** Keine neue Control-Bibliothek, keine Änderung der festgelegten Optik, keine zweite Ereignisschleife.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 — Auswahl ist ein eigener gültiger Zustand
|
||||
|
||||
-1 vor dem oberen Grenzvergleich behandeln. Einfügungen vor dem ausgewählten Eintrag verschieben dessen Index; Entfernen des gewählten Eintrags hebt die Auswahl auf, Entfernen davor verschiebt sie zurück. ListBox.Text liefert ohne Auswahl leer, eine editierbare ComboBox behält ihren separaten Eingabetext.
|
||||
|
||||
### D2 — Aktivierungszeit statt Nullzeit
|
||||
|
||||
Beim Übergang auf einen aktiven Timer den aktuellen Hostzeitpunkt als Ausgangsfrist speichern. Ändernde Property-Zugriffe erhalten diesen Zeitpunkt über den bereits vorhandenen VM/Host-Pfad; kein Systemuhrzugriff und kein globaler Zeitcache. Interval=0/Enabled=0 entfernt anstehende Zustellungen dieses Timers. Explizites Umschalten des Intervalls beginnt eine neue Frist.
|
||||
|
||||
### D3 — Laufende Timer behalten Nachholregel
|
||||
|
||||
Ein kontinuierlich aktiver 100-ms-Timer bekommt bei 250 ms zwei Ereignisse; das gilt nur für Zeit innerhalb seiner aktiven Phase. Reihenfolge gleichzeitig fälliger Timer bleibt dokumentiert nach Namen und bei gleichem Arraynamen nach Index.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Änderung an Auswahl kann ComboBox-Eingabetext löschen → Stilarten und editierbaren Text getrennt testen.
|
||||
- Intervalländerung im eigenen Handler kann alte Queueeinträge übriglassen → Reset/Deaktivierung mit bereits wartenden Ereignissen prüfen.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Nach ereigniszustellung-und-hostgrenzen. Ändert nur die Requirements zu Listen und Timer, nicht Fokus/Menü desselben Capability-Pfads. Bestehende Spec-/Codeverträge erst nach erfolgreicher Umsetzung synchronisieren und archivieren. Bis dahin bleiben alle Tasks offen. Änderungen als zusammenhängenden Commit je Change integrieren; bei Fehlschlag auf den vorherigen Code zurückgehen und neue Datenformatversionen nicht mit alten Lesern öffnen.
|
||||
24
openspec/changes/forms-zustand-und-bedienung/proposal.md
Normal file
24
openspec/changes/forms-zustand-und-bedienung/proposal.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## Why
|
||||
|
||||
Listen und Timer erfüllen einfache gültige Zustandsübergänge nicht: ListIndex=-1 liefert Fehler 5; ein spät aktivierter Timer holt Zeit seit Programmstart nach. F12/F13 betreffen das Zustandsmodell unabhängig von dessen späterem Ereignisdispatch. Siehe [Review vom 05.09.2026](../../../docs/reviews/2026-09-05-openspec-code-review.md).
|
||||
|
||||
## What Changes
|
||||
|
||||
- Explizites Aufheben einer Listenauswahl unterstützen und List/Text/Count bei Einfügen/Entfernen konsistent halten.
|
||||
- Timerfristen ab Aktivierung bzw. neu gesetztem Intervall starten und deaktivierte Zeiten nicht nachholen.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Keine neuen Capability-Pfade.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `forms-steuerelemente`: Listen-Auswahl und Zeitbezug des Timer-Steuerelements durch präzise Zustandsübergänge ergänzen.
|
||||
|
||||
## Impact
|
||||
|
||||
crates/tb-ui/src/forms.rs, Forms-Unit-/VM-Tests, Formular-Korpus und docs/forms-referenz.md. Baut auf der geordneten Ereigniszustellung auf.
|
||||
|
||||
Nach ereigniszustellung-und-hostgrenzen. Ändert nur die Requirements zu Listen und Timer, nicht Fokus/Menü desselben Capability-Pfads.
|
||||
@@ -0,0 +1,51 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Steuerelemente mit Listeninhalt
|
||||
ListBox und ComboBox SHALL `ADDITEM` und `REMOVEITEM` unterstützen und
|
||||
`List`, `ListCount`, `ListIndex` und `Text` konsistent führen; bei
|
||||
`Sorted = -1` SHALL die Einfügereihenfolge der Sortierung folgen.
|
||||
`ListIndex = -1` SHALL „keine Auswahl" bedeuten. Die ComboBox SHALL die
|
||||
drei Stilarten (Dropdown, Simple, Dropdown List) darstellen.
|
||||
|
||||
Eine Zuweisung von ListIndex = -1 SHALL in leerer wie gefüllter Liste zulässig sein. Einfügen vor der Auswahl SHALL ihren Index verschieben, Entfernen der Auswahl SHALL sie aufheben. Eine ListBox ohne Auswahl SHALL Text als leeren String liefern; editierbare ComboBox-Stile SHALL ihren unabhängigen Eingabetext erhalten.
|
||||
|
||||
#### Scenario: Element hinzufügen
|
||||
- **WHEN** `List1.ADDITEM "b"` und `List1.ADDITEM "a"` bei `Sorted = -1` ausgeführt werden
|
||||
- **THEN** liefert `List1.List(0)` den Wert `a` und `List1.ListCount` den Wert 2
|
||||
|
||||
#### Scenario: Keine Auswahl
|
||||
- **WHEN** eine ListBox ohne Auswahl gelesen wird
|
||||
- **THEN** liefert `ListIndex` den Wert −1
|
||||
|
||||
#### Scenario: Auswahl ausdrücklich aufheben
|
||||
- **WHEN** nach ADDITEM die Eigenschaft ListIndex auf -1 gesetzt wird
|
||||
- **THEN** tritt kein Fehler auf und ListIndex ist -1
|
||||
|
||||
#### Scenario: Eintrag vor Auswahl einfügen
|
||||
- **WHEN** vor einem ausgewählten Eintrag ein Element eingefügt wird
|
||||
- **THEN** bleibt derselbe Eintrag ausgewählt und sein Index steigt um 1
|
||||
|
||||
### Requirement: Timer-Steuerelement
|
||||
Ein Timer SHALL bei `Enabled = -1` und `Interval > 0` sein
|
||||
`Timer`-Ereignis im eingestellten Abstand auslösen, gestützt auf die
|
||||
Zeitquelle der Ereignissteuerung. `Interval = 0` SHALL ihn abschalten.
|
||||
Sind mehrere Timer gleichzeitig fällig, SHALL die Reihenfolge festgelegt
|
||||
und dokumentiert sein.
|
||||
|
||||
Die erste Frist SHALL ab dem Einschalten beziehungsweise neu gesetzten Intervall zählen. Zeit vor der Aktivierung MUST NOT nachgeholt werden. Deaktivierung oder Interval = 0 SHALL noch anstehende Timerereignisse verwerfen.
|
||||
|
||||
#### Scenario: Timer feuert im Abstand
|
||||
- **WHEN** ein Timer mit `Interval = 100` läuft und die Zeit um 250 ms vorrückt
|
||||
- **THEN** ist sein Ereignis zweimal gelaufen
|
||||
|
||||
#### Scenario: Interval 0 schaltet ab
|
||||
- **WHEN** `Timer1.Interval = 0` gesetzt wird
|
||||
- **THEN** läuft kein weiteres Ereignis
|
||||
|
||||
#### Scenario: Späte Aktivierung
|
||||
- **WHEN** bei Hostzeit 1000 ms ein zuvor inaktiver Timer mit Interval 100 eingeschaltet wird
|
||||
- **THEN** läuft bis 1099 ms kein Timerereignis und bei 1100 ms genau eines
|
||||
|
||||
#### Scenario: Abschalten verwirft anstehende Ereignisse
|
||||
- **WHEN** Timerereignisse anstehen und vor ihrer Zustellung Interval auf 0 gesetzt wird
|
||||
- **THEN** werden sie nicht mehr zugestellt
|
||||
13
openspec/changes/forms-zustand-und-bedienung/tasks.md
Normal file
13
openspec/changes/forms-zustand-und-bedienung/tasks.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## 1. Listen
|
||||
|
||||
- [ ] 1.1 ListIndex=-1 korrekt zulassen und Text/List/Count bei ADDITEM/REMOVEITEM konsistent halten; verifizieren mit leeren/sortierten Listen, Einfügen vor Auswahl, Entfernen der Auswahl und allen ComboBox-Stilen.
|
||||
|
||||
## 2. Timer
|
||||
|
||||
- [ ] 2.1 Aktivierungs-/Änderungszeit über den Host erfassen und deaktivierte Queueeinträge entfernen; verifizieren mit Aktivierung bei 1000 ms, erster Fälligkeit bei 1100 ms, Änderung im Handler und Interval=0.
|
||||
- [ ] 2.2 Fortlaufende Timer und gleichzeitige Fälligkeit erhalten; verifizieren mit exakt zwei Ereignissen bei 250 ms sowie mehreren Timer-/Arraynamen und Wiederholbarkeit.
|
||||
|
||||
## 3. Abnahme
|
||||
|
||||
- [ ] 3.1 Formular-Korpus um Listenauswahl und späte Timeraktivierung ergänzen und Referenz dokumentieren; verifizieren mit cargo test -p tb-ui, cargo test -p tb-vm und cargo test -p tb-cli --test compat.
|
||||
- [ ] 3.2 Delta-Specs und Diffs prüfen; verifizieren mit openspec validate forms-zustand-und-bedienung --strict und git diff --check.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-05
|
||||
37
openspec/changes/laufzeit-eingabe-und-systemstatus/design.md
Normal file
37
openspec/changes/laufzeit-eingabe-und-systemstatus/design.md
Normal file
@@ -0,0 +1,37 @@
|
||||
## Context
|
||||
|
||||
INPUT # liest stets eine ganze Zeile und ersetzt fehlende Felder durch leer. INPUT$ zählt Tastendrücke bzw. Dateibytes statt Zeichen. bi_cv prüft nur Mindestlänge. Zeitzone::ermitteln erfasst den Startzustand, versatz kann später still auf UTC fallen. Siehe proposal.md und die Delta-Specs.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** Die vorhandenen Lese-/Konvertierungspfade mit erhaltener Position, klaren Fehlern und abfragbarem Zeitzustand korrigieren.
|
||||
|
||||
**Non-Goals:** Kein allgemeines CSV-Paket, keine Änderung der UTF-32-Recorddarstellung, kein Stellen der Betriebssystemuhr.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 — Ein Lesefortschritt je Datei
|
||||
|
||||
Sequenzieller Dateiinput hält Feld-/Zeichenrest und Datei-/Decoderposition zusammen. INPUT # kann Felder derselben Zeile in mehreren Aufrufen lesen; fehlende geforderte Felder führen zu Fehler 62. LINE INPUT und INPUT$ müssen mit demselben logischen Fortschritt zusammenspielen, statt über getrennte Handles dieselben Bytes erneut zu lesen.
|
||||
|
||||
### D2 — Codepoints und Bytedarstellung trennen
|
||||
|
||||
INPUT$ zählt Unicode-Codepoints, behält den Rest einer erweiterten Tastenfolge und decodiert UTF-8 vor der Zeichenzählung. GET/PUT und MK/CV bleiben Byte-/UTF-32-Recordpfade; CV verlangt genau seine Zielbreite. Keine stillen Ersatzwerte für beschädigte Teilsequenzen.
|
||||
|
||||
### D3 — Fehler am Dateieinstieg zuordnen
|
||||
|
||||
Bei NotFound zwischen fehlendem Elternpfad und fehlender Datei unterscheiden; vorhandene allgemeine Fehlerabbildung wiederverwenden und die zusätzliche Pfadprüfung an den Dateisystemoperationen bündeln.
|
||||
|
||||
### D4 — Zonenzustand als kleiner lesbarer Vertrag
|
||||
|
||||
TIMEZONEKNOWN ohne Argument liefert INTEGER -1/0. Zeitwert und Status beruhen auf derselben jeweils gültigen Zonenermittlung; späterer Rückfall darf nicht beim Startzustand stehenbleiben. Rust-Einbetter können den Zustand weiter prüfen, CLI-Warnung bleibt ergänzend. Der Name ist die vorgeschlagene additive API dieses Changes und wird als TerminalBasic-Erweiterung dokumentiert.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Gemischte INPUT-/INPUT$-/LINE INPUT-Aufrufe können Positionen verschieben → alle Übergänge auf derselben Datei und an CRLF/EOF testen.
|
||||
- Strengere CV-Länge ändert bisher permissive Eingaben → ausdrücklich dokumentieren, Fehler 5 entspricht dem vorhandenen Spec-Vertrag.
|
||||
- Zonenermittlung hängt vom System ab → Tests müssen Ermittlungsausfall und Wiederherstellung injizieren können, ohne die Rechnerzeitzone zu ändern.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Eigenständig; FREEFILE bleibt vollständig im ISAM-Change. TIMEZONEKNOWN ersetzt keine monotone Ereigniszeit und teilt deren Zustellpfad nicht. Bestehende Spec-/Codeverträge erst nach erfolgreicher Umsetzung synchronisieren und archivieren. Bis dahin bleiben alle Tasks offen. Änderungen als zusammenhängenden Commit je Change integrieren; bei Fehlschlag auf den vorherigen Code zurückgehen und neue Datenformatversionen nicht mit alten Lesern öffnen.
|
||||
@@ -0,0 +1,29 @@
|
||||
## Why
|
||||
|
||||
Die Laufzeit verwirft INPUT-Felder, liest INPUT$ mit falscher Zeichenzählung und weicht bei CV-Längen sowie Dateifehlern ab. Zusätzlich fehlt die spezifizierte BASIC-Abfrage des Zeitzonenrückfalls (F14–F17/F19). Siehe [Review vom 05.09.2026](../../../docs/reviews/2026-09-05-openspec-code-review.md).
|
||||
|
||||
## What Changes
|
||||
|
||||
- INPUT # als fortlaufenden Feldleser mit korrektem EOF-Verhalten führen.
|
||||
- INPUT$ für Unicode-Codepoints und Teilfolgen von Sondertasten auf exakt n Zeichen begrenzen.
|
||||
- Exakte CV-Argumentbreiten und Datei-/Pfadfehler 53/76 sicherstellen.
|
||||
- Die verlangte Zonenzustandsabfrage als parameterlose INTEGER-Funktion TIMEZONEKNOWN konkretisieren: -1 bei ermittelter Zone, 0 beim UTC-Rückfall.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Keine neuen Capability-Pfade.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `datei-eio`: Fortlaufende Eingabefelder, exakte Record-Konvertierungsbreite und präzise Pfadfehler.
|
||||
- `textbildschirm`: INPUT$ zählt genau n Codepoints in Terminal- und Dateieingabe.
|
||||
- `konsolen-basisbibliothek`: Programmseitige Zonenzustandsabfrage und sichtbarer Rückfall auch nach dem Start.
|
||||
- `sprach-frontend`: Signatur der konkretisierten Zonenzustandsfunktion.
|
||||
|
||||
## Impact
|
||||
|
||||
tb-runtime Datei-/Builtin-/Zeitmodule, tb-vm INPUT-Pfad, Frontend-Builtin/HIR-Zuordnung, Inventar und Eingabe-/Zeitzonen-Korpus. TIMEZONEKNOWN ist eine additive Konkretisierung des bestehenden Solls, keine Behauptung historischer BASIC-Kompatibilität.
|
||||
|
||||
Eigenständig; FREEFILE bleibt vollständig im ISAM-Change. TIMEZONEKNOWN ersetzt keine monotone Ereigniszeit und teilt deren Zustellpfad nicht.
|
||||
@@ -0,0 +1,84 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Sequenzielle Datenübertragung
|
||||
`PRINT #` SHALL wie `PRINT` formatieren (Druckzonen, Zahlendarstellung mit
|
||||
Vorzeichenspalte), `WRITE #` SHALL Elemente komma-getrennt mit Strings in
|
||||
Anführungszeichen schreiben. `INPUT #` SHALL komma-getrennte Felder lesen
|
||||
und dabei Anführungszeichen auswerten, `LINE INPUT #` die ganze Zeile ohne
|
||||
Zeilenende. `EOF` SHALL das Erreichen des Dateiendes melden; Lesen über das
|
||||
Dateiende hinaus MUST Laufzeitfehler 62 „Input past end of file" auslösen.
|
||||
|
||||
INPUT # SHALL nicht konsumierte Felder einer Zeile für spätere Aufrufe bewahren und bei Bedarf über Zeilen hinweg weiterlesen. Fehlende geforderte Felder am Dateiende MUST Fehler 62 auslösen statt als leere Ersatzwerte zu gelten.
|
||||
|
||||
#### Scenario: WRITE # und INPUT # sind gegenläufig
|
||||
- **WHEN** `WRITE #1, "a,b", 5` geschrieben und die Zeile mit `INPUT #1, s$, n` gelesen wird
|
||||
- **THEN** enthält `s$` den Text `a,b` und `n` den Wert 5
|
||||
|
||||
#### Scenario: Lesen über das Dateiende
|
||||
- **WHEN** `INPUT #1, x` nach dem letzten Datensatz ausgeführt wird
|
||||
- **THEN** tritt Laufzeitfehler 62 auf
|
||||
|
||||
#### Scenario: Felder über zwei Aufrufe
|
||||
- **WHEN** WRITE #1, "a", "b" geschrieben und nach Wiederöffnung zweimal INPUT #1 mit je einer Stringvariablen ausgeführt wird
|
||||
- **THEN** liefern die Aufrufe a und b
|
||||
|
||||
#### Scenario: Zu wenige Felder
|
||||
- **WHEN** nur ein Feld vorhanden ist und INPUT #1 zwei Felder verlangt
|
||||
- **THEN** tritt Fehler 62 auf
|
||||
|
||||
### Requirement: Record-Konvertierungsfunktionen
|
||||
Die Bibliothek SHALL die Konvertierungsfunktionen für Recordpuffer
|
||||
bereitstellen: `MKI$`, `MKL$`, `MKS$`, `MKD$` und `MKC$` wandeln einen
|
||||
numerischen Wert in seine Byte-Darstellung als String, `CVI`, `CVL`,
|
||||
`CVS`, `CVD` und `CVC` wandeln zurück. Jedes Paar MUST gegenläufig sein.
|
||||
Ein Argument, dessen Länge nicht zur Zielbreite passt, MUST
|
||||
Laufzeitfehler 5 „Illegal function call" auslösen. `MKSMBF$`, `MKDMBF$`,
|
||||
`CVSMBF` und `CVDMBF` SHALL dieselbe Rolle für das
|
||||
Microsoft-Binärformat älterer BASIC-Versionen erfüllen; ist dieses
|
||||
Format ein deklariertes Non-Feature, MUST die Ablehnung namentlich
|
||||
erfolgen und in der Sprachreferenz unter „Abweichungen" stehen.
|
||||
|
||||
Die Argumentlänge der CV-Funktionen SHALL exakt ihrer Zielbreite entsprechen, nicht nur einer Mindestbreite.
|
||||
|
||||
#### Scenario: MK und CV sind gegenläufig
|
||||
- **WHEN** `CVL(MKL$(123456))` ausgewertet wird
|
||||
- **THEN** ist das Ergebnis 123456
|
||||
|
||||
#### Scenario: Falsche Argumentlänge
|
||||
- **WHEN** `CVI("x")` mit einem ein Zeichen langen String ausgewertet wird
|
||||
- **THEN** tritt Laufzeitfehler 5 auf
|
||||
|
||||
#### Scenario: Zu langes CVI-Argument
|
||||
- **WHEN** CVI("abc") ausgewertet wird
|
||||
- **THEN** tritt Laufzeitfehler 5 auf
|
||||
|
||||
### Requirement: Dateisystem-Anweisungen mit plattformübergreifender Pfadsemantik
|
||||
`KILL`, `NAME … AS`, `FILES`, `CHDIR`, `CHDRIVE`, `MKDIR`, `RMDIR`,
|
||||
`CURDIR$`, `DIR$` sowie `SHELL` als Anweisung **und** als Funktion SHALL
|
||||
verfügbar sein. `CURDIR$` SHALL das aktuelle Verzeichnis liefern, `DIR$`
|
||||
den ersten bzw. — bei leerem Argument — den nächsten Treffer eines
|
||||
Suchmusters und den leeren String, wenn keiner mehr folgt. Die
|
||||
`SHELL`-Funktion SHALL den Beendigungsstatus des aufgerufenen Prozesses
|
||||
liefern. `CHDRIVE` SHALL auf Plattformen ohne Laufwerksbuchstaben
|
||||
namentlich abgewiesen oder als Non-Feature dokumentiert werden. Pfade SHALL sowohl `\` als auch `/` als Trennzeichen
|
||||
akzeptieren und auf die Konvention der laufenden Plattform abgebildet
|
||||
werden; Laufwerksbuchstaben SHALL auf Nicht-Windows-Plattformen als
|
||||
gewöhnlicher Namensbestandteil behandelt oder mit benannter Ablehnung
|
||||
abgewiesen werden. Fehlende Dateien MUST Fehler 53 auslösen, fehlende
|
||||
Pfade Fehler 76 „Path not found".
|
||||
|
||||
#### Scenario: Rückwärts-Schrägstrich auf Unix
|
||||
- **WHEN** `OPEN "unter\d.txt" FOR OUTPUT AS #1` auf einer Unix-Plattform ausgeführt wird
|
||||
- **THEN** entsteht die Datei `d.txt` im Verzeichnis `unter`
|
||||
|
||||
#### Scenario: Verzeichnis durchlaufen
|
||||
- **WHEN** `DIR$("*.txt")` und danach `DIR$("")` wiederholt ausgewertet werden
|
||||
- **THEN** liefern sie nacheinander alle passenden Namen und zuletzt den leeren String
|
||||
|
||||
#### Scenario: Fehlender Pfad
|
||||
- **WHEN** `MKDIR` in einem nicht existierenden Elternverzeichnis aufgerufen wird
|
||||
- **THEN** tritt Laufzeitfehler 76 auf
|
||||
|
||||
#### Scenario: OPEN mit fehlendem Elternpfad
|
||||
- **WHEN** OPEN FOR INPUT einen Dateinamen in einem nicht vorhandenen Elternverzeichnis verwendet
|
||||
- **THEN** tritt Fehler 76 auf; fehlt nur die Datei in einem vorhandenen Verzeichnis, tritt Fehler 53 auf
|
||||
@@ -0,0 +1,23 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Zonenermittlung mit Rückfallebene
|
||||
Die Laufzeit SHALL die Zeitzone des Rechners über die
|
||||
Betriebssystemeinstellung bestimmen. Lässt sie sich nicht ermitteln,
|
||||
SHALL UTC gelten und das Programm ohne Fehler weiterlaufen. Ob die
|
||||
Rückfallebene greift, MUST für ein Programm erkennbar sein und in
|
||||
docs/sprachreferenz.md beschrieben stehen; ein stiller Wechsel auf UTC
|
||||
MUST NOT vorkommen.
|
||||
|
||||
Die parameterlose INTEGER-Funktion TIMEZONEKNOWN SHALL -1 für eine ermittelte Systemzone und 0 für den UTC-Rückfall liefern. Auch eine nach Programmstart scheiternde Zonenermittlung SHALL diesen Zustand sichtbar ändern; die Abfrage SHALL keine Zeitzone oder Systemuhr verstellen.
|
||||
|
||||
#### Scenario: Zone nicht ermittelbar
|
||||
- **WHEN** die Zeitzone des Rechners nicht bestimmt werden kann
|
||||
- **THEN** rechnen die Zeitfunktionen in UTC und das Programm läuft ohne Fehler weiter
|
||||
|
||||
#### Scenario: Rückfallebene ist erkennbar
|
||||
- **WHEN** ein Programm den Zonenzustand abfragt
|
||||
- **THEN** unterscheidet die Antwort erkennbar zwischen ermittelter Zone und Rückfall auf UTC
|
||||
|
||||
#### Scenario: Rückfall nach Programmstart
|
||||
- **WHEN** zunächst eine Zone ermittelt wurde, eine spätere Ermittlung aber scheitert
|
||||
- **THEN** verwenden die Zeitfunktionen UTC und TIMEZONEKNOWN liefert 0
|
||||
@@ -0,0 +1,27 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Jedes dokumentierte Element besitzt eine Signatur
|
||||
Die Semantik SHALL für jedes im Sprachinventar geführte Element eine
|
||||
Signatur besitzen (Argumentanzahl und -typen) — auch für Elemente, deren
|
||||
Laufzeitverhalten noch aussteht. Ein im Inventar geführtes Element MUST
|
||||
NOT als unbekannter Bezeichner oder mit einem generischen Syntaxfehler
|
||||
abgewiesen werden; die Diagnose bzw. der Laufzeitfehler MUST es
|
||||
namentlich nennen (Guiding Principle). Das gilt insbesondere für die
|
||||
Gruppen, die bisher gar keine Signatur haben: die finanzmathematischen
|
||||
Funktionen, die Record-Konvertierungsfunktionen (`MK…$`/`CV…`), die
|
||||
Dateisystem-Funktionen `CURDIR$`, `DIR$`, `CHDRIVE`, sowie `LPOS`,
|
||||
`STACK`, `SYSTEM`, `ERDEV`/`ERDEV$` und `SetFormatCC`.
|
||||
|
||||
Die TerminalBasic-Erweiterung TIMEZONEKNOWN SHALL als parameterlose INTEGER-Funktion eine Signatur besitzen und im Inventar mit ihrer tatsächlichen Umsetzung geführt sein.
|
||||
|
||||
#### Scenario: Element ohne Laufzeitverhalten wird namentlich abgewiesen
|
||||
- **WHEN** ein Modul eine im Inventar als `offen` geführte Funktion verwendet
|
||||
- **THEN** nennt die Ablehnung deren Namen und nicht „unbekannter Bezeichner"
|
||||
|
||||
#### Scenario: Argumentprüfung ohne Laufzeitverhalten
|
||||
- **WHEN** ein Modul `MKL$()` ohne Argument enthält
|
||||
- **THEN** meldet die Semantik eine Argumentanzahl-Diagnose, die `MKL$` nennt
|
||||
|
||||
#### Scenario: Signatur der Zonenzustandsabfrage
|
||||
- **WHEN** PRINT TIMEZONEKNOWN übersetzt wird
|
||||
- **THEN** entsteht eine INTEGER-Funktionsabfrage; TIMEZONEKNOWN(1) wird mit benannter Argumentanzahl-Diagnose abgewiesen
|
||||
@@ -0,0 +1,26 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Tastatureingabe ohne Zeilenmodell
|
||||
`INKEY$` SHALL ohne zu blockieren die nächste anstehende Taste liefern:
|
||||
den leeren String bei leerem Puffer, ein Zeichen bei einer
|
||||
Zeichentaste, eine zwei Zeichen lange Folge mit führendem Nullzeichen bei
|
||||
einer Sondertaste. `INPUT$(n [, #dateinummer])` SHALL genau `n` Zeichen
|
||||
lesen und dabei blockieren, ohne sie am Bildschirm zu wiederholen.
|
||||
|
||||
INPUT$ SHALL n Unicode-Codepoints liefern. Nicht verbrauchte Zeichen einer Sondertastenfolge und noch nicht gelesene Dateizeichen SHALL für Folgeaufrufe erhalten bleiben. Dateieingabe SHALL UTF-8 vor der Zeichenzählung decodieren.
|
||||
|
||||
#### Scenario: INKEY$ bei leerem Tastaturpuffer
|
||||
- **WHEN** `INKEY$` ohne anstehende Taste ausgewertet wird
|
||||
- **THEN** liefert es den leeren String und blockiert nicht
|
||||
|
||||
#### Scenario: Sondertaste als zwei Zeichen
|
||||
- **WHEN** F1 gedrückt wurde und `INKEY$` ausgewertet wird
|
||||
- **THEN** hat das Ergebnis die Länge 2 und beginnt mit dem Nullzeichen
|
||||
|
||||
#### Scenario: Sondertastenfolge in zwei Reads
|
||||
- **WHEN** F1 ansteht und zweimal INPUT$(1) ausgeführt wird
|
||||
- **THEN** liefert der erste Aufruf das Nullzeichen und der zweite den Scancode, jeweils genau ein Zeichen
|
||||
|
||||
#### Scenario: Unicode aus Datei
|
||||
- **WHEN** eine UTF-8-Datei mit ä beginnt und INPUT$(1,#1) aufgerufen wird
|
||||
- **THEN** liefert der Aufruf ä ohne Ersatzzeichen
|
||||
14
openspec/changes/laufzeit-eingabe-und-systemstatus/tasks.md
Normal file
14
openspec/changes/laufzeit-eingabe-und-systemstatus/tasks.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## 1. Eingabe und Recordfunktionen
|
||||
|
||||
- [ ] 1.1 Fortlaufenden INPUT-#-Lesefortschritt implementieren; verifizieren mit zwei Einzelreads nach WRITE zweier Felder, mehreren Zeilen, Quotes/Kommas und Fehler 62 bei zu wenigen Feldern.
|
||||
- [ ] 1.2 INPUT$ auf exakt n Codepoints führen und Reste erhalten; verifizieren mit F1 und zwei INPUT$(1)-Aufrufen, UTF-8 ä/Emoji aus Datei, gemischten Lesearten und EOF.
|
||||
- [ ] 1.3 CV-Zielbreiten exakt prüfen; verifizieren mit allen fünf MK/CV-Paaren und jeweils zu kurzen/passenden/zu langen Argumenten.
|
||||
- [ ] 1.4 Datei-/Elternpfadfehler unterscheiden; verifizieren mit fehlender Datei in vorhandenem Verzeichnis, fehlendem Elternpfad und bestehenden erfolgreichen Dateisystemfällen.
|
||||
|
||||
## 2. Zonenzustand
|
||||
|
||||
- [ ] 2.1 TIMEZONEKNOWN durch Frontend, HIR, Dispatch und Inventar ergänzen und dynamischen Rückfall sichtbar halten; verifizieren mit ermittelter Zone, UTC-Rückfall, späterem Ausfall/Wiederherstellung und unverändertem DATE$/TIME$/NOW/TIMER-Versatz.
|
||||
|
||||
## 3. Abnahme
|
||||
|
||||
- [ ] 3.1 Sprach-/Bibliotheksreferenz und Eingabe-/Zonenkorpus ergänzen; verifizieren mit cargo test --workspace, unveränderten vorhandenen Snapshots und openspec validate laufzeit-eingabe-und-systemstatus --strict.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-05
|
||||
37
openspec/changes/projektmodule-und-kompilat/design.md
Normal file
37
openspec/changes/projektmodule-und-kompilat/design.md
Normal file
@@ -0,0 +1,37 @@
|
||||
## Context
|
||||
|
||||
input_sources/append_project_source erzeugen einen einzigen String, doppelte Definitionen werden teilweise entfernt. FormCatalog.find benutzt den ersten globalen Namen. PROC enthält Code/Zeilen inline, OBJS nur Objektstammdaten. Das CLI-build-Ergebnis verwirft die separaten FormFile-Werte. Siehe proposal.md und die Delta-Specs.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** Den vorhandenen Compilerpipeline-Vertrag auf ein vollständiges Projekt erweitern und daraus ein allein ausführbares Artefakt liefern.
|
||||
|
||||
**Non-Goals:** Keine IDE-Oberfläche, keine native Executable-Verpackung, keine Binärkompatibilität zu alten unvollständigen TBC-Versionen.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 — Quellidentität vor Expansion sichern
|
||||
|
||||
Projektmitglieder und Include-Segmente als Quellen mit stabiler ID führen; physische Datei/Zeile/Spalte getrennt von BASICs numerischem ERL-Label bewahren. Kein nachträgliches Zurückrechnen aus der verketteten Zeilenzahl. Einmodul-APIs können den Projektpfad mit einem Mitglied aufrufen.
|
||||
|
||||
### D2 — Namen innerhalb ihres Containers auflösen
|
||||
|
||||
Form- und Control-IDs bleiben numerisch; Lookup sucht zuerst im expliziten Formular/aktuellen Modul. Modulprozeduren erhalten qualifizierte IDs, Form_Load pro Formular eine eigene Bindung. Doppelte lokale Namen dürfen nicht durch globales Deduplizieren verschwinden; echte Duplikate im selben Scope melden.
|
||||
|
||||
### D3 — Selbstständiges Kompilat
|
||||
|
||||
Vor dem Serialisieren das vollständige Forms-Anfangsmodell mit Werten, Indizes und Eltern-IDs aufnehmen. Lesen rekonstruiert dieselben Anfangswerte; CLI run akzeptiert das neue TBC direkt. Normale Projektdateien müssen nach build zum Ausführen nicht mehr verfügbar sein.
|
||||
|
||||
### D4 — Formatrevision statt zusätzlicher Scheinabschnitte
|
||||
|
||||
Inline-Code und Inline-Quellortinstruktionen beibehalten, aber vollständige Quell-/Modultabelle und Prozedursignaturen hinzufügen. Version 4 reservieren, vorhandene Kennungen dokumentieren und OBJS um vollständige Anfangsdaten erweitern. Kein separater CODE/LINES-Abschnitt allein zur Erfüllung einer veralteten Strukturvorgabe. Unbekannte/ältere inkompatible Versionen klar ablehnen.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Scope-Änderungen können bisher zufällig verbundene globale Namen verändern → Mehrmodulprogramme mit SHARED/COMMON, wiederholten Includes und gleichnamigen privaten Prozeduren prüfen.
|
||||
- Verlust von ERL-Semantik durch neue Quellorte → numerische Labels und physische Dateizeilen ausdrücklich getrennt testen.
|
||||
- Containeränderung kann fremde Dateien fehlinterpretieren → Längen/IDs/Versionen beim Laden prüfen und Roundtrip-Ausführung mit entfernten Quellen testen.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Eigenständiger Compiler-/Container-Change. Nach Ereigniszustellung integrieren, falls beide interp.rs ändern. F24 und alle Bytecode-Requirement-Blöcke gehören ausschließlich hierher. Bestehende Spec-/Codeverträge erst nach erfolgreicher Umsetzung synchronisieren und archivieren. Bis dahin bleiben alle Tasks offen. Änderungen als zusammenhängenden Commit je Change integrieren; bei Fehlschlag auf den vorherigen Code zurückgehen und neue Datenformatversionen nicht mit alten Lesern öffnen.
|
||||
30
openspec/changes/projektmodule-und-kompilat/proposal.md
Normal file
30
openspec/changes/projektmodule-und-kompilat/proposal.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
Mehrere Module/Formulare werden zu einem Quelltext und globalen Objektraum zusammengelegt. Dadurch scheitern formularlokale Namen, Fehlerorte stimmen nicht und FRM-Anfangswerte fehlen nach dem Kompilat-Rundlauf (F09–F11); Containerdokumentation und Spec sind außerdem überholt (F24). Siehe [Review vom 05.09.2026](../../../docs/reviews/2026-09-05-openspec-code-review.md).
|
||||
|
||||
## What Changes
|
||||
|
||||
- Projekt- und Include-Quellen mit ursprünglichem Modul/Datei/Zeile erhalten und Prozeduren/Controls im jeweiligen Namensraum auflösen.
|
||||
- Quellorte und Breakpoints mit Modulidentität durch Frontend, Kompilat und VM führen.
|
||||
- Formularbeschreibungen und Anfangswerte in das eigenständig ladbare Kompilat aufnehmen.
|
||||
- **BREAKING**: Kompilatversion für das vollständige Projektmodell erhöhen; ältere Versionen weiterhin klar mit Versionsnennung ablehnen.
|
||||
- Container-/Opcode-Spezifikation und Dokumentation an das tatsächlich vereinbarte Format angleichen.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Keine neuen Capability-Pfade.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `bytecode-kompilat`: Eigenständiges Projektkompilat, vollständige Quellorte und dokumentiertes Containerformat.
|
||||
- `sprach-frontend`: Projektübergreifende Diagnostik und formularlokale Objektauflösung.
|
||||
- `forms-objektmodell`: Gleichnamige Controls unterschiedlicher Formulare separat adressieren.
|
||||
- `vm-ausfuehrung`: Breakpoints und Inspektion über Modulidentität ermöglichen.
|
||||
|
||||
## Impact
|
||||
|
||||
CLI input_sources/expand_includes, Frontend SourcePos/HIR/FormCatalog, Codegen/bytecode/interp, FRM-Anwendung, Tests und docs/tbvm-design.md/dateiformate.md. Kein neuer Linker als separater Prozess.
|
||||
|
||||
Eigenständiger Compiler-/Container-Change. Nach Ereigniszustellung integrieren, falls beide interp.rs ändern. F24 und alle Bytecode-Requirement-Blöcke gehören ausschließlich hierher.
|
||||
@@ -0,0 +1,59 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Monomorpher Opcode-Satz
|
||||
Der Codegenerator SHALL jedes HIR-Konstrukt in typisierte, monomorphe
|
||||
Opcodes übersetzen: Arithmetik, Vergleiche und Konvertierungen tragen den
|
||||
Operandentyp im Opcode (kein Tag-Dispatch über Werte zur Laufzeit);
|
||||
implizite Konvertierungen erscheinen als explizite Konvertierungs-Opcodes
|
||||
an den vom HIR bestimmten Stellen. Der Opcode-Satz SHALL in
|
||||
docs/tbvm-design.md vollständig dokumentiert sein.
|
||||
|
||||
Die Dokumentation SHALL sämtliche tatsächlich serialisierbaren Instruktionen einschließlich Forms, Ereignissteuerung und Dateioperationen mit Operanden und Encoding führen.
|
||||
|
||||
#### Scenario: Gemischter Ausdruck wird monomorph
|
||||
- **WHEN** `d# = i% + 1.5#` übersetzt wird (INTEGER-Variable, DOUBLE-Ziel)
|
||||
- **THEN** enthält der Bytecode einen Konvertierungs-Opcode INTEGER→DOUBLE und eine DOUBLE-Addition, keinen generischen Additions-Opcode
|
||||
|
||||
#### Scenario: Namen sind zur Laufzeit aufgelöst
|
||||
- **WHEN** eine Variable oder Prozedur im Bytecode referenziert wird
|
||||
- **THEN** geschieht das über Slot- bzw. Tabellenindizes, nicht über Namens-Lookups
|
||||
|
||||
### Requirement: `.tbc`-Containerformat
|
||||
Das Kompilat SHALL als `.tbc`-Datei serialisierbar und wieder ladbar sein:
|
||||
Magic `TBC\0`, Formatversion und dokumentierte Abschnittstabelle.
|
||||
Der Container SHALL Quellen und Module, Konstantenpool, TYPE-Layouts,
|
||||
modulqualifizierte Prozeduren mit vollständiger Signatur, Locals und Code,
|
||||
DATA sowie die vollständigen Forms-Anfangsbeschreibungen führen.
|
||||
Code und Quellortinformationen SHALL inline in den Prozeduren liegen
|
||||
dürfen; eigenständige CODE-/LINES-Abschnitte sind nicht erforderlich. Laden und erneutes Serialisieren MUST verlustfrei sein;
|
||||
eine unbekannte Formatversion MUST mit einer klaren Fehlermeldung
|
||||
abgewiesen werden.
|
||||
|
||||
Ein aus FRM- oder Projektquellen gebautes Kompilat SHALL ohne erneuten Zugriff auf diese Quellen ausführbar sein und ihre Anfangseigenschaften und Designzeit-Control-Arrays bewahren.
|
||||
|
||||
#### Scenario: Roundtrip
|
||||
- **WHEN** ein kompiliertes Modul als `.tbc` geschrieben und wieder geladen wird
|
||||
- **THEN** ist das geladene Kompilat funktional identisch (gleiche Ausführung, gleiche Zeilenzuordnung)
|
||||
|
||||
#### Scenario: Unbekannte Version
|
||||
- **WHEN** eine `.tbc`-Datei mit höherer Formatversion geladen wird
|
||||
- **THEN** wird das Laden mit einer Meldung abgelehnt, die die Version nennt
|
||||
|
||||
#### Scenario: Formularwerte nach Kompilat-Rundlauf
|
||||
- **WHEN** ein Formular mit Text1.Text = "hello" kompiliert, gespeichert und ohne Quelldatei geladen wird
|
||||
- **THEN** liefert der erste Zugriff auf Text1.Text weiterhin hello
|
||||
|
||||
### Requirement: Zeilentabelle für Fehlerortung
|
||||
Der Bytecode SHALL jede Anweisung ihrem Ursprung (Moduldatei, Zeile)
|
||||
zuordnen, sodass Laufzeitfehlermeldungen, `ERL`, Breakpoints und
|
||||
Einzelschritt die Quellzeile exakt benennen können.
|
||||
|
||||
Die Zuordnung SHALL auch nach Include-Expansion und bei mehreren Projektmitgliedern die ursprüngliche Datei und physische Zeile bewahren. Numerische BASIC-Zeilenlabels für ERL SHALL davon getrennt bleiben.
|
||||
|
||||
#### Scenario: Fehler nennt Zeile
|
||||
- **WHEN** in Zeile 42 eines Programms ein Laufzeitfehler ohne Handler auftritt
|
||||
- **THEN** nennt die Fehlermeldung Zeile 42
|
||||
|
||||
#### Scenario: Fehler aus Projektbibliothek
|
||||
- **WHEN** lib.bas in physischer Zeile 2 einen Fehler auslöst und über app.mak aufgerufen wird
|
||||
- **THEN** nennt die Meldung lib.bas und Zeile 2 statt einer verketteten Projektzeile
|
||||
@@ -0,0 +1,27 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Formulare und Steuerelemente als benannte Objekte
|
||||
Ein Formular SHALL unter seinem Namen ansprechbar sein, ein
|
||||
Steuerelement unter seinem Namen innerhalb seines Formulars. Eigenschaften
|
||||
SHALL über `objekt.eigenschaft` gelesen und geschrieben werden, mit dem
|
||||
in der Forms-Referenz festgelegten Typ und Wertebereich. Es SHALL keine
|
||||
Standardeigenschaft geben: `Text1 = "x"` MUST abgewiesen werden. Das
|
||||
Schreiben eines Wertes außerhalb des Wertebereichs MUST Laufzeitfehler 5
|
||||
auslösen; eine zur Laufzeit schreibgeschützte Eigenschaft MUST bei
|
||||
Zuweisung einen Fehler auslösen.
|
||||
|
||||
#### Scenario: Eigenschaft lesen und schreiben
|
||||
- **WHEN** `Text1.Text = "hallo"` ausgeführt und danach `Text1.Text` gelesen wird
|
||||
- **THEN** liefert der Zugriff `hallo`
|
||||
|
||||
#### Scenario: Keine Standardeigenschaft
|
||||
- **WHEN** ein Programm `Text1 = "x"` enthält
|
||||
- **THEN** wird es abgewiesen und die Meldung nennt `Text1`
|
||||
|
||||
#### Scenario: Wert außerhalb des Bereichs
|
||||
- **WHEN** `Check1.Value = 3` ausgeführt wird
|
||||
- **THEN** tritt Laufzeitfehler 5 auf
|
||||
|
||||
#### Scenario: Getrennte Werte gleichnamiger Controls
|
||||
- **WHEN** Form1!Text1.Text auf a und Form2!Text1.Text auf b gesetzt werden
|
||||
- **THEN** liefern die beiden Zugriffe unabhängig a und b
|
||||
@@ -0,0 +1,50 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Diagnostik mit Vorbild-Meldungen
|
||||
Diagnosen SHALL exakte 1-basierte Quelltextpositionen (Zeile, Spalte)
|
||||
tragen; die Meldungstexte SHALL dem Katalog des Vorbilds folgen, wo eine
|
||||
Entsprechung existiert (z. B. „Type mismatch", „Duplicate definition",
|
||||
„Label not defined", „Subprogram not defined", „Overflow").
|
||||
|
||||
Bei Projekten und Includes SHALL jede Diagnose den ursprünglichen Dateinamen mit dessen physischer Zeile und Spalte tragen.
|
||||
|
||||
#### Scenario: Position in der Diagnose
|
||||
- **WHEN** in Zeile 3 ein Typkonflikt auftritt
|
||||
- **THEN** nennt die Diagnose Zeile 3 und die Spalte des Ausdrucks
|
||||
|
||||
#### Scenario: Include-Diagnose
|
||||
- **WHEN** ein eingebundenes Include einen Typfehler enthält
|
||||
- **THEN** nennt die Diagnose die Include-Datei und ihre eigene Quellposition
|
||||
|
||||
### Requirement: Objektzugriffe in Grammatik und Semantik
|
||||
Der Parser SHALL Punktpfade auf Formulare und Steuerelemente
|
||||
(`objekt.eigenschaft`), den Container-Operator
|
||||
`form!steuerelement[.eigenschaft]`, `IF TYPEOF ausdruck IS klasse`, den
|
||||
Metabefehl `$FORM` und die Datentypen `FORM` und `CONTROL` als
|
||||
Parametertypen erkennen. Die Semantik SHALL Eigenschaften gegen die
|
||||
Klasse des Objekts prüfen und ihren Typ bestimmen. Ein Punktpfad, dessen
|
||||
Basis weder ein benutzerdefinierter Typ noch ein bekanntes Objekt ist,
|
||||
MUST eine Diagnose erzeugen, die Basis und Eigenschaft namentlich nennt;
|
||||
er MUST NOT zu einer impliziten Variablen mit Punkt im Namen werden.
|
||||
|
||||
Gleichnamige Steuerelemente unterschiedlicher Formulare SHALL im jeweiligen Formularnamensraum aufgelöst werden; die explizite Containerangabe SHALL vor der lokalen Namenssuche wirken.
|
||||
|
||||
#### Scenario: Unbekanntes Objekt wird benannt
|
||||
- **WHEN** ein Modul `Text9.Text = "a"` ohne Steuerelement `Text9` enthält
|
||||
- **THEN** nennt die Diagnose `Text9` als unbekanntes Objekt statt „Type mismatch" zu melden
|
||||
|
||||
#### Scenario: Unbekannte Eigenschaft wird benannt
|
||||
- **WHEN** ein Modul `Text1.Farbe = 3` enthält
|
||||
- **THEN** nennt die Diagnose `Farbe` als unbekannte Eigenschaft von `TextBox`
|
||||
|
||||
#### Scenario: Eigenschaftstyp wird geprüft
|
||||
- **WHEN** ein Modul `Text1.Text = 5` enthält
|
||||
- **THEN** meldet die Semantik einen Typkonflikt mit Nennung der Eigenschaft
|
||||
|
||||
#### Scenario: TYPEOF
|
||||
- **WHEN** ein Modul `IF TYPEOF Ziel IS CommandButton THEN` enthält
|
||||
- **THEN** wird es übersetzt und die Klasse gegen die bekannten Steuerelementklassen geprüft
|
||||
|
||||
#### Scenario: Gleichnamige Controls verschiedener Formulare
|
||||
- **WHEN** Form1 und Form2 jeweils Text1 enthalten und Form2!Text1.Text gelesen wird
|
||||
- **THEN** wird Text1 in Form2 aufgelöst und das Programm übersetzt diagnosefrei
|
||||
@@ -0,0 +1,24 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Unterbrechbare step()-Ausführung
|
||||
Die VM SHALL als zustandsbehaftete, vom Einbetter getriebene Struktur
|
||||
ausführbar sein: An Anweisungsgrenzen prüft sie ein Flag-Wort und kann
|
||||
anhalten (Breakpoint auf (Modul, Zeile), Einzelschritt, Abbruchwunsch).
|
||||
Im angehaltenen Zustand MUST der Einbetter Variableninhalte (Skalare,
|
||||
Arrayelemente, UDT-Felder) inspizieren und die Ausführung fortsetzen
|
||||
können. Die Tick-Prüfung erfolgt nur an Anweisungsgrenzen, nicht pro
|
||||
Opcode.
|
||||
|
||||
Ein Breakpoint SHALL durch Modulidentität und physische Quellzeile bestimmt sein. Gleiche Zeilennummern anderer Module MUST NOT denselben Breakpoint auslösen.
|
||||
|
||||
#### Scenario: Breakpoint hält an
|
||||
- **WHEN** ein Breakpoint auf Zeile 10 gesetzt ist und die Ausführung Zeile 10 erreicht
|
||||
- **THEN** hält die VM vor der Anweisung an und meldet Modul und Zeile
|
||||
|
||||
#### Scenario: Inspektion im Halt
|
||||
- **WHEN** die VM an einem Breakpoint steht
|
||||
- **THEN** liefert die Inspektions-API den aktuellen Wert einer benannten Variablen
|
||||
|
||||
#### Scenario: Gleiche Zeile in zwei Modulen
|
||||
- **WHEN** ein Breakpoint auf Modul A, Zeile 10 gesetzt ist und Modul B Zeile 10 ausführt
|
||||
- **THEN** läuft B weiter; erst A Zeile 10 hält an
|
||||
15
openspec/changes/projektmodule-und-kompilat/tasks.md
Normal file
15
openspec/changes/projektmodule-und-kompilat/tasks.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## 1. Quellen und Scopes
|
||||
|
||||
- [ ] 1.1 Quell-/Modulidentitäten durch Include-Expansion und Frontend erhalten; verifizieren mit Compile- und Runtimefehlern aus lib.bas:2 und verschachtelten Includes.
|
||||
- [ ] 1.2 Form-/Prozedurscopes korrekt auflösen; verifizieren mit zwei Form_Load-Prozeduren, zwei Text1-Controls und unterschiedlichen Form1!Text1/Form2!Text1-Werten sowie echten Duplikaten im selben Scope.
|
||||
|
||||
## 2. Kompilat und Debugger
|
||||
|
||||
- [ ] 2.1 Vollständige Forms-Anfangsdaten, Quelltabelle und Prozedursignaturen serialisieren; verifizieren durch bytegleichen Kompilat-Rundlauf und gleiche Ausführung inklusive Designzeit-Control-Arrays.
|
||||
- [ ] 2.2 Modulbezogene Breakpoints/Quellortmeldungen ergänzen; verifizieren mit identischen physischen Zeilennummern in zwei Modulen, Include-Fehlern, Einzelschritt und unverändertem ERR/ERL.
|
||||
- [ ] 2.3 CLI-run für TBC anschließen und Versionsgrenze anheben; verifizieren mit build, Entfernen aller Quellen, run des Kompilats und klarer Ablehnung inkompatibler Versionen.
|
||||
|
||||
## 3. Abnahme
|
||||
|
||||
- [ ] 3.1 Container und vollständige Opcode-/Operandentabelle dokumentieren; verifizieren mit Format-/Opcode-Abgleich, cargo test --workspace und dem Compile-Budget.
|
||||
- [ ] 3.2 Delta-Specs vollständig gegen die erhaltenen Szenarien prüfen; verifizieren mit openspec validate projektmodule-und-kompilat --strict und git diff --check.
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-05
|
||||
@@ -0,0 +1,37 @@
|
||||
## Context
|
||||
|
||||
pruefe_forms erwartet jede Klasseneigenschaft und jedes Ereignis pauschal als implementiert. Der Formularfallback in handle_key erzeugt nur KEYDOWN, während FORM_KEYPRESS/FORM_KEYUP implementiert heißen. traps.bas und formular.frm fehlen die genannten Kombinationen. write_text besitzt bereits einen Erhaltungsmodus. Siehe proposal.md und die Delta-Specs.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** Grüne Prüfungen an beobachtbares Sollverhalten binden und veraltete Normtexte gezielt berichtigen.
|
||||
|
||||
**Non-Goals:** Kein erneutes allgemeines Refactoring, kein ungezieltes Testframework, kein Aufweichen der übrigen Spezifikationen, keine Änderung des Bytecode-Vertrags dieses Changes (Eigentum projektmodule-und-kompilat).
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 — Vorhandenen Inventartest erweitern
|
||||
|
||||
Inventareinträge weiterhin aus der Markdowntabelle lesen, aber für implementiert mindestens erzeugbare Frontend-/HIR-Pfade ohne Unsupported und auflösbare Runtime-Ziele prüfen. Syntaxformen mit expliziten kleinen Programmvorlagen abdecken; Forms-Probequellen aus Klassen-/Signaturmetadaten erzeugen. Klassentabellen allein gelten nicht als Laufzeitnachweis. Für Ereignisse reale Auslöser bis zum BASIC-Handler prüfen; die konkret fehlenden Form-KeyPress/KeyUp ergänzen, nicht den Status auf offen setzen.
|
||||
|
||||
### D2 — Korpusfälle nach fehlender Bedingung hinzufügen
|
||||
|
||||
Bestehenden CaptureHost/Harness wiederverwenden; jede Maskierungsquelle in ON/OFF/STOP, EVENT OFF, Control-Alt-Key, Shift-Tab und Fokusreihenfolge mit deklarierter Ereignis-/Zeitfolge prüfen. Sollbilder werden aus erwarteter Semantik festgelegt und bei Änderung fachlich geprüft.
|
||||
|
||||
### D3 — Bestehende Produktentscheidungen synchronisieren
|
||||
|
||||
Nur LINE/PAINT/VIEW/SCREEN 0–13 aus der pauschalen Grafikablehnung ausnehmen; verbleibende Non-Features behalten. FRM: unverändertes Lesen/Schreiben erhält die Originalbytes; neues/geändertes kanonisches Schreiben lässt Defaults aus. Keine Änderung des funktionierenden Erhaltungsmodus nötig.
|
||||
|
||||
### D4 — Abschließender Review als Befundnachweis
|
||||
|
||||
Nach den fünf Korrektur-Changes jede F-ID mit konkretem Regressionstest/Prüfergebnis versehen. Den Bericht von 05.09. als historischen Ausgangsstand erhalten und eine eigene Ergebnisdatei anlegen. Den vorhandenen öffentlichen Fremdprogrammbestand mit seiner dokumentierten Revision verwenden; bei fehlendem Bestand Nachweis ausdrücklich offenlassen statt eine erfolgreiche Bedienung zu behaupten.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Generierte Tests spiegeln wieder nur Implementierungsmetadaten → erwartete Ergebnisse für kritische Ereignisse und negative Markerfälle unabhängig in kleinen Quellen festhalten.
|
||||
- Überlappung mit funktionalen Korrekturen → diese zuerst integrieren und nur die hier benannten verbleibenden Eventquellen ändern.
|
||||
- Fremdprogramme können Hardware-Non-Features außerhalb des akzeptierten Bestands enthalten → dokumentierten Prüfbestand/Revision festhalten, keine globale Kompatibilitätszusage daraus ableiten.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Abschließende Gesamtabnahme nach allen fünf anderen Changes. Die Grafik-/FRM-Textpräzisierungen können vorher überprüft werden; Archivierung und Gesamtabschluss erst nach deren Regressionen. Bestehende Spec-/Codeverträge erst nach erfolgreicher Umsetzung synchronisieren und archivieren. Bis dahin bleiben alle Tasks offen. Änderungen als zusammenhängenden Commit je Change integrieren; bei Fehlschlag auf den vorherigen Code zurückgehen und neue Datenformatversionen nicht mit alten Lesern öffnen.
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
Der aktuelle Inventarabgleich bestätigt überwiegend Tabellen gegen dieselben Tabellen, während konkrete Formularereignisse fehlen. Korpusabdeckung und Hauptspecs sind nach den jüngsten Features nicht vollständig nachgezogen (F21–F23/F25). Siehe [Review vom 05.09.2026](../../../docs/reviews/2026-09-05-openspec-code-review.md).
|
||||
|
||||
## What Changes
|
||||
|
||||
- Inventarabgleich auf tatsächlich erreichbare Frontend-/Laufzeitpfade und benannte negative Nachweise stützen; fehlende deklarierte Form_KeyPress/Form_KeyUp-Pfade ergänzen.
|
||||
- Explizite Korpusfälle für KEY/UEVENT-Maskierung, Control-Access-Key, Rückwärtstab und geordnete Formularereignisse ergänzen.
|
||||
- Grafik-Non-Features mit der bereits dokumentierten implementierten Untermenge abgleichen.
|
||||
- FRM-Schreibvertrag in Erhaltung unveränderter Quelldateien und kanonische Ausgabe präzisieren.
|
||||
- Den Review nach Umsetzung der fünf Korrektur-Changes erneut gegen jeden Befund abgleichen.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Keine neuen Capability-Pfade.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `sprachinventar`: Inventarstatus mit unabhängigem Frontend-/Laufzeitnachweis absichern.
|
||||
- `kompat-testkorpus`: Verlangte Ereigniskombinationen und Fremdprogramm-Nachweise systematisch erreichbar machen.
|
||||
- `sprach-frontend`: Grafik-Ausnahmen zur Non-Feature-Abweisung korrekt beschreiben.
|
||||
- `forms-dateiformat`: Byteerhalt und kanonisches Weglassen von Defaultwerten klar unterscheiden.
|
||||
|
||||
## Impact
|
||||
|
||||
Inventar-/Korpustests, konkrete Formular-Eventquellen, docs/inventar.md, Sprach-/Forms-/Dateiformatreferenzen. Keine pauschale Statusherabsetzung, keine automatische Erzeugung neuer Golden Files.
|
||||
|
||||
Abschließende Gesamtabnahme nach allen fünf anderen Changes. Die Grafik-/FRM-Textpräzisierungen können vorher überprüft werden; Archivierung und Gesamtabschluss erst nach deren Regressionen.
|
||||
@@ -0,0 +1,22 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Schreiben ist die Umkehrung des Lesens
|
||||
Das unveränderte Schreiben einer gelesenen Formularbeschreibung SHALL
|
||||
die Originaldatei bytegleich erhalten, einschließlich explizit
|
||||
geschriebener Vorgabewerte. Beim kanonischen Schreiben einer neuen oder
|
||||
geänderten Beschreibung SHALL nur geschrieben werden, was vom
|
||||
Vorgabewert abweicht. Für diese kanonische Ausgabe SHALL Reihenfolge
|
||||
und Einrückung festgelegt und dokumentiert sein, damit zwei Läufe
|
||||
dieselbe Datei erzeugen.
|
||||
|
||||
#### Scenario: Rundlauf
|
||||
- **WHEN** eine Formulardatei gelesen und unverändert wieder geschrieben wird
|
||||
- **THEN** ist die geschriebene Datei byte-gleich zur gelesenen
|
||||
|
||||
#### Scenario: Vorgabewerte werden nicht geschrieben
|
||||
- **WHEN** eine neue oder geänderte Beschreibung kanonisch geschrieben wird und ein Steuerelement nur Vorgabewerte trägt
|
||||
- **THEN** enthält sein Block außer `Begin`/`End` keine Eigenschaftszeile
|
||||
|
||||
#### Scenario: Expliziter Default im unveränderten Original
|
||||
- **WHEN** eine gelesene Datei einen Vorgabewert ausdrücklich enthält und unverändert geschrieben wird
|
||||
- **THEN** bleibt auch diese Eigenschaftszeile bytegleich erhalten
|
||||
@@ -0,0 +1,64 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Korpusabdeckung der Ereignis-Traps
|
||||
Der Testkorpus SHALL die Ereignissteuerung mit Programmen abdecken, die
|
||||
ohne echte Wartezeit und mit reproduzierbarer Ausgabe laufen: Zeit-Trap,
|
||||
Tasten-Trap einschließlich der Frage, ob die Taste im Eingabestrom
|
||||
verbleibt, benutzerdefiniertes Ereignis, die drei Maskierungszustände je
|
||||
Quelle, der Hauptschalter `EVENT` sowie Rückkehr und Nicht-Wiedereintritt
|
||||
eines Handlers. Ein Korpusprogramm mit Zeitbezug SHALL seinen
|
||||
Zeitverlauf im Programmkopf deklarieren; ohne Deklaration MUST der
|
||||
Harness den Lauf abweisen statt auf die Systemuhr auszuweichen.
|
||||
|
||||
Die drei Zustände SHALL für KEY und UEVENT ebenso ausdrücklich im Korpus vorkommen wie für TIMER, einschließlich der Lesbarkeit einer unter KEY STOP gemerkten Taste.
|
||||
|
||||
#### Scenario: Zeit-Trap im Korpus
|
||||
- **WHEN** ein Korpusprogramm mit deklariertem Zeitverlauf und `ON TIMER` ausgeführt wird
|
||||
- **THEN** entspricht der Bildschirminhalt byte-genau der Sollausgabe und der Lauf benötigt keine messbare Wartezeit
|
||||
|
||||
#### Scenario: Fehlende Zeitdeklaration
|
||||
- **WHEN** ein Korpusprogramm zeitgesteuerte Traps nutzt, aber keinen Zeitverlauf deklariert
|
||||
- **THEN** schlägt der Harness mit einer Meldung fehl, die die fehlende Deklaration nennt
|
||||
|
||||
#### Scenario: KEY STOP im Korpus
|
||||
- **WHEN** der Korpus einen F1-Druck unter KEY STOP und ein späteres ON ausführt
|
||||
- **THEN** prüft seine Sollausgabe sowohl die normal lesbare Taste als auch genau eine spätere Handlerausführung
|
||||
|
||||
### Requirement: Korpusabdeckung der Formularprogramme
|
||||
Der Testkorpus SHALL Formularprogramme mit byte-genauer
|
||||
Bildschirm-Sollausgabe führen. Ihre Ereignisfolge (Tasten, Maus, Zeit)
|
||||
SHALL im Programmkopf deklariert und vom Harness eingespeist werden,
|
||||
sodass ein Lauf ohne Terminal und ohne Wartezeit auskommt und zweimal
|
||||
dasselbe Ergebnis liefert. Abgedeckt SHALL sein: Fokus- und
|
||||
Tabreihenfolge, Access-Key, Klick über die Maus, Menüauswahl, ein
|
||||
Listen-Steuerelement, ein Timer und ein modaler Dialog.
|
||||
|
||||
Der Access-Key-Nachweis SHALL mindestens ein gewöhnliches Control betreffen und MUST NOT allein durch einen Menü-Access-Key ersetzt werden. Rückwärtstab und die beobachtbare Reihenfolge LostFocus vor GotFocus SHALL enthalten sein.
|
||||
|
||||
#### Scenario: Formularprogramm im Korpus
|
||||
- **WHEN** ein Formular-Korpusprogramm mit deklarierter Ereignisfolge ausgeführt wird
|
||||
- **THEN** entspricht der Bildschirminhalt byte-genau der Sollausgabe
|
||||
|
||||
#### Scenario: Wiederholbarkeit
|
||||
- **WHEN** dasselbe Programm zweimal ausgeführt wird
|
||||
- **THEN** ist die Ausgabe beide Male identisch
|
||||
|
||||
#### Scenario: Control-Access-Key und Rückwärtstab
|
||||
- **WHEN** ein Formular-Korpus Alt+O für eine OK-Schaltfläche und anschließend Rückwärtstab deklariert
|
||||
- **THEN** weist die Sollausgabe den Click und den Fokuswechsel in korrekter Reihenfolge nach
|
||||
|
||||
### Requirement: Kompatibilitätsnachweis an Fremdprogrammen
|
||||
Die Formularprogramme aus dem öffentlichen Bestand des Vorbilds SHALL
|
||||
sich ohne Diagnosen oder Non-Features übersetzen lassen und bedienbar
|
||||
sein. Jeder entdeckte fehlende Sprach-, Projekt-, Formular- oder
|
||||
Laufzeitpfad MUST implementiert und als Befund festgehalten werden.
|
||||
|
||||
Der Nachweis SHALL den verwendeten öffentlichen Bestand mit Revision und Programmeinstiegen benennen sowie reproduzierbare Eingabesequenzen und sichtbare Zustandsänderungen führen. Ein bloß erfolgreiches Starten MUST NOT als Bediennachweis gelten.
|
||||
|
||||
#### Scenario: Fremdprogramm übersetzt
|
||||
- **WHEN** ein Formularprogramm des öffentlichen Bestands übersetzt wird
|
||||
- **THEN** entstehen keine Diagnosen und das Programm ist bedienbar
|
||||
|
||||
#### Scenario: Bediennachweis ist wiederholbar
|
||||
- **WHEN** ein dokumentiertes Fremdprogramm erneut mit seiner Prüfsequenz ausgeführt wird
|
||||
- **THEN** werden dieselben relevanten Formularzustände beziehungsweise Abschlussereignisse erreicht
|
||||
@@ -0,0 +1,22 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Compile-Zeit-Abweisung deklarierter Non-Features
|
||||
Die Semantik MUST jede Verwendung der in docs/sprachreferenz.md
|
||||
(„Abweichungen") deklarierten Non-Features — Hardware-Nähe (`PEEK`,
|
||||
`POKE`, `INP`, `OUT`, `WAIT`, `BLOAD`/`BSAVE`, `VARPTR`-Familie, `CALL
|
||||
ABSOLUTE`/`INTERRUPT`, `IOCTL`), `CHAIN`, die weiterhin ausgeschlossenen Grafik-Anweisungen (`PSET`,
|
||||
`PRESET`, `CIRCLE`, `DRAW`, Grafik-`GET`/`PUT`, `PALETTE`, `WINDOW`,
|
||||
`PMAP`, `PCOPY`, `POINT`), `SOUND`/
|
||||
`PLAY` und die Event-Geräte `COM`/`PEN`/`PLAY`/`STRIG` — zur Compile-Zeit
|
||||
mit der Meldung „Feature unavailable" abweisen. Dokumentierte Features
|
||||
außerhalb dieser Liste MUST NOT abgewiesen werden (Guiding Principle).
|
||||
|
||||
Die dokumentierte Zellenpuffer-Untermenge SCREEN 0–13, LINE, PAINT und VIEW SHALL verfügbar bleiben.
|
||||
|
||||
#### Scenario: Hardware-Zugriff
|
||||
- **WHEN** `POKE 100, 1` oder `x = PEEK(100)` kompiliert wird
|
||||
- **THEN** erscheint die Compile-Diagnose „Feature unavailable"
|
||||
|
||||
#### Scenario: Erlaubte Grafikuntermenge
|
||||
- **WHEN** ein Programm SCREEN 1, LINE, PAINT und VIEW in gültiger Syntax verwendet
|
||||
- **THEN** wird es ohne Feature-unavailable-Diagnose übersetzt
|
||||
@@ -0,0 +1,30 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Automatischer Abgleich Inventar gegen Implementierung
|
||||
Ein automatischer Test SHALL das Inventar gegen den tatsächlichen
|
||||
Implementierungsstand prüfen und bei jeder Divergenz fehlschlagen. Als
|
||||
`implementiert` markierte Elemente MUST im Frontend eine Signatur besitzen
|
||||
und MUST NOT auf einen „nicht verfügbar"-Marker absenken; als `offen`
|
||||
markierte Elemente MUST auf einen solchen Marker absenken oder ohne
|
||||
Signatur sein; als `Non-Feature` markierte Elemente MUST zur Compile-Zeit
|
||||
mit „Feature unavailable" abgewiesen werden. Der Test MUST das
|
||||
divergierende Element, seinen Inventarstatus und den vorgefundenen
|
||||
Zustand benennen.
|
||||
|
||||
Klassentabellen oder deklarierte Signaturnamen allein MUST NOT als Laufzeitnachweis gelten. Für implementierte Elemente SHALL ein erreichbarer Frontend-/Absenkungspfad und ein ausführbares Laufzeitziel geprüft werden. Für deklarierte Ereignisse SHALL ein Test den Auslöser bis zum BASIC-Handler führen.
|
||||
|
||||
#### Scenario: Inventar hinkt der Implementierung hinterher
|
||||
- **WHEN** `LOCATE` implementiert wurde, im Inventar aber noch `offen` steht
|
||||
- **THEN** schlägt der Abgleich fehl und nennt `LOCATE`, Status `offen` und den Befund „implementiert"
|
||||
|
||||
#### Scenario: Inventar behauptet zu viel
|
||||
- **WHEN** ein Eintrag `implementiert` trägt, das Element aber weiterhin auf den „nicht verfügbar"-Marker absenkt
|
||||
- **THEN** schlägt der Abgleich fehl und nennt das Element
|
||||
|
||||
#### Scenario: Abdeckungsstand ist abrufbar
|
||||
- **WHEN** der Abgleich läuft
|
||||
- **THEN** nennt er die Zahl der Einträge je Status als Abdeckungsstand
|
||||
|
||||
#### Scenario: Deklariertes Ereignis ohne Auslöser
|
||||
- **WHEN** ein als implementiert geführtes Formularereignis bei seiner dokumentierten Hosteingabe keinen Handler ausführt
|
||||
- **THEN** schlägt der Abgleich mit Name, Status und fehlendem Auslösepfad fehl
|
||||
@@ -0,0 +1,15 @@
|
||||
## 1. Normtexte
|
||||
|
||||
- [ ] 1.1 Grafik-Abweisung und FRM-Schreibmodi gemäß Delta präzisieren; verifizieren mit tbc check für erlaubte Grafik, benannter Ablehnung weiterhin verbotener Formen und bytegleichem FRM-Rundlauf mit explizitem Default.
|
||||
|
||||
## 2. Nachweise
|
||||
|
||||
- [ ] 2.1 Inventarprüfung gegen erreichbare HIR-/Dispatch-Pfade erweitern; verifizieren mit künstlich offen/implementiert/verboten gesetzten Einträgen und einer fehlenden Runtime-Bindung, jeweils mit Namen und Status im Fehler.
|
||||
- [ ] 2.2 Fehlende FORM_KEYPRESS/FORM_KEYUP-Auslöser ergänzen und registrierte Form-Ereignisse auf realen Quellenpfad prüfen; verifizieren durch BASIC-Handler nach Hosteingabe, nicht durch direktes queue_form_event allein.
|
||||
- [ ] 2.3 Maskierungs- und Formular-Korpus ergänzen; verifizieren mit KEY/UEVENT in ON/OFF/STOP, normal lesbarer STOP-Taste, Control-Access-Key, BackTab, Fokusreihenfolge und zweimal identischer Ausgabe.
|
||||
- [ ] 2.4 Öffentlichen Fremdprogrammbestand in dokumentierter Revision prüfen; verifizieren durch diagnosefreie Übersetzung und reproduzierbare deklarierte Eingabesequenzen bis zu sichtbaren Zustandsänderungen. Nicht vorhandene externe Voraussetzungen im Ergebnisbericht benennen, nicht abhaken.
|
||||
|
||||
## 3. Gesamtabnahme
|
||||
|
||||
- [ ] 3.1 Alle F01–F25 nach Integration der Korrektur-Changes mit Regressionstest oder geklärtem Normtext abgleichen; verifizieren durch separaten Ergebnisreport mit Fundstellen pro F-ID.
|
||||
- [ ] 3.2 Gesamten Korpus und Spec-Validierung ausführen; verifizieren mit cargo test --workspace, openspec validate --all --strict und git diff --check; Golden Files nicht ungeprüft neu aufzeichnen.
|
||||
@@ -20,6 +20,8 @@ liefern. `CLOSE` ohne Argument SHALL alle offenen Dateien schließen,
|
||||
„Bad file name or number", eine fehlende Datei bei `INPUT` Fehler 53
|
||||
„File not found".
|
||||
|
||||
FREEFILE SHALL die gemeinsame Belegung durch sequenzielle, wahlfreie, binäre und ISAM-Bindungen berücksichtigen.
|
||||
|
||||
#### Scenario: Sequenzielles Schreiben und Lesen
|
||||
- **WHEN** eine Datei mit `OPEN "d.txt" FOR OUTPUT AS #1` beschrieben, geschlossen und mit `FOR INPUT` wieder geöffnet wird
|
||||
- **THEN** liefert das Lesen genau die geschriebenen Zeilen
|
||||
@@ -32,6 +34,10 @@ liefern. `CLOSE` ohne Argument SHALL alle offenen Dateien schließen,
|
||||
- **WHEN** `#1` offen ist und `FREEFILE` aufgerufen wird
|
||||
- **THEN** liefert `FREEFILE` eine Nummer, die noch nicht belegt ist
|
||||
|
||||
#### Scenario: ISAM belegt eine Dateinummer
|
||||
- **WHEN** eine ISAM-Tabelle auf #1 und sonst keine Datei offen ist
|
||||
- **THEN** liefert FREEFILE den Wert 2
|
||||
|
||||
### Requirement: Sequenzielle Datenübertragung
|
||||
`PRINT #` SHALL wie `PRINT` formatieren (Druckzonen, Zahlendarstellung mit
|
||||
Vorzeichenspalte), `WRITE #` SHALL Elemente komma-getrennt mit Strings in
|
||||
|
||||
@@ -24,6 +24,26 @@ und `ROLLBACK ALL`. `DELETETABLE` SHALL eine Tabelle samt ihren
|
||||
Indizes entfernen; eine nicht vorhandene Tabelle MUST Fehler 82 „ISAM -
|
||||
Table not found" auslösen.
|
||||
|
||||
Eine weitere Bindung derselben Datenbank innerhalb einer laufenden Transaktion SHALL deren bisherigen Zustand sehen und MUST ohne implizites Festschreiben oder Selbstblockade entstehen.
|
||||
|
||||
OPEN und DELETETABLE SHALL relative Datenbankpfade im aktuellen Arbeitsverzeichnis auflösen und kanonisch auflösbare Aliase derselben Datei gemeinsam verwalten. Bereits offene Bindungen MUST nach CHDIR weiterhin auf ihre ursprünglich geöffnete Datenbank zeigen. DELETETABLE SHALL bei Erfolg alle Bindungen der entfernten Tabelle lösen; weitere Zugriffe über diese Nummern MUST Fehler 52 melden. Eine Rücknahme SHALL die gelöschte Tabelle wiederherstellen, ohne geschlossene Bindungen erneut zu öffnen.
|
||||
|
||||
#### Scenario: Gleicher relativer Name nach Verzeichniswechsel
|
||||
- **WHEN** nach OPEN in Verzeichnis A alle Bindungen geschlossen werden und derselbe relative Name in Verzeichnis B geöffnet wird
|
||||
- **THEN** betreffen Lesen und Schreiben die Datei in B; die Datei in A bleibt unverändert
|
||||
|
||||
#### Scenario: Pfadalias in einer Transaktion
|
||||
- **WHEN** dieselbe vorhandene Datenbank nach BEGINTRANS und INSERT über einen relativen oder absoluten Pfadalias erneut geöffnet wird
|
||||
- **THEN** sieht die neue Bindung die bisherigen Änderungen ohne zweiten Writer; ROLLBACK ALL nimmt die Änderungen zurück
|
||||
|
||||
#### Scenario: Neuanlage über einen noch nicht auflösbaren Symlink
|
||||
- **WHEN** OPEN eine Datenbank über einen Symlink mit bislang fehlender Zieldatei anlegt und die Datenbank danach innerhalb einer Transaktion erneut über Alias oder Zielpfad geöffnet wird
|
||||
- **THEN** teilen alle Bindungen denselben Transaktionszustand; Sicherungspunkt-Rücknahme und ROLLBACK ALL funktionieren über beide Pfade
|
||||
|
||||
#### Scenario: Bindungen nach Tabellenlöschung und Rücknahme
|
||||
- **WHEN** eine mehrfach gebundene Tabelle nach SAVEPOINT mit DELETETABLE gelöscht und anschließend zurückgenommen wird
|
||||
- **THEN** sind die alten Bindungen geschlossen; ein erneutes OPEN sieht wieder die ursprünglichen Sätze und Indizes
|
||||
|
||||
#### Scenario: Tabelle wird beim ersten Öffnen angelegt
|
||||
- **WHEN** eine Datenbank ohne die Tabelle `Kunden` mit `OPEN "db" FOR ISAM KundeTyp "Kunden" AS #1` geöffnet wird
|
||||
- **THEN** entsteht die Tabelle mit den Feldern des Typs und die Dateinummer ist nutzbar
|
||||
@@ -40,6 +60,10 @@ Table not found" auslösen.
|
||||
- **WHEN** `DELETETABLE` für eine nicht vorhandene Tabelle aufgerufen wird
|
||||
- **THEN** tritt Laufzeitfehler 82 auf
|
||||
|
||||
#### Scenario: Weitere Tabelle während einer Transaktion
|
||||
- **WHEN** nach BEGINTRANS und INSERT eine zweite Tabelle derselben Datenbank geöffnet und danach ROLLBACK ALL ausgeführt wird
|
||||
- **THEN** kehrt OPEN zurück; die eingefügten Daten sind nach Wiederöffnung nicht festgeschrieben
|
||||
|
||||
### Requirement: Indexverwaltung
|
||||
`CREATEINDEX` SHALL einen benannten Index über eine oder mehrere Spalten
|
||||
anlegen, wahlweise eindeutig oder mehrdeutig und je Spalte auf- oder
|
||||
@@ -155,6 +179,8 @@ einschließlich aller Indizes dem Zustand zum Bezugspunkt entsprechen und
|
||||
der Cursor als unpositioniert gelten. Eine beim Programmende nicht
|
||||
festgeschriebene Transaktion MUST wirkungslos bleiben.
|
||||
|
||||
Die Rücknahme auf einen Sicherungspunkt SHALL auch seitdem angelegte oder gelöschte Indizes und Tabellenstrukturen einschließlich ihrer Metadaten zurücknehmen. CLOSE einer Bindung MUST die spätere Rücknahme ihrer Änderungen weiterhin erlauben.
|
||||
|
||||
#### Scenario: Rücknahme stellt den Bestand wieder her
|
||||
- **WHEN** nach `BEGINTRANS` Sätze eingefügt und gelöscht werden und dann `ROLLBACK` folgt
|
||||
- **THEN** entspricht der Bestand beim erneuten Durchlauf dem Zustand vor `BEGINTRANS`
|
||||
@@ -167,6 +193,14 @@ festgeschriebene Transaktion MUST wirkungslos bleiben.
|
||||
- **WHEN** nach mehreren Sicherungspunkten `ROLLBACK ALL` ausgeführt wird
|
||||
- **THEN** entspricht der Bestand dem Zustand vor `BEGINTRANS` und die Transaktion ist beendet
|
||||
|
||||
#### Scenario: Indexanlage nach Sicherungspunkt
|
||||
- **WHEN** nach SAVEPOINT ein Index angelegt und auf den Sicherungspunkt zurückgesetzt wird
|
||||
- **THEN** ist der neue Index nicht vorhanden; SETINDEX mit seinem Namen löst Fehler 83 aus
|
||||
|
||||
#### Scenario: Geschlossene Tabelle rücknehmen
|
||||
- **WHEN** eine geänderte Tabellenbindung vor ROLLBACK auf einen Sicherungspunkt geschlossen wird
|
||||
- **THEN** gelingt die Rücknahme und der bei Wiederöffnung sichtbare Satz-/Indexbestand entspricht dem Sicherungspunkt
|
||||
|
||||
### Requirement: Vergleichsreihenfolge für Indizes
|
||||
Textspalten SHALL in Indizes nach einer festgelegten, dokumentierten
|
||||
Sortierordnung geordnet werden. Diese Ordnung MUST dieselbe sein, die der
|
||||
@@ -189,10 +223,16 @@ Fehlers oder eines stillen Abbruchs. Weicht die Bedeutung von `SETMEM`
|
||||
mangels DOS-Speichermodell vom Vorbild ab, MUST die Abweichung in
|
||||
docs/sprachreferenz.md dokumentiert sein.
|
||||
|
||||
DELETE SHALL den benötigten Satz-/Undo-Puffer vor der Änderung prüfen. Bei Fehler 89 MUST Satzbestand, Indexbestand und Cursor unverändert bleiben.
|
||||
|
||||
#### Scenario: Pufferfehler wird benannt
|
||||
- **WHEN** eine Operation den verfügbaren ISAM-Puffer überschreitet
|
||||
- **THEN** tritt Laufzeitfehler 89 auf
|
||||
|
||||
#### Scenario: DELETE bei ausgeschöpftem Puffer
|
||||
- **WHEN** DELETE auf einem gültigen aktuellen Satz bei unzureichendem ISAM-Puffer ausgeführt wird
|
||||
- **THEN** meldet es Fehler 89 ohne den Satz zu löschen, den Cursor zu verschieben oder Undo hinzuzufügen; eine spätere Rücknahme bleibt möglich
|
||||
|
||||
### Requirement: Eigenes Datenbankdateiformat
|
||||
Die Datenbankdatei SHALL ein eigenes, in docs/dateiformate.md
|
||||
beschriebenes Format verwenden und ist bewusst nicht binärkompatibel zu
|
||||
|
||||
30
tests/compat/isamdateinummern.bas
Normal file
30
tests/compat/isamdateinummern.bas
Normal file
@@ -0,0 +1,30 @@
|
||||
' tb-screen: 80x25
|
||||
' tb-tempdir
|
||||
' Alle Zugriffsarten teilen dieselben Nummern, auch nach CLOSE und RESET.
|
||||
TYPE R
|
||||
Nr AS INTEGER
|
||||
END TYPE
|
||||
ON ERROR GOTO Handler
|
||||
OPEN "nummern.isam" FOR ISAM R "R" AS #1
|
||||
PRINT "ISAM="; LTRIM$(STR$(FREEFILE))
|
||||
OPEN "ausgabe.txt" FOR OUTPUT AS #2
|
||||
OPEN "random.dat" FOR RANDOM AS #3 LEN = 8
|
||||
OPEN "binary.dat" FOR BINARY AS #4
|
||||
OPEN "ausgabe.txt" FOR INPUT AS #5
|
||||
PRINT "gemischt="; LTRIM$(STR$(FREEFILE))
|
||||
OPEN "doppelt.txt" FOR OUTPUT AS #1
|
||||
OPEN "nummern.isam" FOR ISAM R "R" AS #2
|
||||
CLOSE #3
|
||||
PRINT "Luecke="; LTRIM$(STR$(FREEFILE))
|
||||
OPEN "nummern.isam" FOR ISAM R "R" AS #3
|
||||
PRINT "wiederbelegt="; LTRIM$(STR$(FREEFILE))
|
||||
CLOSE #1
|
||||
PRINT "ISAM frei="; LTRIM$(STR$(FREEFILE))
|
||||
OPEN "binary.dat" FOR BINARY AS #1
|
||||
PRINT "wiederbelegt="; LTRIM$(STR$(FREEFILE))
|
||||
RESET
|
||||
PRINT "RESET="; LTRIM$(STR$(FREEFILE))
|
||||
END
|
||||
Handler:
|
||||
PRINT "ERR="; LTRIM$(STR$(ERR))
|
||||
RESUME NEXT
|
||||
9
tests/compat/isamdateinummern.out
Normal file
9
tests/compat/isamdateinummern.out
Normal file
@@ -0,0 +1,9 @@
|
||||
ISAM=2
|
||||
gemischt=6
|
||||
ERR=55
|
||||
ERR=55
|
||||
Luecke=3
|
||||
wiederbelegt=6
|
||||
ISAM frei=1
|
||||
wiederbelegt=6
|
||||
RESET=1
|
||||
44
tests/compat/isamidentitaet.bas
Normal file
44
tests/compat/isamidentitaet.bas
Normal file
@@ -0,0 +1,44 @@
|
||||
' tb-screen: 80x25
|
||||
' tb-tempdir
|
||||
' Verzeichniswechsel und Pfadaliase duerfen Datenbanken nicht verwechseln.
|
||||
TYPE R
|
||||
Nr AS INTEGER
|
||||
END TYPE
|
||||
DIM r AS R
|
||||
MKDIR "a"
|
||||
MKDIR "b"
|
||||
CHDIR "a"
|
||||
OPEN "db" FOR ISAM R "R" AS #1
|
||||
r.Nr = 1
|
||||
INSERT #1, r
|
||||
CLOSE
|
||||
CHDIR "../b"
|
||||
OPEN "db" FOR ISAM R "R" AS #1
|
||||
PRINT "B neu="; LTRIM$(STR$(LOF(1)))
|
||||
r.Nr = 2
|
||||
INSERT #1, r
|
||||
CLOSE
|
||||
CHDIR "../a"
|
||||
OPEN "db" FOR ISAM R "R" AS #1
|
||||
PRINT "A="; LTRIM$(STR$(LOF(1)))
|
||||
BEGINTRANS
|
||||
r.Nr = 3
|
||||
INSERT #1, r
|
||||
OPEN "./db" FOR ISAM R "R" AS #2
|
||||
PRINT "Alias="; LTRIM$(STR$(LOF(2)))
|
||||
s% = SAVEPOINT
|
||||
DELETETABLE "../a/db", "R"
|
||||
PRINT "frei="; LTRIM$(STR$(FREEFILE))
|
||||
ROLLBACK s%
|
||||
OPEN "db" FOR ISAM R "R" AS #1
|
||||
PRINT "restauriert="; LTRIM$(STR$(LOF(1)))
|
||||
ROLLBACK ALL
|
||||
PRINT "A rollback="; LTRIM$(STR$(LOF(1)))
|
||||
CLOSE
|
||||
CHDIR "../b"
|
||||
OPEN "db" FOR ISAM R "R" AS #1
|
||||
MOVEFIRST #1
|
||||
RETRIEVE #1, r
|
||||
PRINT "B Nr="; LTRIM$(STR$(r.Nr))
|
||||
CLOSE
|
||||
CHDIR ".."
|
||||
7
tests/compat/isamidentitaet.out
Normal file
7
tests/compat/isamidentitaet.out
Normal file
@@ -0,0 +1,7 @@
|
||||
B neu=0
|
||||
A=1
|
||||
Alias=2
|
||||
frei=1
|
||||
restauriert=2
|
||||
A rollback=1
|
||||
B Nr=2
|
||||
27
tests/compat/isamopen.bas
Normal file
27
tests/compat/isamopen.bas
Normal file
@@ -0,0 +1,27 @@
|
||||
' tb-screen: 80x25
|
||||
' tb-tempdir
|
||||
' Weitere Bindungen sehen dieselbe Transaktion; CLOSE schreibt nicht fest.
|
||||
TYPE R
|
||||
Nr AS INTEGER
|
||||
END TYPE
|
||||
DIM r AS R
|
||||
ON ERROR GOTO Handler
|
||||
OPEN "open.isam" FOR ISAM R "Alt" AS #1
|
||||
BEGINTRANS
|
||||
r.Nr = 1
|
||||
INSERT #1, r
|
||||
OPEN "open.isam" FOR ISAM R "Alt" AS #2
|
||||
PRINT "sichtbar="; LTRIM$(STR$(LOF(2)))
|
||||
OPEN "open.isam" FOR ISAM R "Neu" AS #3
|
||||
INSERT #3, r
|
||||
CLOSE
|
||||
ROLLBACK ALL
|
||||
OPEN "open.isam" FOR ISAM R "Alt" AS #1
|
||||
PRINT "nach Ruecknahme="; LTRIM$(STR$(LOF(1)))
|
||||
DELETETABLE "open.isam", "Neu"
|
||||
CLOSE
|
||||
PRINT "fertig"
|
||||
END
|
||||
Handler:
|
||||
PRINT "ERR="; LTRIM$(STR$(ERR))
|
||||
RESUME NEXT
|
||||
4
tests/compat/isamopen.out
Normal file
4
tests/compat/isamopen.out
Normal file
@@ -0,0 +1,4 @@
|
||||
sichtbar=1
|
||||
nach Ruecknahme=0
|
||||
ERR=82
|
||||
fertig
|
||||
54
tests/compat/isamstruktur.bas
Normal file
54
tests/compat/isamstruktur.bas
Normal file
@@ -0,0 +1,54 @@
|
||||
' tb-screen: 80x25
|
||||
' tb-tempdir
|
||||
' Verschachtelte Sicherungspunkte nehmen Inhalt, Indizes und Tabellen zurueck.
|
||||
TYPE R
|
||||
Nr AS INTEGER
|
||||
END TYPE
|
||||
DIM r AS R
|
||||
ON ERROR GOTO Handler
|
||||
OPEN "struktur.isam" FOR ISAM R "R" AS #1
|
||||
CREATEINDEX #1, "Nr", 1, "Nr"
|
||||
r.Nr = 1
|
||||
INSERT #1, r
|
||||
BEGINTRANS
|
||||
a% = SAVEPOINT
|
||||
CREATEINDEX #1, "Neu", 0, "Nr"
|
||||
b% = SAVEPOINT
|
||||
DELETEINDEX #1, "Nr"
|
||||
INSERT #1, r
|
||||
CLOSE #1
|
||||
ROLLBACK b%
|
||||
OPEN "struktur.isam" FOR ISAM R "R" AS #1
|
||||
PRINT "Saetze="; LTRIM$(STR$(LOF(1)))
|
||||
SETINDEX #1, "Neu"
|
||||
PRINT "Index="; GETINDEX$(1)
|
||||
INSERT #1, r
|
||||
CLOSE
|
||||
DELETETABLE "struktur.isam", "R"
|
||||
OPEN "struktur.isam" FOR ISAM R "R" AS #1
|
||||
r.Nr = 9
|
||||
INSERT #1, r
|
||||
CLOSE
|
||||
ROLLBACK a%
|
||||
OPEN "struktur.isam" FOR ISAM R "R" AS #1
|
||||
SETINDEX #1, "Neu"
|
||||
SETINDEX #1, "Nr"
|
||||
RETRIEVE #1, r
|
||||
PRINT "wiederhergestellt="; LTRIM$(STR$(r.Nr))
|
||||
c% = SAVEPOINT
|
||||
r.Nr = 2
|
||||
UPDATE #1, r
|
||||
DELETE #1
|
||||
INSERT #1, r
|
||||
CLOSE
|
||||
ROLLBACK c%
|
||||
COMMITTRANS
|
||||
OPEN "struktur.isam" FOR ISAM R "R" AS #1
|
||||
SETINDEX #1, "Nr"
|
||||
RETRIEVE #1, r
|
||||
PRINT "nach COMMIT="; LTRIM$(STR$(r.Nr))
|
||||
CLOSE
|
||||
END
|
||||
Handler:
|
||||
PRINT "ERR="; LTRIM$(STR$(ERR))
|
||||
RESUME NEXT
|
||||
6
tests/compat/isamstruktur.out
Normal file
6
tests/compat/isamstruktur.out
Normal file
@@ -0,0 +1,6 @@
|
||||
Saetze=1
|
||||
Index=Neu
|
||||
ERR=86
|
||||
ERR=83
|
||||
wiederhergestellt=1
|
||||
nach COMMIT=1
|
||||
65
tests/support/prozessfrist.rs
Normal file
65
tests/support/prozessfrist.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
//! Harte Frist auch für blockierende Bibliotheksaufrufe im Testprozess.
|
||||
use std::sync::mpsc::{self, Sender};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Prozessfrist(Sender<()>);
|
||||
|
||||
impl Prozessfrist {
|
||||
pub fn neu(name: &str, dauer: Duration) -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let name = name.to_string();
|
||||
std::thread::spawn(move || {
|
||||
if matches!(rx.recv_timeout(dauer), Err(mpsc::RecvTimeoutError::Timeout)) {
|
||||
eprintln!("Prozessfrist überschritten: {name}");
|
||||
// Auch ein in redb blockierter Test lässt sich so sicher beenden.
|
||||
std::process::exit(124);
|
||||
}
|
||||
});
|
||||
Self(tx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Prozessfrist {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.0.send(());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frist_beendet_blockierten_testprozess() {
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::Instant;
|
||||
|
||||
const MARKER: &str = "TB_TEST_FRIST_KIND";
|
||||
if std::env::var_os(MARKER).is_some() {
|
||||
let _frist = Prozessfrist::neu("absichtlich blockiert", Duration::from_millis(100));
|
||||
loop {
|
||||
std::thread::park();
|
||||
}
|
||||
}
|
||||
let mut child = Command::new(std::env::current_exe().unwrap())
|
||||
.arg("frist_beendet_blockierten_testprozess")
|
||||
.arg("--nocapture")
|
||||
.current_dir(std::env::temp_dir())
|
||||
.env(MARKER, "1")
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let start = Instant::now();
|
||||
loop {
|
||||
if child.try_wait().unwrap().is_some() {
|
||||
break;
|
||||
}
|
||||
if start.elapsed() > Duration::from_secs(5) {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
panic!("Prozessfrist hat den blockierten Test nicht beendet");
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
let out = child.wait_with_output().unwrap();
|
||||
assert_eq!(out.status.code(), Some(124), "{out:?}");
|
||||
assert!(String::from_utf8_lossy(&out.stderr).contains("absichtlich blockiert"));
|
||||
}
|
||||
Reference in New Issue
Block a user