//! Typisiertes, abgesenktes HIR — die Ausgabe der semantischen Analyse //! und Eingabe des Codegenerators (`tb-vm`). //! //! Eigenschaften (siehe Design der Phase-2-Änderung): //! - Namen sind aufgelöst: Variablen sind Slot-Indizes (global/lokal), //! Prozeduren und UDTs Tabellenindizes, Sprungziele `LabelId`s je Rumpf. //! - Jeder Ausdrucksknoten trägt seinen Ergebnistyp; implizite //! Konvertierungen sind als explizite `Conv`-Knoten materialisiert //! (Semantik: Konvertierungsmatrix in docs/tbvm-design.md). //! - Kontrollzucker ist abgesenkt: `SELECT CASE` zu Vergleichsketten, //! `ELSEIF` zu verschachteltem `If`, `EXIT FOR/DO` zu `Goto` auf //! synthetisierte Labels, `SWAP` zu Zuweisungen über einen Temp-Slot. //! - `STATIC`-Locals und versteckte Temps liegen im globalen Slot-Bereich. //! //! Ein vollständiges, korrektes HIR ist nur bei diagnose-freier Analyse //! garantiert. /// Numerischer Skalartyp (Kürzel wie im Opcode-Satz: I2/I4/CY/R4/R8). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NumTy { Int, Lng, Cur, Sng, Dbl, } /// Ganzzahlbreite der Logik-Operatoren. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IntKind { I2, I4, } /// Aufgelöster HIR-Typ. #[derive(Debug, Clone, PartialEq)] pub enum HTy { Num(NumTy), Str, /// Fester String mit Zeichenlänge (Zuweisung padded/kürzt). FixedStr(u32), /// Benutzerdefinierter Typ (Index in `HirModule::udts`). Udt(u16), Form, Control, } impl HTy { pub fn num(&self) -> Option { match self { HTy::Num(n) => Some(*n), _ => None, } } pub fn is_str(&self) -> bool { matches!(self, HTy::Str | HTy::FixedStr(_)) } } /// Slot-Referenz: globaler Bereich (Modulvariablen, STATICs, versteckte /// Temps des Hauptprogramms) oder Frame-lokal (Parameter zuerst). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VarSlot { Global(u16), Local(u16), } /// Sprungziel innerhalb eines Prozedurrumpfs. pub type LabelId = u16; /// Variablen-/Slotbeschreibung (auch für die Debugger-Inspektion). #[derive(Debug, Clone)] pub struct HVar { pub name: String, pub ty: HTy, pub array: bool, } #[derive(Debug, Clone)] pub struct HUdt { pub name: String, pub fields: Vec<(String, HTy)>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HProcKind { /// Hauptprogramm (Modulrumpf) — immer Prozedur 0. Main, Sub, Function, /// `DEF FN` — Parameter BYVAL, freie Namen binden an Modulvariablen. DefFn, } #[derive(Debug, Clone)] pub struct HParam { pub name: String, pub ty: HTy, pub array: bool, /// Skalar-Parameter, der als Referenz übergeben wird (Arrays und /// UDTs sind implizit immer Referenzen auf ihr Handle). pub by_ref: bool, } #[derive(Debug)] pub struct HProc { pub name: String, pub kind: HProcKind, pub params: Vec, /// Alle Frame-Slots; `params.len()` erste Einträge sind die Parameter. pub locals: Vec, /// Slot der Rückgabevariablen (Function/DefFn). pub ret_slot: Option, pub ret_ty: Option, pub body: Vec, /// Anzahl vergebener LabelIds in diesem Rumpf. pub label_count: u16, } /// Eine DATA-Konstante (unkonvertiert; `READ` konvertiert zur Laufzeit). #[derive(Debug, Clone)] pub struct DataItem { pub text: String, pub line: u32, } /// Aufgelöste COMMON-Deklaration für die projektweite Slotverknüpfung. #[derive(Debug, Clone)] pub struct HCommon { pub slot: u16, pub block: Option, pub key: String, pub ty: HTy, pub dims: Option, Option)>>, pub pos: crate::SourcePos, } #[derive(Debug)] pub struct HirModule { pub name: String, pub globals: Vec, pub commons: Vec, pub udts: Vec, /// Prozeduren; Index 0 ist das Hauptprogramm. pub procs: Vec, pub data: Vec, pub option_base: u8, /// Zur Übersetzungszeit bekannte Forms-Objekte in Indexreihenfolge. pub objects: Vec, /// Ereignisprozeduren: Objektindex, Ereignisname, Prozedurindex. pub event_procs: Vec, } #[derive(Debug, Clone)] pub struct HEventProc { pub object: u16, pub event: String, pub proc: u16, } // ---- Ausdrücke ------------------------------------------------------------- /// L-Wert: Basis-Slot, optional Array-Indizes, optional UDT-Feldpfad. #[derive(Debug, Clone)] pub struct HPlace { pub base: VarSlot, /// Skalar-BYREF-Parameter: der Slot enthält eine Referenz. pub base_is_ref: bool, /// Array-Elementzugriff (leer = Skalar bzw. ganzes Array). pub indices: Vec, /// UDT-Feldpfad (Feldindizes je Ebene). pub fields: Vec, /// Typ des adressierten Werts. pub ty: HTy, /// Element-/Basistyp und Dimension für Auto-DIM impliziter Arrays. pub array_elem: Option<(HTy, u8)>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HArith { Add, Sub, Mul, Div, IDiv, Mod, Pow, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HCmp { Eq, Ne, Lt, Le, Gt, Ge, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HLogic { And, Or, Xor, Eqv, Imp, } /// Vergleichs-Operandentyp. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CmpKind { Num(NumTy), Str, } /// Bibliotheksfunktionen/-anweisungen der Phase-2-Scheibe. Der /// Diskriminant ist zugleich der stabile Index der Dispatch-Tabelle /// (`CALL_BUILTIN`); Phase 3 erweitert ausschließlich am Ende. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum Builtin { // Strings Len, LeftS, RightS, MidS, InstrF, UcaseS, LcaseS, LtrimS, RtrimS, SpaceS, StringS, ChrS, Asc, StrS, Val, HexS, OctS, /// MID$-Anweisung als reine Funktion: (ziel, start, länge, ersatz) → neuer String. MidAssign, // Mathematik Abs, Sgn, IntF, Fix, Sqr, Exp, Log, Sin, Cos, Tan, Atn, Rnd, Randomize, // Konsole (PRINT-Familie; Wirkung über Host + Druckspalten-Zustand) PrintVal, PrintStrLit, PrintComma, PrintTab, PrintSpc, PrintNewline, PrintUsing, FormatS, SetFormatCc, // Bildschirm (Anweisungen) Cls, Color, Locate, Width, ViewPrint, ScreenStmt, KeyAssign, KeyList, KeyDisplay, // Bildschirm (Funktionen) Csrlin, PosFn, ScreenFn, // Tastatur InkeyS, InputS, // System EnvironS, EnvironSet, Fre, Clear, Tron, Troff, StackFn, StackStmt, Erdev, ErdevS, // Datei-E/A Open, Close, CloseAll, PrintZiel, WriteFile, EofF, LofF, LocF, SeekF, SeekStmt, Freefile, Fileattr, LockStmt, Kill, NameStmt, Files, Chdir, Chdrive, Mkdir, Rmdir, CurdirS, DirS, Lpos, ShellStmt, ShellFn, MkS, CvF, // Finanzmathematik Fv, Pv, Pmt, NPer, IPmt, PPmt, Rate, Npv, Irr, Mirr, Sln, Syd, Ddb, // Sonstiges Timer, DateS, TimeS, DateSet, TimeSet, Now, TimezoneKnown, DateSerial, TimeSerial, DateValue, TimeValue, DayF, MonthF, YearF, WeekdayF, HourF, MinuteF, SecondF, CommandS, Doevents, Sleep, /// `SetUEvent` — löst das benutzerdefinierte Ereignis aus (§8). SetUEvent, Beep, // ISAM (Change `phase-3-isam`) IsamOpen, IsamCreateIndex, IsamDeleteIndex, IsamSetIndex, IsamGetIndexS, IsamInsert, IsamRetrieve, IsamUpdate, IsamDelete, IsamDeleteTable, IsamMoveFirst, IsamMoveLast, IsamMoveNext, IsamMovePrevious, IsamSeekEq, IsamSeekGt, IsamSeekGe, IsamBeginTrans, IsamCommitTrans, IsamRollback, IsamSavepoint, IsamSetmem, IsamBof, MsgBox, InputBoxS, ClipboardAdd, ClipboardGet, GraphicsLine, GraphicsPaint, GraphicsView, } #[derive(Debug, Clone)] pub enum HExpr { Int(i16), Lng(i32), /// TYPE-Tabellenreferenz für ISAM; beim Projektlinken zu versetzen. UdtId(u16), Sng(f32), Dbl(f64), Cur(i64), Str(String), Load(Box), ObjectProperty { object: u16, index: Option>, property: u16, ty: HTy, }, ObjectIndexedProperty { object: u16, object_index: Option>, property: u16, index: Box, ty: HTy, }, ObjectMethodCall { object: u16, index: Option>, method: u16, args: Vec, ty: HTy, }, DynamicObjectProperty { object: Box, property: String, }, ObjectRef { object: u16, index: Option>, class: crate::forms::ObjectClass, }, TypeOf { value: Box, class: crate::forms::ObjectClass, }, /// Numerische Konvertierung nach Matrix (Rundung/Überlauf). Conv { from: NumTy, to: NumTy, arg: Box, }, /// Kürzen/Padden auf feste Stringlänge. FixStr { len: u32, arg: Box, }, Neg { ty: NumTy, arg: Box, }, /// Monomorphe Arithmetik: beide Operanden und das Ergebnis haben `ty` /// (bei `Div`/`Pow` nur R4/R8, bei `IDiv`/`Mod` nur I2/I4). Bin { op: HArith, ty: NumTy, l: Box, r: Box, }, Not { ty: IntKind, arg: Box, }, Logic { op: HLogic, ty: IntKind, l: Box, r: Box, }, /// Vergleich; Ergebnis ist INTEGER (−1/0). Cmp { op: HCmp, ty: CmpKind, l: Box, r: Box, }, Concat(Box, Box), /// FUNCTION-/DEF FN-Aufruf. FnCall { proc: u16, args: Vec, ret: HTy, }, Builtin { b: Builtin, args: Vec, ret: HTy, }, /// LBOUND/UBOUND eines Arrays. ArrayBound { lower: bool, place: Box, dim: Box, }, Err, Erl, /// Dokumentiertes, aber erst in einer späteren Phase implementiertes /// Feature: löst zur Laufzeit Fehler 73 „Advanced feature" aus. Unsupported(&'static str), } /// Prozedurargument. #[derive(Debug, Clone)] pub enum HArg { ByRef(HPlace), /// Ganzes Array als Referenz (`prozedur a()`). ArrayRef(HPlace), ByVal(HExpr), } // ---- Anweisungen ----------------------------------------------------------- #[derive(Debug, Clone)] pub enum HPrintItem { Val(HExpr), Tab(HExpr), Spc(HExpr), Comma, } #[derive(Debug, Clone)] pub enum HResume { Retry, Next, Label(LabelId), } #[derive(Debug)] pub struct HStmt { pub pos: crate::SourcePos, pub line: u32, pub kind: HStmtKind, } #[derive(Debug)] pub enum HStmtKind { /// Numerische Zeilennummer durchlaufen (setzt `ERL`). SetErl(u32), Label(LabelId), Assign { place: HPlace, value: HExpr, }, SetObjectProperty { object: u16, index: Option, property: u16, value: HExpr, }, SetObjectIndexedProperty { object: u16, object_index: Option, property: u16, index: HExpr, value: HExpr, }, SetDynamicObjectProperty { object: HExpr, property: String, value: HExpr, }, ObjectMethod { object: u16, index: Option, method: u16, args: Vec, }, ObjectLoad { object: u16, index: Option, unload: bool, }, Print { items: Vec, /// Endet die Anweisung mit `;`/`,` (kein Zeilenumbruch)? trailing: bool, }, /// `FIELD #n, laenge AS var$, …` — Recordpuffer in Felder aufteilen. Field { file: HExpr, fields: Vec<(HExpr, HPlace)>, }, /// `LSET`/`RSET` — links- bzw. rechtsbündig zuweisen; ist das Ziel ein /// `FIELD`-Feld, wirkt die Zuweisung zugleich auf den Recordpuffer. LsetRset { rset: bool, target: HPlace, value: HExpr, }, /// `GET`/`PUT` auf einer Datei: Datensatz- bzw. Bytenummer und Ziel- /// bzw. Quellvariable. Ohne Variable wirkt der Recordpuffer. GetPut { put: bool, file: HExpr, recnum: Option, var: Option, }, Input { /// `INPUT #n` — Quelle ist eine Datei statt der Tastatur. file: Option, line_mode: bool, prompt: Option, /// Fragezeichen nach dem Prompt (`;`-Form). question: bool, targets: Vec, }, If { cond: HExpr, then: Vec, els: Vec, }, /// DO/LOOP, WHILE/WEND (nur `pre`) — Bedingungen: (ist_until, Ausdruck). Loop { end_pos: crate::SourcePos, pre: Option<(bool, HExpr)>, post: Option<(bool, HExpr)>, body: Vec, exit_label: LabelId, }, For { end_pos: crate::SourcePos, var: HPlace, ty: NumTy, from: HExpr, to: HExpr, step: Option, /// Versteckte Slots für Grenze/Schritt (Schritt nur wenn dynamisch). limit_slot: VarSlot, step_slot: Option, body: Vec, exit_label: LabelId, }, Goto(LabelId), Gosub(LabelId), OnGoto { sel: HExpr, gosub: bool, targets: Vec, }, ReturnGosub(Option), Run { target: Option, string: bool, }, /// Ereignis-Trap erklären (Sprachreferenz §8). `art` ist die Quelle /// (0 KEY, 1 TIMER, 2 UEVENT, 3 SIGNAL), `index` ihre Kennung bzw. bei /// `TIMER` das Intervall in Sekunden. `ziel = None` = `GOSUB 0`. TrapDef { art: u8, index: HExpr, ziel: Option, }, /// `(n) ON|OFF|STOP` — `zustand` 0 An, 1 Aus, 2 Gestoppt. TrapSet { art: u8, index: HExpr, zustand: u8, }, /// `EVENT ON` / `EVENT OFF`. EventSwitch(bool), /// EXIT SUB/FUNCTION/DEF bzw. Rumpfende. ExitProc, CallSub { proc: u16, args: Vec, }, BuiltinStmt { b: Builtin, args: Vec, }, OnError { local: bool, /// `None` = `GOTO 0` (deaktivieren). target: Option, }, OnErrorResumeNext { local: bool, }, Resume(HResume), /// `ERROR n`. RaiseError(HExpr), /// `ERR = n` — Fehlercode setzen, ohne einen Fehler auszulösen. SetErr(HExpr), Read(Vec), /// Ziel als Index in `HirModule::data` (0 = Anfang). Restore(u32), /// DIM/REDIM eines Arrays: Grenzen (lo, hi) je Dimension. Dim { slot: VarSlot, elem: HTy, dims: Vec<(HExpr, HExpr)>, redim: bool, }, Erase(Vec), End, Stop, System, /// Dokumentiertes Feature einer späteren Phase → Laufzeitfehler 73. Unsupported(&'static str), } /// Konstanter Literalwert eines Ausdrucks (z. B. FOR-STEP-Erkennung im /// Codegen: konstanter Schritt braucht keinen versteckten Slot). pub fn literal_value(e: &HExpr) -> Option { match e { HExpr::Int(v) => Some(*v as f64), HExpr::Lng(v) => Some(*v as f64), HExpr::Sng(v) => Some(*v as f64), HExpr::Dbl(v) => Some(*v), HExpr::Cur(v) => Some(*v as f64 / 10_000.0), HExpr::Conv { arg, .. } => literal_value(arg), HExpr::Neg { arg, .. } => literal_value(arg).map(|v| -v), _ => None, } }