Files
TerminalBasic/crates/tb-frontend/src/hir.rs

717 lines
16 KiB
Rust
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.
//! 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<NumTy> {
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<HParam>,
/// Alle Frame-Slots; `params.len()` erste Einträge sind die Parameter.
pub locals: Vec<HVar>,
/// Slot der Rückgabevariablen (Function/DefFn).
pub ret_slot: Option<VarSlot>,
pub ret_ty: Option<HTy>,
pub body: Vec<HStmt>,
/// 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<String>,
pub key: String,
pub ty: HTy,
pub dims: Option<Vec<(Option<i32>, Option<i32>)>>,
pub pos: crate::SourcePos,
}
#[derive(Debug)]
pub struct HirModule {
pub name: String,
pub globals: Vec<HVar>,
pub commons: Vec<HCommon>,
pub udts: Vec<HUdt>,
/// Prozeduren; Index 0 ist das Hauptprogramm.
pub procs: Vec<HProc>,
pub data: Vec<DataItem>,
pub option_base: u8,
/// Zur Übersetzungszeit bekannte Forms-Objekte in Indexreihenfolge.
pub objects: Vec<crate::forms::FormObject>,
/// Ereignisprozeduren: Objektindex, Ereignisname, Prozedurindex.
pub event_procs: Vec<HEventProc>,
}
#[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<HExpr>,
/// UDT-Feldpfad (Feldindizes je Ebene).
pub fields: Vec<u16>,
/// 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<HPlace>),
ObjectProperty {
object: u16,
index: Option<Box<HExpr>>,
property: u16,
ty: HTy,
},
ObjectIndexedProperty {
object: u16,
object_index: Option<Box<HExpr>>,
property: u16,
index: Box<HExpr>,
ty: HTy,
},
ObjectMethodCall {
object: u16,
index: Option<Box<HExpr>>,
method: u16,
args: Vec<HExpr>,
ty: HTy,
},
DynamicObjectProperty {
object: Box<HExpr>,
property: String,
},
ObjectRef {
object: u16,
index: Option<Box<HExpr>>,
class: crate::forms::ObjectClass,
},
TypeOf {
value: Box<HExpr>,
class: crate::forms::ObjectClass,
},
/// Numerische Konvertierung nach Matrix (Rundung/Überlauf).
Conv {
from: NumTy,
to: NumTy,
arg: Box<HExpr>,
},
/// Kürzen/Padden auf feste Stringlänge.
FixStr {
len: u32,
arg: Box<HExpr>,
},
Neg {
ty: NumTy,
arg: Box<HExpr>,
},
/// 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<HExpr>,
r: Box<HExpr>,
},
Not {
ty: IntKind,
arg: Box<HExpr>,
},
Logic {
op: HLogic,
ty: IntKind,
l: Box<HExpr>,
r: Box<HExpr>,
},
/// Vergleich; Ergebnis ist INTEGER (1/0).
Cmp {
op: HCmp,
ty: CmpKind,
l: Box<HExpr>,
r: Box<HExpr>,
},
Concat(Box<HExpr>, Box<HExpr>),
/// FUNCTION-/DEF FN-Aufruf.
FnCall {
proc: u16,
args: Vec<HArg>,
ret: HTy,
},
Builtin {
b: Builtin,
args: Vec<HExpr>,
ret: HTy,
},
/// LBOUND/UBOUND eines Arrays.
ArrayBound {
lower: bool,
place: Box<HPlace>,
dim: Box<HExpr>,
},
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<HExpr>,
property: u16,
value: HExpr,
},
SetObjectIndexedProperty {
object: u16,
object_index: Option<HExpr>,
property: u16,
index: HExpr,
value: HExpr,
},
SetDynamicObjectProperty {
object: HExpr,
property: String,
value: HExpr,
},
ObjectMethod {
object: u16,
index: Option<HExpr>,
method: u16,
args: Vec<HExpr>,
},
ObjectLoad {
object: u16,
index: Option<HExpr>,
unload: bool,
},
Print {
items: Vec<HPrintItem>,
/// 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<HExpr>,
var: Option<HPlace>,
},
Input {
/// `INPUT #n` — Quelle ist eine Datei statt der Tastatur.
file: Option<HExpr>,
line_mode: bool,
prompt: Option<String>,
/// Fragezeichen nach dem Prompt (`;`-Form).
question: bool,
targets: Vec<HPlace>,
},
If {
cond: HExpr,
then: Vec<HStmt>,
els: Vec<HStmt>,
},
/// 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<HStmt>,
exit_label: LabelId,
},
For {
end_pos: crate::SourcePos,
var: HPlace,
ty: NumTy,
from: HExpr,
to: HExpr,
step: Option<HExpr>,
/// Versteckte Slots für Grenze/Schritt (Schritt nur wenn dynamisch).
limit_slot: VarSlot,
step_slot: Option<VarSlot>,
body: Vec<HStmt>,
exit_label: LabelId,
},
Goto(LabelId),
Gosub(LabelId),
OnGoto {
sel: HExpr,
gosub: bool,
targets: Vec<LabelId>,
},
ReturnGosub(Option<LabelId>),
Run {
target: Option<HExpr>,
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<LabelId>,
},
/// `<quelle>(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<HArg>,
},
BuiltinStmt {
b: Builtin,
args: Vec<HExpr>,
},
OnError {
local: bool,
/// `None` = `GOTO 0` (deaktivieren).
target: Option<LabelId>,
},
OnErrorResumeNext {
local: bool,
},
Resume(HResume),
/// `ERROR n`.
RaiseError(HExpr),
/// `ERR = n` — Fehlercode setzen, ohne einen Fehler auszulösen.
SetErr(HExpr),
Read(Vec<HPlace>),
/// 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<VarSlot>),
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<f64> {
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,
}
}