Files
TerminalBasic/openspec/specs/sprach-frontend/spec.md

328 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sprach-frontend Specification
## Purpose
Das Sprach-Frontend übersetzt Terminal-Basic-Quelltext (VBDOS-kompatibler
Dialekt) in einen typgeprüften AST: Lexer, fehlertoleranter Parser und
semantische Analyse mit Diagnostik nach den Meldungstexten des Vorbilds.
## Requirements
### Requirement: Lexikalische Analyse nach Sprachreferenz
Der Lexer SHALL Quelltext gemäß docs/sprachreferenz.md §1 zerlegen:
Typ-Suffixe (`% & ! # $ @`) an Bezeichnern und Literalen, case-insensitive
Keywords (Bibliotheksnamen bleiben Bezeichner), `'`/`REM`-Kommentare,
`:`-Anweisungstrenner, Zeilenfortsetzung mit `_`, Hex- (`&H`) und
Oktal-Literale (`&O`), `""`-Escape in Strings sowie die Metabefehle
`'$INCLUDE`, `'$STATIC`, `'$DYNAMIC`.
#### Scenario: Suffix trennt Bibliotheksfunktion von Keyword
- **WHEN** der Lexer `STRING$` und `STRING` liest
- **THEN** wird `STRING$` als Bezeichner (Builtin) und `STRING` als Typ-Keyword erkannt
#### Scenario: Zeilenfortsetzung
- **WHEN** eine Zeile nach Leerraum mit `_` endet
- **THEN** wird die Folgezeile ohne Zeilenende-Token angeschlossen
### Requirement: Literal-Typisierung
Der Lexer SHALL numerische Literale typisieren: Ganzzahlen INTEGER → LONG
→ Gleitkomma nach Wertebereich; `E`-Exponent → SINGLE, `D`-Exponent →
DOUBLE; Suffixe erzwingen den Typ (Überlauf → Diagnose „Overflow");
Dezimalpunkt-Literale bis 7 signifikante Stellen → SINGLE, darüber →
DOUBLE (Projektentscheidung 2026-09-02); `@`-Literale als
Currency-Festkomma ×10 000.
#### Scenario: Schwelle SINGLE/DOUBLE
- **WHEN** die Literale `1.5` und `3.14159265` gelext werden
- **THEN** ist `1.5` SINGLE und `3.14159265` DOUBLE
### Requirement: Vollständige Anweisungs-Grammatik
Der Parser SHALL den Anweisungssatz des Dialekts in einen AST überführen,
darunter: Zuweisung inkl. `MID$`-Anweisung, `PRINT`/`LPRINT` (mit
`USING`/`#`-Datei, `?`-Kurzform), `INPUT`/`LINE INPUT`, `IF` (Block und
einzeilig, `THEN zeilennummer`), `SELECT CASE` (Werte, `TO`-Bereiche,
`IS`-Vergleiche, `CASE ELSE`), `FOR`/`DO`/`WHILE`-Schleifen mit `EXIT`,
`GOTO`/`GOSUB`/`RETURN`/`ON … GOTO|GOSUB`, `ON [LOCAL] ERROR`/`RESUME`,
`DIM`/`REDIM`/`ERASE`/`CONST`/`DEFtype`/`OPTION`/`TYPE…END TYPE`/
`DECLARE`/`SUB`/`FUNCTION`/`CALL`, `COMMON`/`SHARED`/`STATIC`,
`DATA`/`READ`/`RESTORE`, `DEF FN` (einzeilig und Blockform),
`VIEW PRINT`, `NAME … AS`, Ereignissteuerung (`TIMER/KEY(n)/UEVENT
ON|OFF|STOP`) sowie die komplette Datei-E/A-Grammatik (`OPEN` in beiden
Syntaxen inkl. `ISAM`-/`ACCESS`-/`LOCK`-Klauseln, `CLOSE`, `FIELD`,
`GET`/`PUT`, `LSET`/`RSET`, `WRITE`, `SEEK`, `LOCK`/`UNLOCK`).
Zeilennummern und Labels am Zeilenanfang SHALL als Sprungziele erfasst
werden.
#### Scenario: Blockstrukturen über mehrere Zeilen
- **WHEN** ein `SELECT CASE` mit `CASE 1: PRINT "eins"` und `CASE ELSE` geparst wird
- **THEN** entsteht ein Select-Knoten mit zwei Armen und Inline-Anweisungen im ersten Arm
#### Scenario: Fehlertoleranz je Anweisung
- **WHEN** eine Anweisung einen Syntaxfehler enthält
- **THEN** wird eine Diagnose mit Position gesammelt, bis zum Anweisungsende synchronisiert und die Folgeanweisung normal geparst
### Requirement: Semantische Prüfung
Die Semantik SHALL prüfen: Typkompatibilität (numerisch ↔ numerisch,
String ↔ String; sonst „Type mismatch"), implizite Deklaration bei
Erstverwendung (Suffix-getrennte Variablen), `DEFtype`-Buchstabenbereiche,
`OPTION EXPLICIT` („Variable not defined"), Arrays (implizit, `DIM`,
`REDIM`; doppelte explizite Deklaration → „Duplicate definition"),
UDT-Feldzugriffe (`kunde.name` mit Feldtyp; unbekanntes Feld → „Element
not defined"), `SHARED`-Import von Modulvariablen in Prozeduren,
Konstantenfaltung für `CONST` (nicht konstant → „Invalid constant"),
Sprungziele („Label not defined") sowie Prozedur- und Builtin-Signaturen
(Anzahl → „Argument-count mismatch", Typen).
#### Scenario: Typkonflikt
- **WHEN** `a$ = 1` geprüft wird
- **THEN** wird „Type mismatch" mit Quelltextposition gemeldet
#### Scenario: UDT-Feldtyp
- **WHEN** ein `TYPE` mit Feld `Name AS STRING * 30` deklariert ist und `k.Name = 5` geprüft wird
- **THEN** wird „Type mismatch" gemeldet
### Requirement: Compile-Zeit-Abweisung deklarierter Non-Features
Die Semantik MUST jede Verwendung der in docs/sprachreferenz.md
(„Abweichungen") deklarierten Non-Features — Hardware-Nähe (`PEEK`,
`POKE`, `INP`, `OUT`, `WAIT`, `BLOAD`/`BSAVE`, `VARPTR`-Familie, `CALL
ABSOLUTE`/`INTERRUPT`, `IOCTL`), `CHAIN`, die weiterhin ausgeschlossenen Grafik-Anweisungen (`PSET`,
`PRESET`, `CIRCLE`, `DRAW`, Grafik-`GET`/`PUT`, `PALETTE`, `WINDOW`,
`PMAP`, `PCOPY`, `POINT`), `SOUND`/
`PLAY` und die Event-Geräte `COM`/`PEN`/`PLAY`/`STRIG` — zur Compile-Zeit
mit der Meldung „Feature unavailable" abweisen. Dokumentierte Features
außerhalb dieser Liste MUST NOT abgewiesen werden (Guiding Principle).
Die dokumentierte Zellenpuffer-Untermenge SCREEN 013, LINE, PAINT und VIEW SHALL verfügbar bleiben.
#### Scenario: Hardware-Zugriff
- **WHEN** `POKE 100, 1` oder `x = PEEK(100)` kompiliert wird
- **THEN** erscheint die Compile-Diagnose „Feature unavailable"
#### Scenario: Erlaubte Grafikuntermenge
- **WHEN** ein Programm SCREEN 1, LINE, PAINT und VIEW in gültiger Syntax verwendet
- **THEN** wird es ohne Feature-unavailable-Diagnose übersetzt
### Requirement: Diagnostik mit Vorbild-Meldungen
Diagnosen SHALL exakte 1-basierte Quelltextpositionen (Zeile, Spalte)
tragen; die Meldungstexte SHALL dem Katalog des Vorbilds folgen, wo eine
Entsprechung existiert (z. B. „Type mismatch", „Duplicate definition",
„Label not defined", „Subprogram not defined", „Overflow").
Bei Projekten und Includes SHALL jede Diagnose den ursprünglichen Dateinamen mit dessen physischer Zeile und Spalte tragen.
#### Scenario: Position in der Diagnose
- **WHEN** in Zeile 3 ein Typkonflikt auftritt
- **THEN** nennt die Diagnose Zeile 3 und die Spalte des Ausdrucks
#### Scenario: Include-Diagnose
- **WHEN** ein eingebundenes Include einen Typfehler enthält
- **THEN** nennt die Diagnose die Include-Datei und ihre eigene Quellposition
### Requirement: Typisiertes HIR als Sema-Ausgabe
Die semantische Analyse SHALL neben den Diagnosen einen typisierten,
abgesenkten Zwischenbaum (HIR) liefern, der die Codegen-Eingabe bildet:
Namen sind zu Slot-/Tabellenindizes aufgelöst, jeder Ausdrucksknoten
trägt seinen Ergebnistyp, `CONST`-Werte sind gefaltet, `DEFtype`-Regeln
angewandt und implizite Konvertierungen als explizite
Konvertierungsknoten materialisiert. Bei Diagnosen mit Fehlercharakter
darf kein HIR für das betroffene Konstrukt garantiert werden; ein
diagnose-freies Modul MUST ein vollständiges HIR ergeben.
#### Scenario: Konvertierung wird materialisiert
- **WHEN** `d# = i% + 1.5#` semantisch analysiert wird
- **THEN** enthält das HIR einen expliziten Konvertierungsknoten INTEGER→DOUBLE unter dem Additionsknoten und die Zuweisung trägt den Zieltyp DOUBLE
#### Scenario: Diagnose-freies Modul liefert HIR
- **WHEN** ein Korpusmodul ohne Diagnosen analysiert wird
- **THEN** liefert das Frontend ein HIR, aus dem der Codegen ohne erneute Namens- oder Typauflösung Bytecode erzeugen kann
### Requirement: Jedes dokumentierte Element besitzt eine Signatur
Die Semantik SHALL für jedes im Sprachinventar geführte Element eine
Signatur besitzen (Argumentanzahl und -typen) — auch für Elemente, deren
Laufzeitverhalten noch aussteht. Ein im Inventar geführtes Element MUST
NOT als unbekannter Bezeichner oder mit einem generischen Syntaxfehler
abgewiesen werden; die Diagnose bzw. der Laufzeitfehler MUST es
namentlich nennen (Guiding Principle). Das gilt insbesondere für die
Gruppen, die bisher gar keine Signatur haben: die finanzmathematischen
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"
#### Scenario: Argumentprüfung ohne Laufzeitverhalten
- **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
einer Prozedur steht und das Label auf Modulebene liegt. Nur
`ON LOCAL ERROR GOTO label` SHALL prozedurlokale Handler-Auflösung
verlangen. Ein modulweites `ON ERROR` in einer Prozedur MUST NOT wie
`ON LOCAL ERROR` behandelt werden, und ein auf Modulebene vorhandenes
Label MUST NOT als „Label not defined" gemeldet werden.
#### Scenario: Handler auf Modulebene aus einer Prozedur
- **WHEN** ein Modul das Label `Fehler:` auf Modulebene führt und eine `SUB` die Anweisung `ON ERROR GOTO Fehler` enthält
- **THEN** wird das Label aufgelöst und es entsteht keine Diagnose
#### Scenario: ON LOCAL ERROR bleibt prozedurlokal
- **WHEN** eine `SUB` die Anweisung `ON LOCAL ERROR GOTO Fehler` enthält und `Fehler:` nur auf Modulebene existiert
- **THEN** wird „Label not defined" gemeldet
### Requirement: DATA-Elemente behalten ihren Rohtext
Unquotierte Elemente einer `DATA`-Anweisung SHALL exakt so erhalten
bleiben, wie sie im Quelltext stehen — insbesondere in ihrer Groß- und
Kleinschreibung. Die Normalisierung von Bezeichnern MUST NOT auf
`DATA`-Elemente wirken. Führender und nachgestellter Leerraum eines
unquotierten Elements SHALL entfernt werden, innenliegender erhalten
bleiben.
#### Scenario: Gemischte Schreibweise bleibt erhalten
- **WHEN** ein Modul `DATA Hallo Welt` enthält und `READ s$` ausgeführt wird
- **THEN** enthält `s$` den Text `Hallo Welt` mit unveränderter Groß- und Kleinschreibung
#### Scenario: Leerraum an den Rändern
- **WHEN** ein Modul `DATA ab cd ` enthält und gelesen wird
- **THEN** lautet der Wert `ab cd`
### Requirement: ISAM-Anweisungen und -Funktionen in Grammatik und Signaturprüfung
Der Parser SHALL die ISAM-Anweisungen und -Funktionen des Vorbilds als
eigene Konstrukte erkennen, und die Semantik SHALL für jede eine Signatur
führen (Argumentanzahl und -typen). Der maßgebliche Umfang ergibt sich
aus dem Sprachinventar; er umfasst mindestens `CREATEINDEX`,
`DELETEINDEX`, `SETINDEX`, `GETINDEX$`, `INSERT`, `RETRIEVE`, `UPDATE`,
`DELETE`, `DELETETABLE`, `MOVEFIRST`, `MOVELAST`, `MOVENEXT`,
`MOVEPREVIOUS`, `SEEKEQ`, `SEEKGT`, `SEEKGE`, `BEGINTRANS`,
`COMMITTRANS`, `ROLLBACK`, `ROLLBACK ALL`, `SAVEPOINT`, `SETMEM` und
`BOF`. Eine fehlerhafte Verwendung MUST eine Diagnose erzeugen, die
das Element namentlich nennt; ein ISAM-Element MUST NOT als unbekannter
Bezeichner oder mit einem generischen Syntaxfehler abgewiesen werden
(Guiding Principle).
#### Scenario: ISAM-Anweisung wird als solche erkannt
- **WHEN** ein Modul `SETINDEX #1, "NachName"` enthält
- **THEN** entsteht keine Diagnose „unbekannter Bezeichner" und die Anweisung wird als ISAM-Anweisung erfasst
#### Scenario: Falsche Argumentanzahl wird namentlich gemeldet
- **WHEN** ein Modul `SEEKGT #1` ohne Schlüsselwert enthält
- **THEN** nennt die Diagnose `SEEKGT` und die erwartete Argumentanzahl
#### Scenario: Typprüfung des Satzarguments
- **WHEN** `RETRIEVE #1, x$` mit einem String statt einer Variablen des Tabellentyps geprüft wird
- **THEN** wird „Type mismatch" mit Quelltextposition gemeldet
### Requirement: Ereignis-Traps in Grammatik und Signaturprüfung
Der Parser SHALL die Trap-Anweisungen des Vorbilds als eigene Konstrukte
erkennen: `ON TIMER(n) GOSUB ziel`, `ON KEY(n) GOSUB ziel`,
`ON UEVENT GOSUB ziel` und `ON SIGNAL(n) GOSUB ziel`. Diese Formen MUST
NOT als berechnetes `ON ausdruck GOSUB liste` behandelt werden. Die
Steueranweisungen `TIMER`, `KEY(n)`, `UEVENT` und `SIGNAL(n)` mit `ON`,
`OFF` oder `STOP` SHALL erkannt und nur für diese Quellen zugelassen
werden; `EVENT` SHALL nur `ON` und `OFF` zulassen, weil das Vorbild
keinen dritten Zustand kennt. `COM`, `PEN`, `PLAY` und `STRIG` MUST als
Non-Feature namentlich abgewiesen werden. `SetUEvent` SHALL eine
Signatur besitzen. Die Wertebereiche SHALL geprüft werden: `ON KEY(n%)`
mit `n%` aus 0, 125 oder 3031, `ON TIMER(n&)` mit `n&` aus 186 400
und `ON SIGNAL(n%)` mit `n%` aus 12. Eine fehlerhafte Verwendung MUST
eine Diagnose erzeugen, die das Element namentlich nennt.
#### Scenario: ON TIMER ist kein berechnetes GOSUB
- **WHEN** ein Modul `ON TIMER(5) GOSUB Marke` enthält
- **THEN** wird die Anweisung als Zeit-Trap erfasst und nicht als `ON ausdruck GOSUB` mit einer einelementigen Zielliste
#### Scenario: Ungültige Kennung wird namentlich gemeldet
- **WHEN** ein Modul `ON KEY(99) GOSUB Marke` enthält
- **THEN** nennt die Diagnose `ON KEY` und den zulässigen Wertebereich
#### Scenario: EVENT kennt kein STOP
- **WHEN** ein Modul `EVENT STOP` enthält
- **THEN** nennt die Diagnose `EVENT` und die zulässigen Formen `ON` und `OFF`
#### Scenario: Zeitintervall außerhalb des Bereichs
- **WHEN** ein Modul `ON TIMER(90000) GOSUB Marke` enthält
- **THEN** nennt die Diagnose `ON TIMER` und den Bereich 1 bis 86400
#### Scenario: Non-Feature-Quelle wird abgewiesen
- **WHEN** ein Modul `ON PEN GOSUB Marke` enthält
- **THEN** meldet die Übersetzung „Feature unavailable" an der Fundstelle der Anweisung — wie bei jedem anderen Non-Feature — und nicht einen generischen Syntaxfehler
#### Scenario: SetUEvent besitzt eine Signatur
- **WHEN** ein Modul `CALL SetUEvent` enthält
- **THEN** entsteht keine Diagnose „Subprogram not defined"
### Requirement: Trap-Ziele sind Sprungziele des Moduls
Das Ziel eines Ereignis-Traps SHALL wie ein `GOSUB`-Ziel aufgelöst
werden. Ein unbekanntes Ziel MUST zur Übersetzungszeit gemeldet werden
und nennt dabei das Label.
#### Scenario: Unbekanntes Trap-Ziel
- **WHEN** ein Modul `ON TIMER(1) GOSUB Fehlt` ohne Label `Fehlt` enthält
- **THEN** meldet die Übersetzung ein undefiniertes Sprungziel und nennt `Fehlt`
### Requirement: Objektzugriffe in Grammatik und Semantik
Der Parser SHALL Punktpfade auf Formulare und Steuerelemente
(`objekt.eigenschaft`), den Container-Operator
`form!steuerelement[.eigenschaft]`, `IF TYPEOF ausdruck IS klasse`, den
Metabefehl `$FORM` und die Datentypen `FORM` und `CONTROL` als
Parametertypen erkennen. Die Semantik SHALL Eigenschaften gegen die
Klasse des Objekts prüfen und ihren Typ bestimmen. Ein Punktpfad, dessen
Basis weder ein benutzerdefinierter Typ noch ein bekanntes Objekt ist,
MUST eine Diagnose erzeugen, die Basis und Eigenschaft namentlich nennt;
er MUST NOT zu einer impliziten Variablen mit Punkt im Namen werden.
Gleichnamige Steuerelemente unterschiedlicher Formulare SHALL im jeweiligen Formularnamensraum aufgelöst werden; die explizite Containerangabe SHALL vor der lokalen Namenssuche wirken.
#### Scenario: Unbekanntes Objekt wird benannt
- **WHEN** ein Modul `Text9.Text = "a"` ohne Steuerelement `Text9` enthält
- **THEN** nennt die Diagnose `Text9` als unbekanntes Objekt statt „Type mismatch" zu melden
#### Scenario: Unbekannte Eigenschaft wird benannt
- **WHEN** ein Modul `Text1.Farbe = 3` enthält
- **THEN** nennt die Diagnose `Farbe` als unbekannte Eigenschaft von `TextBox`
#### Scenario: Eigenschaftstyp wird geprüft
- **WHEN** ein Modul `Text1.Text = 5` enthält
- **THEN** meldet die Semantik einen Typkonflikt mit Nennung der Eigenschaft
#### Scenario: TYPEOF
- **WHEN** ein Modul `IF TYPEOF Ziel IS CommandButton THEN` enthält
- **THEN** wird es übersetzt und die Klasse gegen die bekannten Steuerelementklassen geprüft
#### Scenario: Gleichnamige Controls verschiedener Formulare
- **WHEN** Form1 und Form2 jeweils Text1 enthalten und Form2!Text1.Text gelesen wird
- **THEN** wird Text1 in Form2 aufgelöst und das Programm übersetzt diagnosefrei
### Requirement: Bang-Operator neben dem SINGLE-Suffix
Der Lexer SHALL `!` als Container-Operator erkennen, wenn ihm ein
Bezeichner folgt, und andernfalls als Typsuffix für SINGLE. Beide Formen
MUST im selben Modul nebeneinander verwendbar sein.
#### Scenario: Beide Bedeutungen im selben Modul
- **WHEN** ein Modul `Wert! = 1.5` und `Form1!Text1.Text = "a"` enthält
- **THEN** wird die erste Zeile als SINGLE-Variable und die zweite als Containerzugriff übersetzt
### Requirement: Ereignisprozeduren mit festen Signaturen
Die Semantik SHALL eine `SUB`, deren Name der Form `Objekt_Ereignis`
entspricht und deren Objekt bekannt ist, als Ereignisprozedur erkennen
und ihre Parameterliste gegen die Signatur der Forms-Referenz prüfen.
Eine abweichende Parameterliste MUST namentlich gemeldet werden. Eine
`SUB` mit Unterstrich im Namen ohne bekanntes Objekt MUST weiterhin eine
gewöhnliche Prozedur sein.
#### Scenario: Falsche Signatur
- **WHEN** ein Modul `SUB Form_MouseDown (X AS SINGLE)` enthält
- **THEN** nennt die Diagnose die erwartete Parameterliste der Ereignisprozedur
#### Scenario: Gewöhnliche SUB mit Unterstrich
- **WHEN** ein Modul `SUB Zins_Berechnen (n AS INTEGER)` ohne Objekt `Zins` enthält
- **THEN** wird sie als gewöhnliche Prozedur übersetzt