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

@@ -0,0 +1,180 @@
## Context
Siehe proposal.md — Why. Der Ist-Zustand ist eng umrissen:
- `tb-runtime::datetime` rechnet vollständig selbst: Kalenderumrechnung
(Howard Hinnant), serielle Datumswerte ab dem 30.12.1899, `WEEKDAY`,
Textformen. Diese Rechnung ist getestet und bleibt.
- Der einzige Punkt, an dem die Zeitzone eine Rolle spielt, ist
`jetzt_sekunden()` — Sekunden seit der Unix-Epoche. Alles Weitere
(`jetzt_serial`, `DATE$`, `TIME$`, `TIMER`, `NOW`) baut darauf auf.
- `tb-runtime` hat bislang eine einzige Abhängigkeit mit fachlichem
Gewicht: `unicode-width`.
Damit ist die Aufgabe klein: **eine Zahl** — der UTC-Versatz zu einem
Zeitpunkt — kommt künftig von außen.
## Goals / Non-Goals
**Goals:**
- Zeitfunktionen liefern das, was die Uhr des Rechners zeigt.
- Sommerzeit wird zum jeweiligen Zeitpunkt berücksichtigt.
- Die Berührungsfläche zur Bibliothek bleibt eine Funktion, damit sie
austauschbar ist und die getestete Kalenderrechnung unberührt bleibt.
- Zeitverhalten bleibt automatisiert prüfbar, ohne von der Zone des
Testrechners abzuhängen.
**Non-Goals:**
- Zeitzonenbehaftete Datentypen im Dialekt — das Vorbild kennt nur
Wanduhrzeit.
- Eine Sprachanbindung an die Bibliothek (Zonennamen, Umrechnung zwischen
Zonen). Das ist der Stufe-2-Eintrag im PLAN, nicht dieser Change.
- Historische Zonendaten jenseits dessen, was die Systemdatenbank
hergibt.
## Decisions
### D1: `jiff` als Zeitzonenbibliothek
*Gewählt:* `jiff` — sie liest die Zeitzonendatenbank des Systems und
liefert den Versatz zu einem Zeitpunkt ohne Einschränkung.
*Alternativen:*
| Bibliothek | Bewertung |
|---|---|
| `jiff` | Für genau diese Frage gebaut; Systemzonendatenbank; keine Sicherheitsauflage. **Gewählt.** |
| `chrono` (≥ 0.4.20) | Gleichwertig sicher, seit 0.4.20 ohne `localtime_r` (Zonenermittlung über `iana-time-zone`, Datenbank über einen `tz-rs`-Zweig). Etablierter, bringt aber Serde-/Ökosystem-Anbindung mit, die hier niemand braucht. Tragfähige zweite Wahl. |
| `time` | Der lokale Versatz ist auf Unix nur nach ausdrücklichem Opt-in in möglicherweise undefiniertes Verhalten verfügbar (`set_soundness`). **Verworfen** — das ist kein Preis für eine Uhrzeit. |
| Eigenbau (`iana-time-zone` + TZif-Parser) | Hieße, die Zonendatenbank selbst zu lesen und dauerhaft nachzupflegen. Genau das, wofür man eine Bibliothek nimmt. **Verworfen.** |
*Warum die Wahl leicht revidierbar bleibt:* Die Bibliothek wird an genau
einer Stelle aufgerufen (D2). Ein Wechsel zu `chrono` wäre ein Austausch
dieser einen Funktion.
### D2: Berührungsfläche ist eine Funktion
```
tb-runtime::datetime
utc_versatz(zeitpunkt: i64) -> Option<i32> // Sekunden, None = Zone unbekannt
└── einziger Aufruf der Bibliothek
jetzt_sekunden() ─┐
jetzt_serial() ├── rechnen weiter selbst
civil_from_days() │
weekday() … ─┘
```
Die Kalenderrechnung sieht die Bibliothek nie. Sie bekommt nach wie vor
Sekunden seit der Epoche — nur eben um den Versatz verschobene.
*Warum nicht die Bibliothek durchreichen:* Ihre Datums-/Zeittypen wären
ein zweites Kalendermodell neben dem des Dialekts (der bei 1899 zählt,
`WEEKDAY` = 1 für Sonntag, CURRENCY-nahe Serienwerte). Zwei Modelle
nebeneinander wären eine dauerhafte Fehlerquelle; die vorhandene Rechnung
ist getestet und bleibt maßgeblich.
### D3: Der Versatz gilt je Zeitpunkt, nicht je Programmlauf
`utc_versatz` bekommt den Zeitpunkt als Argument. Ein Programm, das über
eine Zeitumstellung hinweg läuft, folgt ihr; ein Programm, das mit
`DATE$ =` in einen anderen Teil des Jahres springt, bekommt den dort
gültigen Versatz.
*Preis:* Eine Zonenabfrage je Zeitabfrage. Bei den Aufrufmengen eines
Terminalprogramms belanglos; die Bibliothek hält die Zonendaten selbst
vor.
### D4: Zonenquelle ist umschaltbar — damit Tests nicht von der Maschine abhängen
```rust
pub enum Zeitzone {
/// Zone des Rechners (Vorgabe).
System,
/// Fester Versatz in Sekunden — für Tests und den Korpus.
Fest(i32),
/// Zone nicht ermittelbar; es gilt UTC.
Unbekannt,
}
```
`RtState` hält eine `Zeitzone`. Die Vorgabe ist `System`; schlägt die
Ermittlung fehl, wird daraus `Unbekannt`. Tests und Korpusprogramme
setzen `Fest(n)`.
*Warum nicht `TZ` in den Tests setzen:* Umgebungsvariablen sind
prozessweit, Tests laufen nebenläufig, und `set_var` ist seit Rust 2024
`unsafe`. Ein Zustand im `RtState` ist der offensichtliche Weg — er ist
genau so weit sichtbar wie nötig.
*Korpus:* Das Harness bekommt eine Kopfzeilen-Direktive analog zu
`' tb-screen:` und `' tb-tempdir`:
```text
' tb-tz: +02:00
```
Damit bleibt die Sollausgabe eines Zeitprogramms unabhängig davon, wo der
Testrechner steht.
### D5: Rückfallebene ist sichtbar, nicht still
Ohne ermittelbare Zone gilt UTC — ein Abbruch wäre unverhältnismäßig, ein
stiller Wechsel aber ein Verstoß gegen den Guiding Principle. Die Laufzeit
führt den Zustand mit, und der Zustand ist abfragbar; die Sprachreferenz
beschreibt ihn.
*Wie ein Programm es erfährt:* Über die vorhandene Systemabfrage
`ENVIRON$` wäre es geraten. Stattdessen bekommt die Laufzeit einen
benannten Zustand, den der Runner beim Start auf die Fehlerausgabe
schreibt, wenn er `Unbekannt` ist — einmal, nicht je Abfrage. Ein
Programm, das es programmatisch braucht, bekommt es in Stufe 2 über die
Bibliotheksanbindung.
### D6: Reihenfolge der Umsetzung
```
1. Abhaengigkeit + utc_versatz() -> isoliert testbar
2. jetzt_sekunden/jetzt_serial umstellen -> DATE$/TIME$/TIMER/NOW folgen
3. Zeitzone im RtState + Korpusdirektive -> Tests werden maschinenunabhaengig
4. Doku: Abweichung zurueckziehen, PLAN Stufe 2
```
## Risks / Trade-offs
- **Erste schwere Abhängigkeit der Laufzeit** → bewusst entschieden
(2026-09-04). Die Berührungsfläche ist eine Funktion (D2); ein Wechsel
der Bibliothek bleibt ein lokaler Eingriff.
- **Zeitabhängige Tests werden flatterig** → deshalb D4: kein Test und
kein Korpusprogramm fragt die echte Zone ab. Was die Systemzone
betrifft, wird über `Fest(n)` und `Unbekannt` geprüft.
- **Zeitumstellung erzeugt mehrdeutige Wanduhrzeiten** (Rückstellung) und
nicht existierende (Vorstellung) → betrifft nur die Richtung
Wanduhr → Zeitpunkt. Die Zeitfunktionen dieses Dialekts gehen die
andere Richtung (Zeitpunkt → Wanduhr) und sind davon nicht betroffen.
`DATEVALUE`/`TIMEVALUE` lesen Wanduhrzeit und rechnen gar nicht um.
`DATE$ =`/`TIME$ =` setzen einen Versatz gegen die aktuelle Wanduhrzeit
— auch das ist eindeutig.
- **Die Systemzonendatenbank kann fehlen** (schlanke Container) → D5.
- **Bauzeit und Binärgröße wachsen** → für ein Werkzeug, dessen Programme
Uhrzeiten anzeigen, ist eine falsche Uhrzeit der größere Preis.
## Migration Plan
Rein verhaltensändernd, ohne Formatbruch: `.tbc`-Dateien, Korpus-Golden-
Files und Schnittstellen bleiben unberührt. Das einzige beobachtbare
Verhalten, das sich ändert, ist der Wert von `DATE$`, `TIME$`, `TIMER`
und `NOW` — und zwar zum Richtigen hin.
Rücknahme: Ein Zurücksetzen auf UTC wäre `Zeitzone::Unbekannt` als
Vorgabe; die Abhängigkeit bliebe ungenutzt.
## Open Questions
- Ob die Bibliotheksanbindung in Stufe 2 (Zonennamen, Umrechnung zwischen
Zonen als BASIC-Funktionen) auf derselben Bibliothek aufsetzt oder eine
eigene Wahl trifft, entscheidet Stufe 2. Für diesen Change ohne
Wirkung — hier wird nur der Versatz gebraucht.