Laufzeit-Eingabe und Zonenzustand korrigieren und Change archivieren
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
## 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).
|
||||
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
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
//! Unabhängige Reviewproben, 06.09.2026. Als Rust-Testbinary gegen tb-vm/tb-runtime.
|
||||
use std::{path::PathBuf, rc::Rc};
|
||||
use tb_runtime::{
|
||||
builtins::{builtin_table, ids, RtState},
|
||||
datetime::Zeitzone,
|
||||
host::{CaptureHost, Ereignis},
|
||||
value::Value,
|
||||
};
|
||||
use tb_vm::{
|
||||
bytecode::CompiledModule,
|
||||
interp::{RunEvent, Vm},
|
||||
};
|
||||
|
||||
struct Temp(PathBuf);
|
||||
impl Temp {
|
||||
fn new(name: &str) -> Self {
|
||||
let p =
|
||||
std::env::temp_dir().join(format!("tb_runtime_review_{name}_{}", std::process::id()));
|
||||
std::fs::create_dir_all(&p).unwrap();
|
||||
Self(p)
|
||||
}
|
||||
fn file(&self) -> String {
|
||||
self.0.join("data.txt").to_str().unwrap().to_owned()
|
||||
}
|
||||
}
|
||||
impl Drop for Temp {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
fn text(vm: &Vm, name: &str) -> String {
|
||||
match vm.inspect(name).unwrap() {
|
||||
Value::Str(s) => s.to_string(),
|
||||
other => panic!("{other:?}"),
|
||||
}
|
||||
}
|
||||
fn expr(s: &str) -> String {
|
||||
let escaped = s
|
||||
.replace('"', "\"\"")
|
||||
.replace('\r', "\" + CHR$(13) + \"")
|
||||
.replace('\n', "\" + CHR$(10) + \"")
|
||||
.replace('\0', "\" + CHR$(0) + \"");
|
||||
format!("\"{escaped}\"")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_input_roundtrip_string_matrix() {
|
||||
let tmp = Temp::new("roundtrip");
|
||||
let p = tmp.file();
|
||||
let parts = ["", "a", " ", "\t", "\"", ",", "\r\n", "ä😀", "\0", "\u{a0}"];
|
||||
for a in parts {
|
||||
for b in parts {
|
||||
let expected = format!("{a}{b}");
|
||||
let src = format!("OPEN \"{p}\" FOR OUTPUT AS #1\nWRITE #1, {}, \"next\"\nCLOSE\nOPEN \"{p}\" FOR INPUT AS #1\nINPUT #1, got$\nINPUT #1, next$\nEND", expr(&expected));
|
||||
let mut vm = Vm::new(tb_vm::compile_source("REVIEW", &src).unwrap());
|
||||
assert_eq!(
|
||||
vm.run(&mut CaptureHost::default()),
|
||||
RunEvent::Ended,
|
||||
"{expected:?}"
|
||||
);
|
||||
assert_eq!(text(&vm, "got"), expected);
|
||||
assert_eq!(text(&vm, "next"), "next");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_nine_file_read_transitions() {
|
||||
let tmp = Temp::new("transitions");
|
||||
let p = tmp.file();
|
||||
for first in 0..3 {
|
||||
for second in 0..3 {
|
||||
std::fs::write(&p, "ä,😀\r\nZ").unwrap();
|
||||
let statement = |mode, variable| match mode {
|
||||
0 => format!("INPUT #1, {variable}$"),
|
||||
1 => format!("{variable}$ = INPUT$(1, #1)"),
|
||||
_ => format!("LINE INPUT #1, {variable}$"),
|
||||
};
|
||||
let src = format!(
|
||||
"OPEN \"{p}\" FOR INPUT AS #1\n{}\n{}\nEND",
|
||||
statement(first, "a"),
|
||||
statement(second, "b")
|
||||
);
|
||||
let mut vm = Vm::new(tb_vm::compile_source("REVIEW", &src).unwrap());
|
||||
assert_eq!(vm.run(&mut CaptureHost::default()), RunEvent::Ended);
|
||||
assert_eq!(text(&vm, "a"), if first == 2 { "ä,😀" } else { "ä" });
|
||||
let expected = match (first, second) {
|
||||
(0, _) => "😀",
|
||||
(1, 0) => "",
|
||||
(1, 1) => ",",
|
||||
(1, 2) => ",😀",
|
||||
_ => "Z",
|
||||
};
|
||||
assert_eq!(text(&vm, "b"), expected, "transition {first}/{second}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_mk_cv_pair_preserves_target_values_and_width() {
|
||||
let mut st = RtState::default();
|
||||
let mut host = CaptureHost::default();
|
||||
let cases = [
|
||||
(0, Value::Int(i16::MIN)),
|
||||
(0, Value::Int(i16::MAX)),
|
||||
(1, Value::Lng(i32::MIN)),
|
||||
(1, Value::Lng(i32::MAX)),
|
||||
(2, Value::Sng(-0.0)),
|
||||
(2, Value::Sng(f32::MAX)),
|
||||
(3, Value::Dbl(-0.0)),
|
||||
(3, Value::Dbl(f64::MIN_POSITIVE)),
|
||||
(4, Value::Cur(i64::MIN)),
|
||||
(4, Value::Cur(i64::MAX)),
|
||||
];
|
||||
for (kind, value) in cases {
|
||||
let encoded = builtin_table()[ids::MK_S as usize](
|
||||
&mut st,
|
||||
&mut host,
|
||||
&mut [value.clone(), Value::Lng(kind)],
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let decoded = builtin_table()[ids::CV_F as usize](
|
||||
&mut st,
|
||||
&mut host,
|
||||
&mut [encoded.clone(), Value::Lng(kind)],
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
match (&value, &decoded) {
|
||||
(Value::Int(a), Value::Int(b)) => assert_eq!(a, b),
|
||||
(Value::Lng(a), Value::Lng(b)) => assert_eq!(a, b),
|
||||
(Value::Cur(a), Value::Cur(b)) => assert_eq!(a, b),
|
||||
(Value::Sng(a), Value::Sng(b)) => assert_eq!(a.to_bits(), b.to_bits()),
|
||||
(Value::Dbl(a), Value::Dbl(b)) => assert_eq!(a.to_bits(), b.to_bits()),
|
||||
_ => panic!("{value:?} -> {decoded:?}"),
|
||||
}
|
||||
let Value::Str(encoded) = encoded else {
|
||||
panic!()
|
||||
};
|
||||
let len = encoded.chars().count();
|
||||
for wrong in [
|
||||
encoded.chars().take(len - 1).collect::<String>(),
|
||||
format!("{encoded}\0"),
|
||||
] {
|
||||
let err = builtin_table()[ids::CV_F as usize](
|
||||
&mut st,
|
||||
&mut host,
|
||||
&mut [Value::Str(Rc::from(wrong)), Value::Lng(kind)],
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.0, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timezoneknown_survives_tbc_and_source_failure_recovery() {
|
||||
thread_local! { static ZONE: std::cell::Cell<Option<i32>> = const { std::cell::Cell::new(None) }; }
|
||||
let module = tb_vm::compile_source(
|
||||
"REVIEW",
|
||||
"a% = TIMEZONEKNOWN\nSTOP\nb% = TIMEZONEKNOWN\nSTOP\nc% = TIMEZONEKNOWN\nEND",
|
||||
)
|
||||
.unwrap();
|
||||
let mut vm = Vm::new(CompiledModule::from_tbc(&module.to_tbc()).unwrap());
|
||||
vm.rt.zonen_ermittler = |_| ZONE.get();
|
||||
let mut host = CaptureHost::default();
|
||||
for (name, zone, expected) in [("a", Some(3600), -1), ("b", None, 0), ("c", Some(0), -1)] {
|
||||
ZONE.set(zone);
|
||||
let ev = vm.run(&mut host);
|
||||
assert!(matches!(ev, RunEvent::Stopped { .. } | RunEvent::Ended));
|
||||
assert!(matches!(vm.inspect(name), Some(Value::Int(n)) if n == expected));
|
||||
assert_eq!(vm.rt.zeitzone, Zeitzone::System);
|
||||
assert_eq!(vm.rt.uhr_offset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successive_function_keys_are_split_in_order() {
|
||||
let module = tb_vm::compile_source(
|
||||
"REVIEW",
|
||||
"a$ = INPUT$(3)\nb$ = INPUT$(1)\nc$ = INPUT$(1)\nEND",
|
||||
)
|
||||
.unwrap();
|
||||
let mut vm = Vm::new(module);
|
||||
let mut host = CaptureHost::default();
|
||||
for s in ["\0;", "\0<", "😀"] {
|
||||
host.ereignis(Ereignis::Taste(s.into(), 0));
|
||||
}
|
||||
assert_eq!(vm.run(&mut host), RunEvent::Ended);
|
||||
assert_eq!(text(&vm, "a"), "\0;\0");
|
||||
assert_eq!(text(&vm, "b"), "<");
|
||||
assert_eq!(text(&vm, "c"), "😀");
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# Verification Report: laufzeit-eingabe-und-systemstatus
|
||||
|
||||
Erneute Prüfung am 06.09.2026. Grundlage ist der aktuelle, noch uncommittete
|
||||
Arbeitsbaum auf `815825dde76eb8f4b48cb27d83f21732afe707c2`. Die Aussage aus
|
||||
`verification.md` wurde gegen Proposal, Design, Tasks, alle vier Delta-Specs,
|
||||
die tatsächlichen Aufrufpfade und zusätzlich erstellte Reviewproben geprüft.
|
||||
|
||||
## Summary
|
||||
|
||||
| Dimension | Ergebnis |
|
||||
|---|---|
|
||||
| Vollständigkeit | 6/6 Tasks abgeschlossen; 6 Requirements mit 21 Szenarien abgeglichen |
|
||||
| Korrektheit | 463 Workspace-Tests und 5 unabhängige Reviewproben bestanden |
|
||||
| Kohärenz | D1–D4 eingehalten; gemeinsame Dateileseposition, Codepoint-/Byte-Trennung, Pfadfehler und aktuelle Zonenauflösung |
|
||||
| CRITICAL | 0 |
|
||||
| WARNING | 0 |
|
||||
| SUGGESTION | 0 |
|
||||
|
||||
**Keine offenen Befunde im geprüften Change.** Die vorhandene
|
||||
[Szenariozuordnung](verification.md#szenarien-und-ausführbare-nachweise)
|
||||
ist durch die erneute Codeprüfung und den aktuellen Testlauf bestätigt.
|
||||
|
||||
## Zusätzliche unabhängige Proben
|
||||
|
||||
[review-probes.rs](review-probes.rs) wurde separat mit `rustc --test` gegen
|
||||
die aktuell gebauten `tb_vm`- und `tb_runtime`-Bibliotheken aus
|
||||
`target/debug/deps` übersetzt und ausgeführt. Alle fünf Tests bestanden.
|
||||
Die Proben wurden nicht aus den vorhandenen Tests kopiert.
|
||||
|
||||
| Probe | Abdeckung / Ergebnis |
|
||||
|---|---|
|
||||
| `write_input_roundtrip_string_matrix` | 100 Kombinationen aus leeren Strings, ASCII, Leerzeichen, Tab, Anführungszeichen, Komma, CRLF, ä/Emoji, NUL und geschütztem Leerzeichen: WRITE und zwei folgende INPUT-#-Aufrufe erhalten Inhalt und nächstes Feld exakt |
|
||||
| `all_nine_file_read_transitions` | Sämtliche 3×3 Übergänge zwischen INPUT #, INPUT$ und LINE INPUT # auf derselben UTF-8-/CRLF-Datei liefern den erwarteten Cursorfortschritt |
|
||||
| `every_mk_cv_pair_preserves_target_values_and_width` | INTEGER-/LONG-/CURRENCY-Grenzen, negative Null und Fließkomma-Grenzwerte bleiben typ- und bitgetreu; jeweils zu kurze und zu lange Argumente erzeugen Fehler 5 |
|
||||
| `timezoneknown_survives_tbc_and_source_failure_recovery` | Neuer Builtin über Kompilat-Speicherung und -Laden ausführbar; derselbe Resolver wechselt bekannt → ausgefallen → bekannte UTC. BASIC liefert INTEGER -1/0/-1; Zonenquelle und Programmversatz bleiben unverändert |
|
||||
| `successive_function_keys_are_split_in_order` | Zwei aufeinanderfolgende erweiterte Tasten und Emoji werden durch INPUT$(3), INPUT$(1), INPUT$(1) ohne Verlust oder Umordnung gelesen |
|
||||
|
||||
Statisch zusätzlich abgeglichen: gemeinsame Leser-/SEEK-/EOF-Aufrufpfade,
|
||||
Binärzugriff nach Einführung von BufReader, beide INPUT$-Dispatchwege,
|
||||
Trap-Behandlung bereits angenommener Reste, alle fünf CV-Zieltypen,
|
||||
OPEN/KILL/NAME/MKDIR-Fehlerzuordnung sowie Frontend/HIR/Builtin-ID/CLI
|
||||
und alle Zeitfunktionen einschließlich der Zuweisungen an DATE$/TIME$.
|
||||
Die neue Builtin-ID wird angehängt; bestehende IDs werden nicht verschoben.
|
||||
|
||||
## Erneut ausgeführte Abnahme
|
||||
|
||||
- `cargo test --workspace --quiet`: **463 bestanden, 0 fehlgeschlagen,
|
||||
1 ignoriert**; der Golden-Generator blieb ignoriert.
|
||||
- Separate Reviewproben: **5 bestanden, 0 fehlgeschlagen**.
|
||||
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`: bestanden.
|
||||
- `cargo fmt --all -- --check`: bestanden.
|
||||
- `git diff --check`: bestanden vor dem Staging. Die abschließende
|
||||
Prüfung des gestagten Changes mit `git diff --cached --check -- .
|
||||
':(exclude)tests/compat/*.out'` besteht ebenfalls. Die neuen `.out`-Dateien
|
||||
enthalten absichtlich signifikante PRINT-Leerzeichen am Zeilenende.
|
||||
- `openspec validate laufzeit-eingabe-und-systemstatus --strict`: bestanden.
|
||||
- `openspec status` und `openspec instructions apply`: Planung vollständig,
|
||||
6/6 Tasks, `all_done`. Die Häkchen wurden gegen Code und Tests geprüft.
|
||||
- Keine bestehenden Korpus-Sollausgaben verändert; die drei neuen
|
||||
Eingabe-/Zonenprogramme bestehen im regulären Korpuslauf jeweils zweimal.
|
||||
|
||||
Während dieser Verifikation wurden nur dieser Bericht und die zusätzlichen
|
||||
Reviewproben angelegt. Produktcode, bestehende Tests und Sollausgaben blieben
|
||||
unverändert. Headless auf macOS geprüft; kein neuer Windows-/Linux-Lauf oder
|
||||
interaktiver Fremdprogrammvergleich. Die Prüfung betrifft diesen Change und
|
||||
ersetzt nicht den noch offenen übergreifenden Spezifikationsabgleich.
|
||||
|
||||
Alle Prüfungen bestanden. Der Change ist zur Synchronisierung und
|
||||
anschließenden Archivierung bereit.
|
||||
|
||||
## Archivabschluss
|
||||
|
||||
Am 06.09.2026 wurden alle sechs Delta-Requirements mit den vier Hauptspecs
|
||||
synchronisiert und vollständig abgeglichen. Alle 15 Hauptspecs bestehen
|
||||
die strenge Validierung. Der Change wurde anschließend einschließlich
|
||||
der fünf Reviewproben archiviert.
|
||||
@@ -1,14 +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.
|
||||
- [x] 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.
|
||||
- [x] 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.
|
||||
- [x] 1.3 CV-Zielbreiten exakt prüfen; verifizieren mit allen fünf MK/CV-Paaren und jeweils zu kurzen/passenden/zu langen Argumenten.
|
||||
- [x] 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.
|
||||
- [x] 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.
|
||||
- [x] 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,110 @@
|
||||
# Implementierungs- und Verifikationsnachweis
|
||||
|
||||
Change: `laufzeit-eingabe-und-systemstatus`, Stand 06.09.2026.
|
||||
Grundlage: Arbeitsbaum auf `815825dde76eb8f4b48cb27d83f21732afe707c2`;
|
||||
Proposal, Design, vier Delta-Specs und alle sechs Tasks abgeglichen.
|
||||
|
||||
## Ergebnis
|
||||
|
||||
| Dimension | Ergebnis |
|
||||
|---|---|
|
||||
| Vollständigkeit | 6/6 Tasks, 6/6 Requirements, 21/21 Szenarien abgeglichen |
|
||||
| Korrektheit | Alle zugeordneten Regressionen und Workspace-Tests bestanden |
|
||||
| Kohärenz | D1–D4 umgesetzt; keine zusätzlichen Dependencies |
|
||||
| Offene Befunde | 0 CRITICAL, 0 WARNING, 0 SUGGESTION im Change |
|
||||
|
||||
F14–F17 und F19 sind umgesetzt. Die vier Hauptspecs wurden am 06.09.2026
|
||||
synchronisiert und alle 15 Hauptspecs streng validiert. Der Change ist unter
|
||||
`openspec/changes/archive/2026-09-06-laufzeit-eingabe-und-systemstatus` archiviert.
|
||||
|
||||
## Umsetzung und Design
|
||||
|
||||
- **D1:** `Datei` verwendet einen `BufReader<File>` für den gemeinsamen Cursor.
|
||||
`feld_lesen`, `zeile_lesen`, `zeichen_lesen`, EOF und SEEK erhalten den
|
||||
Lesefortschritt. INPUT # liest einzelne Felder statt vollständiger Zeilen;
|
||||
fehlende Felder erzeugen Fehler 62. Dekodierte Strings werden in der VM
|
||||
nicht erneut von Anführungszeichen oder Rand-Leerraum befreit.
|
||||
- **D2:** INPUT$ zählt UTF-8-Codepoints. Beschädigte oder abgeschnittene
|
||||
UTF-8-Sequenzen führen zu Fehler 57; EOF vor einem geforderten Zeichen
|
||||
zu Fehler 62. Ein bereits angenommener Tastaturrest wird vor weiteren
|
||||
Tasten geliefert und nicht nochmals als Trap-/Forms-Ereignis eingereiht.
|
||||
Direkter Builtin-Dispatch und resumierbare VM-Eingabe verwenden dieselbe
|
||||
Aufteilung. Alle fünf CV-Funktionen prüfen exakte Breite und Bytezeichen.
|
||||
GET/PUT und UTF-32-Recorddarstellung bleiben eigenständige Binärpfade.
|
||||
- **D3:** `fehler_am_pfad` unterscheidet fehlenden Dateieintrag (53) von
|
||||
fehlendem Elternverzeichnis (76) bei OPEN, KILL, NAME und MKDIR.
|
||||
Vorhandene Berechtigungs-/Existenzfehler bleiben erhalten; ein Pfadbestandteil,
|
||||
der kein Verzeichnis ist, wird ebenfalls als Pfadfehler eingeordnet.
|
||||
- **D4:** TIMEZONEKNOWN ist als parameterlose INTEGER-Funktion durch Semantik,
|
||||
HIR, Codegenerator, stabile neue Builtin-ID 156 und Dispatch geführt.
|
||||
`RtState::zeitpunkt` löst Zeit und Status gemeinsam auf. Die Systemquelle
|
||||
bleibt nach Ausfällen aktiv, sodass spätere Erholung sichtbar wird.
|
||||
Tests können die Zonenquelle ersetzen, ohne die Rechnerzone zu verändern.
|
||||
Programmversatz, monotone Ereigniszeit und Betriebssystemuhr werden durch
|
||||
die Statusabfrage nicht verändert. CLI-Startwarnung und Inventar sind angepasst.
|
||||
|
||||
## Szenarien und ausführbare Nachweise
|
||||
|
||||
Testnamen ohne Präfix stehen in `crates/tb-vm/tests/vm.rs`. `runtime` meint
|
||||
`crates/tb-runtime/src/builtins.rs`, `frontend` die Semantik bzw. Inventartests.
|
||||
Die Korpusprogramme liegen in `tests/compat`; der Harness führt jedes zweimal
|
||||
gegen seine festgeschriebene Sollausgabe aus.
|
||||
|
||||
| Requirement / Szenario | Nachweis |
|
||||
|---|---|
|
||||
| Sequenzielle Datenübertragung: WRITE # und INPUT # sind gegenläufig | `input_datei_bewahrt_felder_und_unterscheidet_leer_von_fehlend`; `eingabefortschritt.bas` |
|
||||
| Lesen über das Dateiende | Derselbe Test: leere Datei, fehlendes zweites Feld und EOF; `input_datei_alle_lesearten_teilen_utf8_cursor_und_seek` |
|
||||
| Felder über zwei Aufrufe | `input_datei_bewahrt_felder_und_unterscheidet_leer_von_fehlend`: WRITE a/b, anschließende getrennte Reads |
|
||||
| Zu wenige Felder | Derselbe Test: mit/ohne finales Zeilenende Fehler 62; explizit leere Felder bleiben lesbar |
|
||||
| Record-Konvertierungen: MK und CV sind gegenläufig | `alle_mk_cv_paare_verlangen_exakte_bytezeichenbreite`: alle fünf Paare |
|
||||
| Falsche Argumentlänge | Derselbe Test: jeweils Zielbreite minus ein Zeichen → Fehler 5 |
|
||||
| Zu langes CVI-Argument | Derselbe Test: jeweils Zielbreite plus ein Zeichen → Fehler 5; zusätzlich Nicht-Bytezeichen |
|
||||
| Dateisystem: Rückwärts-Schrägstrich auf Unix | `datei_und_elternpfadfehler_sind_am_sprachzugang_unterscheidbar`: OPEN über Backslash, anschließendes NAME/Lesen/Löschen |
|
||||
| Verzeichnis durchlaufen | Derselbe Test: DIR$ mit Muster und Folgeaufruf; vorhandener Dateikorpus |
|
||||
| Fehlender Pfad | Derselbe Test: MKDIR, NAME-Ziel, CHDIR, RMDIR, DIR$ → 76 |
|
||||
| OPEN mit fehlendem Elternpfad | Derselbe Test: INPUT mit fehlender Datei → 53, fehlendem Elternpfad → 76; zusätzlich OUTPUT/APPEND/BINARY/RANDOM |
|
||||
| Zonenermittlung: Zone nicht ermittelbar | runtime `zonenquelle_ausfall_und_erholung_gelten_fuer_alle_zeitfunktionen`; `timezonefallback.bas` |
|
||||
| Rückfallebene ist erkennbar | `timezoneknown_hat_integer_signatur_und_zeigt_aktuellen_status`; `timezoneknown.bas` und `timezonefallback.bas` |
|
||||
| Rückfall nach Programmstart | Derselbe VM-Test über STOP/Fortsetzung und runtime-Zonenquelle mit Ausfall und Erholung; bekannte UTC liefert -1 |
|
||||
| Signaturen: Element ohne Laufzeitverhalten wird namentlich abgewiesen | Vorhandener frontend-Test `dokumentierte_elemente_werden_namentlich_abgewiesen`; Inventar hat derzeit 0 offene Elemente |
|
||||
| Argumentprüfung ohne Laufzeitverhalten | frontend `builtins_typen`: MKL$() hat benannte Argumentanzahl-Diagnose |
|
||||
| Signatur der Zonenzustandsabfrage | frontend `builtins_typen`: 0 Argumente, INTEGER; TIMEZONEKNOWN(1) benannt abgewiesen; runtime-Test prüft tatsächliches `Value::Int` |
|
||||
| Tastatur: INKEY$ bei leerem Puffer | `inkey_ohne_taste_ist_leer` |
|
||||
| Sondertaste als zwei Zeichen | `inkey_liefert_zeichen_und_sondertaste` |
|
||||
| Sondertastenfolge in zwei Reads | `input_s_teilt_sondertasten_und_bewahrt_rest_fuer_inkey`; `eingabefortschritt.bas`; direkter runtime-Dispatch |
|
||||
| Unicode aus Datei | `input_datei_alle_lesearten_teilen_utf8_cursor_und_seek`: ä/Emoji, INPUT und BINARY |
|
||||
|
||||
Zusätzliche Randfälle: CR/LF/CRLF, Leerzeilen, eingebettete Kommas,
|
||||
verdoppelte Anführungszeichen und Zeilenumbrüche, Null-Länge, negative Länge,
|
||||
ungültiges UTF-8, falscher Dateimodus, SEEK nach EOF und gemischte Lesearten.
|
||||
`unicode_puffergrenzen_und_binaere_records_bleiben_getrennt` prüft UTF-8 an
|
||||
8-KiB-Puffergrenzen sowie GET/PUT zwischen INPUT$-Aufrufen.
|
||||
`sondertastenrest_wird_nicht_erneut_getrappt` prüft F1 unter KEY STOP/ON.
|
||||
Die bestehenden Programmversatz- und Ereignistests bestehen weiterhin.
|
||||
|
||||
## Abnahme
|
||||
|
||||
- `cargo test --workspace --quiet`: **463 bestanden, 0 fehlgeschlagen,
|
||||
1 ignoriert**. Der ignorierte Golden-Generator wurde nicht ausgeführt.
|
||||
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`: bestanden.
|
||||
- `cargo fmt --all -- --check`: bestanden.
|
||||
- `git diff --check`: bestanden vor dem Staging. Die abschließende
|
||||
Prüfung des gestagten Changes mit `git diff --cached --check -- .
|
||||
':(exclude)tests/compat/*.out'` besteht ebenfalls. Die neuen `.out`-Dateien
|
||||
enthalten absichtlich signifikante PRINT-Leerzeichen am Zeilenende.
|
||||
- `openspec validate laufzeit-eingabe-und-systemstatus --strict`: bestanden.
|
||||
- Bestehende `tests/compat/*.out` unverändert; drei neue Programme mit
|
||||
vorab festgelegten Sollausgaben ergänzt. Die Inventar-Abdeckungszahl
|
||||
berücksichtigt die neue Erweiterung: 795 implementiert, 0 offen,
|
||||
50 Non-Features, insgesamt 845 Einträge.
|
||||
|
||||
Die anfänglichen Testfehler (doppelte Quote-Auswertung, fehlende Korpus-
|
||||
Rückfalldirektive, Test-Namensauflösung, Inventar-Gesamtzahl) sind behoben;
|
||||
der abschließende Workspace-Lauf ist grün. Keine Tests oder vorhandenen
|
||||
Sollausgaben wurden zum Übergehen dieser Fehler abgeschwächt.
|
||||
|
||||
Geprüft wurde headless auf macOS. Es erfolgte kein neuer interaktiver
|
||||
Fremdprogrammvergleich und keine Windows-/Linux-Ausführung. Die ergänzenden
|
||||
Korpusfälle belegen den erklärten TerminalBasic-Vertrag, keine historische
|
||||
Bytekompatibilität. Der verbleibende Change
|
||||
`spezifikationsabgleich-und-regressionsnachweise` bleibt eigenständig.
|
||||
@@ -46,6 +46,8 @@ 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
|
||||
@@ -54,6 +56,14 @@ Dateiende hinaus MUST Laufzeitfehler 62 „Input past end of file" auslösen.
|
||||
- **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: Wahlfreier Zugriff mit Record-Puffern
|
||||
`OPEN … FOR RANDOM … LEN = n` SHALL Datensätze fester Länge bereitstellen.
|
||||
`GET` und `PUT` SHALL einen Datensatz an einer 1-basierten Recordnummer
|
||||
@@ -83,6 +93,8 @@ 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
|
||||
@@ -91,6 +103,10 @@ erfolgen und in der Sprachreferenz unter „Abweichungen" stehen.
|
||||
- **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: Festtextfelder als UTF-32 mit fester Recordlänge
|
||||
Feste Strings (`STRING * n`) in Recordpuffern SHALL als UTF-32 gespeichert
|
||||
werden — 4 Bytes je Zeichen, damit `STRING * n` stets genau `4 * n` Bytes
|
||||
@@ -165,6 +181,10 @@ Pfade Fehler 76 „Path not found".
|
||||
- **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
|
||||
|
||||
### Requirement: LPRINT als Druckausgabe
|
||||
`LPRINT` und `LPRINT USING` SHALL wie `PRINT` formatieren und in einen
|
||||
Druckerkanal schreiben. `LPOS` SHALL die aktuelle Spalte im
|
||||
|
||||
@@ -265,6 +265,8 @@ 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
|
||||
@@ -273,6 +275,10 @@ MUST NOT vorkommen.
|
||||
- **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
|
||||
|
||||
### Requirement: Programmversatz wirkt auf die Ortszeit
|
||||
`DATE$ = text$` und `TIME$ = text$` SHALL weiterhin nicht die Systemuhr
|
||||
stellen, sondern einen programmeigenen Versatz setzen. Dieser Versatz
|
||||
|
||||
@@ -139,6 +139,8 @@ 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"
|
||||
@@ -147,6 +149,10 @@ Dateisystem-Funktionen `CURDIR$`, `DIR$`, `CHDRIVE`, sowie `LPOS`,
|
||||
- **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
|
||||
|
||||
### Requirement: Modulweites ON ERROR ist aus Prozeduren ansprechbar
|
||||
`ON ERROR GOTO label` ohne `LOCAL` SHALL einen modulweiten Fehlerhandler
|
||||
setzen, dessen Sprungziel auch dann aufgelöst wird, wenn die Anweisung in
|
||||
|
||||
@@ -154,6 +154,8 @@ 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
|
||||
@@ -162,6 +164,14 @@ lesen und dabei blockieren, ohne sie am Bildschirm zu wiederholen.
|
||||
- **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
|
||||
|
||||
### Requirement: Keine feste Bildschirmgröße im Verhalten
|
||||
Kein beobachtbares Verhalten SHALL eine feste Spalten- oder Zeilenzahl
|
||||
voraussetzen. Wo die Referenz des Vorbilds von 80×25 spricht, ist stets
|
||||
|
||||
Reference in New Issue
Block a user