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:
2026-09-04 06:54:19 +02:00
parent a6f005d023
commit 52ccbb5848
94 changed files with 10421 additions and 792 deletions

View File

@@ -19,6 +19,7 @@ pub fn compile(hir: &HirModule) -> CompiledModule {
strings: Vec::new(),
string_ids: HashMap::new(),
jump_tables: Vec::new(),
modul_label_pc: Vec::new(),
};
let mut procs = Vec::new();
for proc in &hir.procs {
@@ -74,6 +75,10 @@ struct Codegen {
strings: Vec<Rc<str>>,
string_ids: HashMap<String, u16>,
jump_tables: Vec<Vec<u32>>,
/// Sprungziele des Modulrumpfs (Prozedur 0). Ein modulweites
/// `ON ERROR GOTO` aus einer Prozedur zeigt dorthin; da Prozedur 0
/// zuerst übersetzt wird, stehen die Positionen rechtzeitig fest.
modul_label_pc: Vec<Option<u32>>,
}
struct ProcCtx {
@@ -137,9 +142,26 @@ impl Codegen {
// Rumpfende
self.emit_proc_exit(&mut ctx, proc);
// Prozedur 0 (Modulrumpf) gibt ihre Sprungziele weiter.
if proc.kind == hir::HProcKind::Main {
self.modul_label_pc = ctx.label_pc.clone();
}
// Fixups patchen
for (idx, label) in std::mem::take(&mut ctx.fixups) {
let pc = ctx.label_pc[label as usize].expect("Label ohne Position");
// Modulweites `ON ERROR GOTO` in einer Prozedur: das Label lebt
// im Modulrumpf, nicht im eigenen.
let modulweit = proc.kind != hir::HProcKind::Main
&& matches!(ctx.code[idx], Instr::OnErrorGoto(_));
let pc = if modulweit {
self.modul_label_pc
.get(label as usize)
.copied()
.flatten()
.expect("Modul-Label ohne Position")
} else {
ctx.label_pc[label as usize].expect("Label ohne Position")
};
match &mut ctx.code[idx] {
Instr::Jump(t)
| Instr::JumpIfFalse(t)
@@ -224,7 +246,57 @@ impl Codegen {
ctx.emit(Instr::CallBuiltin(ids::PRINT_NEWLINE, 0));
}
}
HStmtKind::Input { line_mode, prompt, question, targets } => {
HStmtKind::SetErr(e) => {
self.expr(ctx, e);
ctx.emit(Instr::SetErr);
}
HStmtKind::Field { file, fields } => {
self.expr(ctx, file);
for (len, place) in fields {
self.expr(ctx, len);
self.make_ref(ctx, place);
}
ctx.emit(Instr::Field(fields.len() as u8));
}
HStmtKind::LsetRset { rset, target, value } => {
self.make_ref(ctx, target);
self.expr(ctx, value);
ctx.emit(Instr::LsetRset(*rset));
}
HStmtKind::GetPut { put, file, recnum, var } => {
self.expr(ctx, file);
if let Some(r) = recnum {
self.expr(ctx, r);
}
let (art, zusatz) = match var {
None => (0u8, 0u16),
Some(v) => {
self.make_ref(ctx, v);
match &v.ty {
HTy::Num(NumTy::Int) => (1, 0),
HTy::Num(NumTy::Lng) => (2, 0),
HTy::Num(NumTy::Sng) => (3, 0),
HTy::Num(NumTy::Dbl) => (4, 0),
HTy::Num(NumTy::Cur) => (5, 0),
HTy::FixedStr(n) => (6, *n as u16),
HTy::Udt(i) => (7, *i),
// Variable Strings: Länge erst zur Laufzeit.
HTy::Str => (8, 0),
}
}
};
ctx.emit(Instr::GetPut(*put, recnum.is_some(), art, zusatz));
}
HStmtKind::Input { file, line_mode, prompt, question, targets } => {
if let Some(f) = file {
// Dateinummer zuerst, dann die Referenzen darüber.
self.expr(ctx, f);
for t in targets {
self.make_ref(ctx, t);
}
ctx.emit(Instr::InputFile(targets.len() as u8, *line_mode));
return;
}
for t in targets {
self.make_ref(ctx, t);
}
@@ -901,9 +973,90 @@ fn builtin_id(b: Builtin) -> u16 {
Builtin::PrintTab => ids::PRINT_TAB,
Builtin::PrintSpc => ids::PRINT_SPC,
Builtin::PrintNewline => ids::PRINT_NEWLINE,
Builtin::PrintUsing => ids::PRINT_USING,
Builtin::FormatS => ids::FORMAT_S,
Builtin::SetFormatCc => ids::SET_FORMAT_CC,
Builtin::Cls => ids::CLS,
Builtin::Color => ids::COLOR,
Builtin::Locate => ids::LOCATE,
Builtin::Width => ids::WIDTH,
Builtin::ViewPrint => ids::VIEW_PRINT,
Builtin::ScreenStmt => ids::SCREEN_STMT,
Builtin::KeyAssign => ids::KEY_ASSIGN,
Builtin::KeyList => ids::KEY_LIST,
Builtin::KeyDisplay => ids::KEY_DISPLAY,
Builtin::Csrlin => ids::CSRLIN,
Builtin::PosFn => ids::POS_FN,
Builtin::ScreenFn => ids::SCREEN_FN,
Builtin::InkeyS => ids::INKEY_S,
Builtin::InputS => ids::INPUT_S,
Builtin::EnvironS => ids::ENVIRON_S,
Builtin::EnvironSet => ids::ENVIRON_SET,
Builtin::Fre => ids::FRE,
Builtin::Clear => ids::CLEAR,
Builtin::Tron => ids::TRON,
Builtin::Troff => ids::TROFF,
Builtin::StackFn => ids::STACK_FN,
Builtin::StackStmt => ids::STACK_STMT,
Builtin::Erdev => ids::ERDEV,
Builtin::ErdevS => ids::ERDEV_S,
Builtin::Open => ids::OPEN,
Builtin::Close => ids::CLOSE,
Builtin::CloseAll => ids::CLOSE_ALL,
Builtin::PrintZiel => ids::PRINT_ZIEL,
Builtin::WriteFile => ids::WRITE_FILE,
Builtin::EofF => ids::EOF_F,
Builtin::LofF => ids::LOF_F,
Builtin::LocF => ids::LOC_F,
Builtin::SeekF => ids::SEEK_F,
Builtin::SeekStmt => ids::SEEK_STMT,
Builtin::Freefile => ids::FREEFILE,
Builtin::Fileattr => ids::FILEATTR,
Builtin::LockStmt => ids::LOCK_STMT,
Builtin::Kill => ids::KILL,
Builtin::NameStmt => ids::NAME_STMT,
Builtin::Files => ids::FILES,
Builtin::Chdir => ids::CHDIR,
Builtin::Chdrive => ids::CHDRIVE,
Builtin::Mkdir => ids::MKDIR,
Builtin::Rmdir => ids::RMDIR,
Builtin::CurdirS => ids::CURDIR_S,
Builtin::DirS => ids::DIR_S,
Builtin::Lpos => ids::LPOS,
Builtin::ShellStmt => ids::SHELL_STMT,
Builtin::ShellFn => ids::SHELL_FN,
Builtin::MkS => ids::MK_S,
Builtin::CvF => ids::CV_F,
Builtin::Fv => ids::FV,
Builtin::Pv => ids::PV,
Builtin::Pmt => ids::PMT,
Builtin::NPer => ids::NPER,
Builtin::IPmt => ids::IPMT,
Builtin::PPmt => ids::PPMT,
Builtin::Rate => ids::RATE,
Builtin::Npv => ids::NPV,
Builtin::Irr => ids::IRR,
Builtin::Mirr => ids::MIRR,
Builtin::Sln => ids::SLN,
Builtin::Syd => ids::SYD,
Builtin::Ddb => ids::DDB,
Builtin::Timer => ids::TIMER,
Builtin::DateS => ids::DATE_S,
Builtin::TimeS => ids::TIME_S,
Builtin::DateSet => ids::DATE_SET,
Builtin::TimeSet => ids::TIME_SET,
Builtin::Now => ids::NOW,
Builtin::DateSerial => ids::DATE_SERIAL,
Builtin::TimeSerial => ids::TIME_SERIAL,
Builtin::DateValue => ids::DATE_VALUE,
Builtin::TimeValue => ids::TIME_VALUE,
Builtin::DayF => ids::DAY_F,
Builtin::MonthF => ids::MONTH_F,
Builtin::YearF => ids::YEAR_F,
Builtin::WeekdayF => ids::WEEKDAY_F,
Builtin::HourF => ids::HOUR_F,
Builtin::MinuteF => ids::MINUTE_F,
Builtin::SecondF => ids::SECOND_F,
Builtin::CommandS => ids::COMMAND_S,
Builtin::Doevents => ids::DOEVENTS,
Builtin::Sleep => ids::SLEEP,