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

@@ -2,14 +2,17 @@
Rekonstruierte Referenz des Dialekts (DOS-BASIC, Stand 1992: prozedurale
BASIC-Familie mit Forms-Erweiterung). Dieses Dokument ist die verbindliche
Grundlage für Frontend und Runtime. Unklare Detailfragen sind mit `TODO`
markiert und werden per Testkorpus geklärt bzw. entschieden.
Grundlage für Frontend und Runtime. Offene Detailfragen sind ausgeräumt
(Stand 2026-09-04); wo die Original-Dokumentation schweigt, steht an Ort
und Stelle eine datierte Entscheidung mit Begründung.
**Primärquellen:** Original-Hilfedateien des Vorbilds als HTML unter
https://dos-help.soulsphere.org/ (`qck.hlp` Quick Reference, `advr.hlp`
Details, `ener.hlp` Fehlermeldungen; Vergleich PDS 7.1: `bas7qck.hlp`)
sowie das Original-README der Professional Edition. Aussagen ohne
TODO-Markierung sind daraus belegt.
ausgewiesene Entscheidung sind daraus belegt; die Themenlisten von
`bas7advr.hlp` und `qb45advr.hlp` bilden zusammen das
Vollständigkeits-Inventar (docs/inventar.md).
**Guiding Principle:** Die Original-Dokumentation führt. Jedes dort
dokumentierte Sprachelement wird unterstützt oder — nur nach expliziter
@@ -94,7 +97,9 @@ stehen am Ende dieses Dokuments; nur diese Liste rechtfertigt Abweisungen.
- `DIM [SHARED] var[(dims)] [AS typ]``AS`-Klausel: INTEGER, LONG, SINGLE,
DOUBLE, STRING, STRING * n, CURRENCY, benutzerdefinierter Typ.
- `COMMON [SHARED] [/blockname/] liste` — modulübergreifend (Kette
CHAIN-kompatibel im Vorbild; TODO: Relevanz ohne CHAIN klären).
CHAIN-kompatibel im Vorbild; ohne `CHAIN` bleibt `COMMON` die
Verknüpfung mehrerer Module eines Projekts — geklärt mit der
Projektverwaltung in Phase 5).
- `SHARED` (in Prozedur): Zugriff auf Modulebene-Variablen.
- `STATIC` (in Prozedur): Variablen behalten Werte zwischen Aufrufen;
`STATIC`-Attribut an `SUB`/`FUNCTION` macht alle lokalen Variablen statisch.
@@ -152,7 +157,8 @@ INTEGER 1 (wahr) / 0 (falsch).
oder `DEF…`-Regel.
- **Parameterübergabe standardmäßig BYREF.** Klammern um ein Argument
(`CALL f((x))`) erzwingen Wertübergabe. `BYVAL` nur in `DECLARE` für
externe Routinen (entfällt bei uns; TODO: `BYVAL` allgemein erlauben?).
externe Routinen (entfällt bei uns; `BYVAL` allgemein zu erlauben wäre
eine Erweiterung über den Kernstandard hinaus → Stufe 2, PLAN.md).
- Arrays werden mit `name()` übergeben, TYPEs BYREF.
- Rekursion erlaubt (außer bei `STATIC`-Semantik-Konflikten).
- `DEF FNname (args) = ausdruck` und Block-`DEF FN … END DEF` (mit
@@ -194,8 +200,21 @@ solange ein Menü-Control den Fokus hat. Für Terminal Basic relevant:
position, `-` nachgestelltes Minus, `$$` führendes $, `**` Füllsterne,
`**$` kombiniert, `^^^^` Exponent; String `&` ganz, `!` erstes Zeichen,
`\ \` n Zeichen (Leerzeichen + 2); `_` Literal-Escape; alle anderen
Zeichen literal. TODO: `%`-Präfix bei Feldüberlauf (QB-Verhalten) in der
Vorbild-Hilfe nicht gelistet — per Test klären, wir übernehmen es vorerst.
Zeichen literal.
- **Feldüberlauf** (geklärt 2026-09-04): Passt ein Wert nicht in sein
Feld, wird er vollständig ausgegeben und die Ausgabe mit `%`
eingeleitet (`PRINT USING "##"; 12345``%12345`). Die Original-Hilfe
listet das nicht; wir übernehmen das Verhalten der QB-Familie, weil ein
stilles Abschneiden Zahlen verfälschen würde.
- Vorzeichenangaben (`+` vorn, `+`/`-` hinten) belegen eine **eigene**
Position neben den Ziffernstellen; `$$` und `**` bringen ihre Stellen
dagegen mit.
- Die Formatzeichenkette wird wiederholt, solange Werte übrig sind. Eine
Zeichenkette ohne Feld bei vorhandenen Werten löst Fehler 5 aus.
- `SetFormatCC(ländercode)` setzt das Zeichen für `$$`. Nur wenige Codes
sind belegt (1 = `$`, 33 = `F`, 39 = `L`, 44 = `£`, 49 = `DM`,
81 = `¥`); alle übrigen behalten `$` — die Tabelle des Vorbilds ist
nicht überliefert (dokumentierte Abweichung).
- `INPUT ["prompt"{;|,}] var, …` (mit `;` vor Prompt: kein „? ");
`LINE INPUT` (ganze Zeile in String).
- `INKEY$` (nicht blockierend; "" wenn leer; erweiterte Tasten:
@@ -205,13 +224,26 @@ solange ein Menü-Control den Fokus hat. Für Terminal Basic relevant:
— arbeiten auf der tatsächlichen Bildschirmgröße (dynamisch, s. Abweichungen).
- `COLOR [vg][,hg]` (vg 031: 1631 = blinkend — bei uns als „hell"
simuliert, Entscheidung 2026-09-02; hg 07), `CLS`,
`WIDTH` (im Vorbild 80/40 — bei uns wirkungslos, s. Abweichungen),
`WIDTH` (hebt den Puffer höchstens an, s. Abweichungen),
`VIEW PRINT oben TO unten` (Scrollbereich).
- `TAB(n)`, `SPC(n)` in PRINT-Listen.
- `BEEP`, `SOUND freq, dauer` (Terminal-Bell / Nicht-Ziel, s. Abweichungen),
`PLAY` (Nicht-Ziel).
- `KEY n, text$` / `KEY LIST` / `KEY ON/OFF` (Funktionstasten-Makros +
Statuszeile) — TODO: Umfang klären.
- **Funktionstasten-Makros** (Umfang geklärt 2026-09-03 aus der
Original-Hilfe):
- `KEY n, text$` belegt ein Makro. `n` ist 110 für F1F10 sowie 30 und
31 für F11/F12; andere Werte lösen Fehler 5 aus. `text$` wird nach
**15 Zeichen** abgeschnitten.
- `KEY LIST` gibt alle zwölf Makros untereinander aus.
- `KEY ON` blendet die Softkey-Zeile in der untersten Bildschirmzeile ein
(Tastennummer plus die ersten sechs Zeichen des Makros), `KEY OFF`
blendet sie aus. Solange sie sichtbar ist, endet der Scrollbereich
darüber, damit die Ausgabe sie nicht überschreibt; ein danach gesetztes
`VIEW PRINT` hat Vorrang.
- `KEY(n) ON|OFF|STOP` **mit** Index ist Ereignissteuerung und kommt mit
der Forms-Engine (Phase 4).
- Abweichung: ein kompiliertes Programm startet mit **leeren** Makros;
die Vorbelegung des Vorbilds gehörte zu dessen Interpreter-Oberfläche.
## 10. Datei-E/A
@@ -240,8 +272,20 @@ solange ein Menü-Control den Fokus hat. Für Terminal Basic relevant:
`HEX$`, `OCT$`, `LSET`/`RSET` (Anweisungen).
**Mathematik:** `ABS`, `SGN`, `INT` (abrunden), `FIX` (Richtung 0), `SQR`,
`EXP`, `LOG`, `SIN`, `COS`, `TAN`, `ATN`, `RND[(n)]`, `RANDOMIZE [saat]`
(kompatibler PRNG! → Testkorpus), `CINT`, `CLNG`, `CSNG`, `CDBL`, `CCUR`.
`EXP`, `LOG`, `SIN`, `COS`, `TAN`, `ATN`, `RND[(n)]`, `RANDOMIZE [saat]`,
`CINT`, `CLNG`, `CSNG`, `CDBL`, `CCUR`.
**Zufallszahlen.** Der Generator ist der lineare Kongruenzgenerator des
Vorbilds auf 24 Bit:
`zustand = (zustand · &H FD43FD + &H C39EC3) MOD 2^24`, Ergebnis
`zustand / 2^24` als SINGLE im Bereich [0, 1).
`RND` und `RND(n)` mit `n > 0` liefern die nächste Zahl, `RND(0)`
wiederholt die zuletzt gelieferte, `RND(n)` mit `n < 0` setzt den Zustand
aus dem Argument. `RANDOMIZE saat` bildet aus der Saat 16 Bit, die die
Bits 823 des Zustands besetzen; das niederwertige Byte ist fest — damit
liefert dieselbe Saat stets dieselbe Folge, unabhängig von vorherigen
`RND`-Aufrufen. `RANDOMIZE` ohne Argument fragt den Startwert mit
`Random Number Seed (-32768 to 32767)?` ab.
**Datum/Zeit:** `DATE$` (Funktion und Anweisung — Setzen: Nicht-Ziel),
`TIME$`, `TIMER` (Sekunden seit Mitternacht, SINGLE); dazu die nativen
@@ -287,7 +331,13 @@ Dialoge:
- Der Bildschirmpuffer speichert Unicode-Zeichen; Rahmen werden mit
Unicode-Box-Drawing gezeichnet.
- Zeichen mit Darstellungsbreite 2 (Emoji, CJK) **belegen zwei Zellen**
(Entscheidung 2026-09-02; Umsetzung Phase 3).
(Entscheidung 2026-09-02, umgesetzt): der Cursor rückt um zwei Spalten
vor, `POS` nennt die Anfangsspalte, `LOCATE` auf die zweite Hälfte
wirkt auf den Zeichenanfang. Passt ein breites Zeichen nicht mehr in
die Zeile, wandert es als Ganzes in die nächste und die letzte Spalte
bleibt leer. Wird eine Hälfte überschrieben, verschwindet die andere.
Zeichen der Breite 0 (kombinierende Zeichen) belegen vorerst eine
eigene Zelle.
- `GET`/`PUT`-Records: feste Strings (`STRING * n`) werden als **UTF-32**
gespeichert (4 Bytes/Zeichen, feste Record-Länge) — Binärdateien sind
dadurch bewusst inkompatibel zum Vorbild (Entscheidung 2026-09-02).
@@ -295,20 +345,60 @@ Dialoge:
`OUT`/`WAIT`/`BLOAD`/`BSAVE`/`VARPTR`/`VARSEG`/`SADD`/`CALL ABSOLUTE`/
`CALL INTERRUPT`/`IOCTL` werden bereits **zur Compile-Zeit** mit der
Meldung „Feature unavailable" abgewiesen (kein Laufzeitfehler).
3. **`INKEY$` für erweiterte Tasten** liefert weiterhin
`CHR$(0) + code`-Sequenzen mit den klassischen Scancodes (F1 = `CHR$(0)+";"`
usw.), damit bestehender Code funktioniert. Zusätzliche moderne Tasten:
TODO.
Ebenso abgewiesen: die Interlanguage-Schnittstelle (`CALLS`, `SSEG`,
`SSEGADD`, `StringAddress`/`StringAssign`/`StringLength`/
`StringRelease`), die Eingabegeräte `PEN`, `STRIG`, `STICK` und die
serielle Schnittstelle (`COM`, `ON COM`, `OPEN "COMn:"`).
3. **`INKEY$` für erweiterte Tasten** liefert `CHR$(0) + code`-Sequenzen
mit den klassischen Scancodes, damit bestehender Code funktioniert
(festgelegt 2026-09-04): F1F10 = 5968, Pos1 = 71, ↑ = 72, Bild↑ = 73,
← = 75, → = 77, Ende = 79, ↓ = 80, Bild↓ = 81, Einfg = 82, Entf = 83.
Zeichentasten liefern ein Zeichen, Eingabe `CHR$(13)`, Rückschritt
`CHR$(8)`, Esc `CHR$(27)`, Tabulator `CHR$(9)`. Strg+C und Strg+Untbr
sind Abbruch und erscheinen nicht in `INKEY$`. Tasten heutiger
Tastaturen ohne Entsprechung im Vorbild (etwa F11/F12 oder Meta-
Kombinationen) liefern nichts — eine eigene Kodierung dafür wäre eine
Erweiterung und gehört in Stufe 2.
4. **Kein `CHAIN`/Overlay-Mechanismus**; `SHELL` startet die System-Shell.
5. **Grafik-Anweisungen** (`SCREEN n>0`, `PSET`, `LINE`, `CIRCLE`, `PAINT`,
`DRAW`, Grafik-`GET`/`PUT`, `PALETTE`, `WINDOW`/`VIEW`, `PMAP`, `PCOPY`)
sind Nicht-Ziel (Fehler 73). Hinweis: das Vorbild **hatte** die
5. **Grafik-Anweisungen** (`SCREEN n>0`, `PSET`, `PRESET`, `LINE`,
`CIRCLE`, `PAINT`, `DRAW`, Grafik-`GET`/`PUT`, `PALETTE`, `WINDOW`,
`VIEW`, `PMAP`, `PCOPY`, `POINT`) sind Nicht-Ziel und werden **zur
Compile-Zeit** mit „Feature unavailable" abgewiesen. Die Formen, die
sich ein Schlüsselwort mit einer unterstützten Anweisung teilen, erkennt
der Compiler an ihrer Syntax: `LINE` ohne `INPUT`, `GET`/`PUT` mit
`(`, `VIEW` ohne `PRINT`. Hinweis: das Vorbild **hatte** die
Grafikmodi 013, aber nicht gleichzeitig mit Forms (dort Fehler 272
„Invalid when forms are showing") — wir übernehmen nur den
Forms-/Textmodus-Zweig.
6. **`PLAY`/`SOUND`**: `BEEP` = Terminal-Bell; Rest Nicht-Ziel (Fehler 73).
5a. **`LPRINT` schreibt in eine Datei** (2026-09-03): einen
plattformübergreifenden Druckerkanal gibt es nicht. Der Druckerpuffer
geht am Programmende in `LPT1.TXT` im aktuellen Verzeichnis; `LPOS`
nennt die Spalte in diesem Puffer.
5b. **Satzsperren** (`LOCK`/`UNLOCK`) prüfen nur, ob die Datei offen ist;
eine echte Sperre bietet die Plattform nicht einheitlich. `FILEATTR`
liefert nur die Modus-Kennzahl (Argument 1); das
Betriebssystem-Handle bildet der Dialekt nicht ab und liefert 0.
5c. **`CHDRIVE`** ist außerhalb von Windows folgenlos — Laufwerksbuchstaben
gibt es dort nicht.
6a. **Uhr läuft in UTC** (2026-09-03): `DATE$`, `TIME$`, `TIMER` und `NOW`
liefern koordinierte Weltzeit, nicht Ortszeit — die Standardbibliothek
von Rust kennt keine Zeitzonen, und eine Zeitzonenbibliothek wäre die
erste schwere Abhängigkeit der Laufzeit. `DATE$ = …` und `TIME$ = …`
stellen nicht die Systemuhr, sondern einen programmeigenen Versatz;
alle Zeitfunktionen rechnen ihn mit ein.
6b. **Serielle Datumswerte** zählen Tage ab dem 30.12.1899 (Serie 2 =
1.1.1900), der Nachkommaanteil ist die Tageszeit. `WEEKDAY` liefert 1
für Sonntag. Zweistellige Jahre gelten in `DATESERIAL` als 19xx, in
`DATEVALUE` als 20xx unter 80 und sonst als 19xx.
7. **Dynamische Bildschirmgröße** (2026-09-02): kein festes 80×25 — der
Bildschirm folgt der Terminalgröße (Mindestgröße 80×25, darunter nur
ein Hinweis). `CSRLIN`/`POS`/`LOCATE`/`VIEW PRINT` und das
`SCREEN`-Objekt arbeiten auf der tatsächlichen Größe. `WIDTH 40/80`
wird akzeptiert, ändert aber nichts (die Größe bestimmt das Terminal).
80×25 ist überall nur die **untere Schranke der Darstellung**, nie eine
Bildschirmgröße im Verhalten des Programms. Ändert sich die Größe
**während der Ausführung**, wird das zugestellt: der Inhalt bleibt oben
links erhalten, der Cursor wird in die neuen Grenzen geklemmt, und ein
`VIEW PRINT`-Bereich wird am unteren Rand geklemmt bzw. — wenn er ganz
vom Schirm fällt — auf den vollen Bildschirm zurückgesetzt.