Phase 3 (Kern): Laufzeitbibliothek, Bildschirm und Datei-E/A
Setzt den OpenSpec-Change phase-3-runtime-bildschirm um (77/77 Aufgaben). Abdeckung laut Inventar: 195 implementiert, 37 offen, 53 Non-Feature. Vollstaendigkeits-Inventar - docs/inventar.md mit 285 Eintraegen aus den Themenlisten von bas7advr.hlp und qb45advr.hlp, je mit Status und Fundstelle - crates/tb-frontend/tests/inventar.rs haelt die Tabelle in beide Richtungen gegen den Code; der Abdeckungsstand kann nicht veralten Bruchschritt (Puffer, Host, Korpus) - Cell/TextScreen ziehen ratatui-frei nach tb-runtime::screen; tb-ui behaelt Farbabbildung, Widget und den neuen Terminal-Host - Host wechselt vom Zeichenstrom auf Anzeige des Zellenpuffers plus Ereignisse (Taste, Groessenaenderung, Abbruch) - Korpusvergleich auf getrimmten Bildschirm-Snapshot; die acht bestehenden Sollausgaben blieben dabei unveraendert Groessenunabhaengigkeit - 80x25 ist nirgends mehr eine feste Grenze; Groessenaenderungen waehrend der Ausfuehrung werden zugestellt (Inhalt oben links erhalten, Cursor und VIEW PRINT geklemmt) - Korpusnachweis: dasselbe Programm bei 80x25 und 120x40 mit je eigener Sollausgabe, plus ein Programm mit Groessenwechsel mitten im Lauf Bibliothek - Breite Unicode-Zeichen belegen zwei Zellen (Cursor, POS, Randumbruch) - Bildschirm: CLS, COLOR, LOCATE, WIDTH, VIEW PRINT, SCREEN, CSRLIN, POS - Tastatur: INKEY$, INPUT$, Funktionstasten-Makros (KEY n / LIST / ON) - PRINT USING, LPRINT USING, FORMAT$, SetFormatCC - Mathematik mit kompatiblem PRNG (gleiche Saat, gleiche Folge) - Datum und Zeit mit Serienwerten ab 1899 - Finanzmathematik: FV#, PV#, Pmt#, IPmt#, PPmt#, NPer#, Rate#, NPV#, IRR#, MIRR#, SLN#, SYD#, DDB# - Datei-E/A: sequenziell, RANDOM (Recordpuffer und UDT-Variablen), BINARY, FIELD/LSET/RSET, Statusfunktionen, Dateisystem, MK$/CV - System: ENVIRON, FRE, CLEAR, TRON/TROFF, STACK, ERDEV, ERR-Anweisung Altlasten aus Phase 2 - ON ERROR GOTO auf Modulebene ist aus Prozeduren ansprechbar (prozeduruebergreifender Fixup im Codegenerator) - DATA behaelt seinen Rohtext (Gross-/Kleinschreibung, innerer Leerraum) - Die vier TODO-verify-Zellen der Konvertierungsmatrix sind aufgeloest Vom Inventar aufgedeckte Fehler - Zwoelf Non-Features wies der Compiler entgegen der Phase-1-Spec nicht ab (CALLS, SSEG, POINT, VIEW, COM, PEN, STRIG, STICK, die String*-Routinen, LINE und OPEN "COMn:") -- geschlossen - Drei Gruppen fehlten im urspruenglichen Umfang: Finanzmathematik, Record-Konvertierung, Rest der Dateisystemfunktionen - bas7advr.hlp allein ist keine vollstaendige Quelle; das Inventar bildet die Vereinigung mit qb45advr.hlp Neue Changes - phase-3-isam: schliesst Phase 3 ab (Speicherschicht redb) - phase-3-ortszeit: zieht die UTC-Abweichung zurueck Neue Abhaengigkeit: unicode-width. Dokumentiert: sprachreferenz.md und tbvm-design.md sind TODO-frei, docs/bibliothek.md neu, PLAN.md fortgeschrieben. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,8 @@
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use tb_runtime::host::ConsoleHost;
|
||||
use tb_runtime::host::{Ereignis, Host};
|
||||
use tb_ui::host::TerminalHost;
|
||||
use tb_vm::interp::{RunEvent, Vm};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
@@ -90,8 +91,36 @@ fn cmd_run(args: &[String]) -> ExitCode {
|
||||
};
|
||||
let mut vm = Vm::new(module);
|
||||
vm.rt.command = args[1..].join(" ");
|
||||
let mut host = ConsoleHost;
|
||||
match vm.run(&mut host) {
|
||||
// Im Rohmodus ist Strg+C kein Signal mehr — der Abbruch kommt als
|
||||
// Ereignis und muss den Lauf beenden können.
|
||||
vm.set_poll_interrupt(true);
|
||||
|
||||
// Ohne Terminal (Pipe, Skript, CI) läuft das Programm im PipeHost:
|
||||
// Eingabe zeilenweise von stdin, Ausgabe am Ende als Snapshot.
|
||||
let ereignis = match TerminalHost::new() {
|
||||
Ok(mut host) => {
|
||||
// Der Bildschirm folgt der Terminalgröße von Beginn an (80×25 ist
|
||||
// stets nur die untere Schranke, nie eine feste Größe).
|
||||
if let Ok((cols, rows)) = host.groesse() {
|
||||
vm.rt.screen.resize(cols, rows);
|
||||
}
|
||||
let e = vm.run(&mut host);
|
||||
drop(host); // Alternativschirm verlassen, bevor gedruckt wird
|
||||
e
|
||||
}
|
||||
Err(_) => vm.run(&mut PipeHost::new()),
|
||||
};
|
||||
print!("{}", tb_runtime::snapshot::text(&vm.rt.screen));
|
||||
// `LPRINT` sammelt im Druckerpuffer; am Programmende geht er in die
|
||||
// Datei LPT1.TXT im aktuellen Verzeichnis (dokumentierte Abweichung —
|
||||
// einen Druckerkanal gibt es plattformübergreifend nicht).
|
||||
if !vm.rt.print.drucker.is_empty() {
|
||||
if let Err(e) = std::fs::write("LPT1.TXT", &vm.rt.print.drucker) {
|
||||
eprintln!("Druckerausgabe nicht schreibbar: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
match ereignis {
|
||||
RunEvent::Ended => ExitCode::SUCCESS,
|
||||
RunEvent::Stopped { line } => {
|
||||
// STOP außerhalb der IDE: Meldung + Exit-Code ≠ 0 (D6).
|
||||
@@ -103,8 +132,61 @@ fn cmd_run(args: &[String]) -> ExitCode {
|
||||
ExitCode::from(2)
|
||||
}
|
||||
// Ohne Debugger-Flags treten diese Ereignisse nicht auf.
|
||||
RunEvent::Breakpoint { .. } | RunEvent::Stepped { .. } | RunEvent::Interrupted { .. } => {
|
||||
ExitCode::from(2)
|
||||
RunEvent::Interrupted { line } => {
|
||||
eprintln!("Abgebrochen in Zeile {line}");
|
||||
ExitCode::from(3)
|
||||
}
|
||||
// Ohne Debugger-Flags treten diese Ereignisse nicht auf.
|
||||
RunEvent::Breakpoint { .. } | RunEvent::Stepped { .. } => ExitCode::from(2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Host ohne Terminal: für Pipes und Skripte (`tbc run x.bas < eingabe.txt`).
|
||||
/// Zeigt während des Laufs nichts an; die Ausgabe entsteht am Ende aus dem
|
||||
/// Bildschirm-Snapshot. Tastendrücke kommen zeilenweise von stdin.
|
||||
struct PipeHost {
|
||||
puffer: std::collections::VecDeque<Ereignis>,
|
||||
eof: bool,
|
||||
}
|
||||
|
||||
impl PipeHost {
|
||||
fn new() -> Self {
|
||||
PipeHost { puffer: std::collections::VecDeque::new(), eof: false }
|
||||
}
|
||||
|
||||
/// Eine Zeile von stdin in Tastendrücke zerlegen.
|
||||
fn nachfuellen(&mut self) {
|
||||
use std::io::BufRead;
|
||||
if self.eof {
|
||||
return;
|
||||
}
|
||||
let mut zeile = String::new();
|
||||
match std::io::stdin().lock().read_line(&mut zeile) {
|
||||
Ok(0) | Err(_) => {
|
||||
self.eof = true;
|
||||
self.puffer.push_back(Ereignis::Ende);
|
||||
}
|
||||
Ok(_) => {
|
||||
while zeile.ends_with('\n') || zeile.ends_with('\r') {
|
||||
zeile.pop();
|
||||
}
|
||||
for c in zeile.chars() {
|
||||
self.puffer.push_back(Ereignis::Taste(c.to_string()));
|
||||
}
|
||||
self.puffer
|
||||
.push_back(Ereignis::Taste(tb_runtime::host::taste::ENTER.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Host for PipeHost {
|
||||
fn present(&mut self, _screen: &tb_runtime::screen::TextScreen) {}
|
||||
|
||||
fn next_event(&mut self, blockierend: bool) -> Option<Ereignis> {
|
||||
if self.puffer.is_empty() && blockierend {
|
||||
self.nachfuellen();
|
||||
}
|
||||
self.puffer.pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user