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

17 KiB
Raw Blame History

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, Grafik-Anweisungen, 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).

Scenario: Hardware-Zugriff

  • WHEN POKE 100, 1 oder x = PEEK(100) kompiliert wird
  • THEN erscheint die Compile-Diagnose „Feature unavailable"

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