Projektmodule und vollständiges TBC-Kompilat umsetzen und Change archivieren

This commit is contained in:
2026-09-05 23:06:56 +02:00
parent 58b1f620ea
commit 815825dde7
40 changed files with 4177 additions and 555 deletions

View File

@@ -253,6 +253,10 @@ Das Dateiformat bleibt Version 1.
## Kompilat: `.tbc` (neu, eigenes Format)
Container für TBVM-Bytecode, Entwurf in [tbvm-design.md](tbvm-design.md).
`tbc build` erzeugt wahlweise `.tbc` oder ein eigenständiges Executable
(Runner + eingebettetes `.tbc`).
Version 4 ist ein eigenständig ausführbarer Bytecode-Container; das vollständige
Format steht in [tbvm-design.md](tbvm-design.md). `tbc build app.mak` erzeugt
`app.tbc`, `tbc run app.tbc` führt es ohne BAS-/FRM-/MAK-/Include-Quellen aus.
Quellorte, Prozedursignaturen und Formular-Anfangswerte einschließlich
Designzeit-Control-Arrays bleiben erhalten. Ältere Versionen 13 und unbekannte
Versionen werden mit Versionsangabe abgewiesen. Native Executable-Verpackung
ist noch nicht implementiert.

View File

@@ -31,6 +31,21 @@ einschließlich breiter Zeichen und Bildschirmränder. Die erneute Verifikation
hat keine offenen Befunde im Change: 428 Workspace-Tests und alle vier
Review-Proben bestehen; eine Bildschirmmatrix deckt 3.360 Kombinationen ab.
## Umsetzungsstand: Projektmodule und Kompilat
F09, F10, F11 und F24 sind im archivierten Change
[projektmodule-und-kompilat](../../openspec/changes/archive/2026-09-05-projektmodule-und-kompilat/proposal.md)
umgesetzt. Die zunächst sechs offenen Reviewbefunde und alle bei der
Nachprüfung zusätzlich reproduzierten Abweichungen sind behoben.
Die [abschließende Verifikation](../../openspec/changes/archive/2026-09-05-projektmodule-und-kompilat/review.md)
hat keine offenen Befunde im Change: 453 Workspace-Tests, alle sieben
unveränderten Reviewproben, Clippy, Format-/Spec-Prüfung und das echte
Mehrmodul-Compile-Budget bestehen. Alle zwölf Tasks sind abgeschlossen;
Die Hauptspecs wurden synchronisiert und der Change am 05.09.2026
archiviert. Details und
Szenariozuordnung: [Implementierungsnachweis](../../openspec/changes/archive/2026-09-05-projektmodule-und-kompilat/verification.md).
Die folgenden Befundtexte bleiben die historische Bestandsaufnahme.
## Prüfmethode und Grenzen
- Alle Requirements/Szenarien gelesen und gegen Einstiegspunkte, Implementierung und vorhandene Tests abgeglichen. Die Matrix unten enthält jeden Requirement-Titel einmal.
@@ -113,7 +128,7 @@ Zwei Formulare mit je Text1 lassen sich laden, aber Form2!Text1.Text erzeugt Con
Beleg: [crates/tb-frontend/src/sema.rs:949](../../crates/tb-frontend/src/sema.rs#L949). Vertrag: [openspec/specs/forms-objektmodell/spec.md:14](../../openspec/specs/forms-objektmodell/spec.md#L14), [openspec/specs/forms-objektmodell/spec.md:36](../../openspec/specs/forms-objektmodell/spec.md#L36), [openspec/specs/sprach-frontend/spec.md:251](../../openspec/specs/sprach-frontend/spec.md#L251).
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md).
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/archive/2026-09-05-projektmodule-und-kompilat/proposal.md).
### F10 — Quellorte und Debuggerziele verlieren die Modulidentität (hoch)
@@ -121,7 +136,7 @@ Ein Overflow in lib.bas:2 wird über app.mak als Overflow in line 5 ohne Modulda
Beleg: [crates/tb-cli/src/main.rs:184](../../crates/tb-cli/src/main.rs#L184). Vertrag: [openspec/specs/bytecode-kompilat/spec.md:46](../../openspec/specs/bytecode-kompilat/spec.md#L46), [openspec/specs/sprach-frontend/spec.md:96](../../openspec/specs/sprach-frontend/spec.md#L96), [openspec/specs/vm-ausfuehrung/spec.md:89](../../openspec/specs/vm-ausfuehrung/spec.md#L89), [openspec/specs/vm-fehlerbehandlung/spec.md:11](../../openspec/specs/vm-fehlerbehandlung/spec.md#L11).
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md).
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/archive/2026-09-05-projektmodule-und-kompilat/proposal.md).
### F11 — Formularwerte fehlen im serialisierten Kompilat (hoch)
@@ -129,7 +144,7 @@ Mit FormFile.apply liest die VM den initialen Text hello; nach Serialisierung un
Beleg: [crates/tb-vm/src/bytecode.rs:484](../../crates/tb-vm/src/bytecode.rs#L484). Vertrag: [openspec/specs/bytecode-kompilat/spec.md:28](../../openspec/specs/bytecode-kompilat/spec.md#L28).
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md).
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/archive/2026-09-05-projektmodule-und-kompilat/proposal.md).
### F12 — ListIndex = -1 wird abgewiesen (mittel)
@@ -233,7 +248,7 @@ Die Hauptspec verlangt separate CODE/LINES-Abschnitte und vollständige Prozedur
Beleg: [docs/tbvm-design.md:69](../../docs/tbvm-design.md#L69). Vertrag: [openspec/specs/bytecode-kompilat/spec.md:12](../../openspec/specs/bytecode-kompilat/spec.md#L12), [openspec/specs/bytecode-kompilat/spec.md:28](../../openspec/specs/bytecode-kompilat/spec.md#L28).
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md).
Umsetzung: [projektmodule-und-kompilat](../../openspec/changes/archive/2026-09-05-projektmodule-und-kompilat/proposal.md).
### F25 — FRM-Schreibvertrag enthält zwei unvereinbare Universalregeln (Klärung)
@@ -250,7 +265,7 @@ Umsetzung: [spezifikationsabgleich-und-regressionsnachweise](../../openspec/chan
| 1 | [isam-transaktionen-und-dateinummern](../../openspec/changes/archive/2026-09-05-isam-transaktionen-und-dateinummern/proposal.md) | F01, F02, F03 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
| 2 | [ereigniszustellung-und-hostgrenzen](../../openspec/changes/archive/2026-09-05-ereigniszustellung-und-hostgrenzen/proposal.md) | F04, F05, F06, F07, F08, F18, F20 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
| 3 | [forms-zustand-und-bedienung](../../openspec/changes/archive/2026-09-05-forms-zustand-und-bedienung/proposal.md) | F12, F13 | Nach Ereigniszustellung |
| 4 | [projektmodule-und-kompilat](../../openspec/changes/projektmodule-und-kompilat/proposal.md) | F09, F10, F11, F24 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
| 4 | [projektmodule-und-kompilat](../../openspec/changes/archive/2026-09-05-projektmodule-und-kompilat/proposal.md) | F09, F10, F11, F24 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
| 5 | [laufzeit-eingabe-und-systemstatus](../../openspec/changes/laufzeit-eingabe-und-systemstatus/proposal.md) | F14, F15, F16, F17, F19 | Eigenständig; bei gemeinsamen Dateien sequenziell integrieren |
| 6 | [spezifikationsabgleich-und-regressionsnachweise](../../openspec/changes/spezifikationsabgleich-und-regressionsnachweise/proposal.md) | F21, F22, F23, F25 | Abschließende Gesamtabnahme nach den fünf Korrektur-Changes |

View File

@@ -126,33 +126,100 @@ Der erste Befehl lädt ein FRM-Korpusprogramm und prüft dessen `KM`-Ausgabe
mit CaptureHost. Der zweite Baum darf crossterm, ratatui und signal-hook
nicht enthalten.
## `.tbc`-Container (Stand Phase 2, Formatversion 1)
## `.tbc`-Container (Formatversion 4, 2026-09-05)
```
Magic "TBC\0" · Formatversion u16 · Flags u16 · Abschnittsanzahl u32
Abschnittstabelle: [ (Kennung 4 Byte, Offset u32, Länge u32) ]
Abschnitte:
MODN Modulname + OPTION BASE
CONS deduplizierter Stringpool
TYPS TYPE-Layouts (Feld-Initialisierungstypen)
GLOB globale Slots (Initialisierungstyp + Name für Debugger)
PROC Prozedurtabelle: Name, Parameteranzahl, Frame-Slots, Code
(Zeileninfo liegt inline im Code: Stmt-/SetErl-Instruktionen —
ersetzt den früher geplanten separaten LINES-Abschnitt)
DATA DATA-Konstanten (Rohtext + Quellzeile)
JMPT Sprungtabellen für ON n GOTO/GOSUB
FORMS (ab Phase 4: serialisierte .FRM-Beschreibungen)
```
`tbc build app.mak` erzeugt `app.tbc`; `tbc run app.tbc` benötigt die
BAS-/FRM-/MAK-/Include-Quellen nicht mehr. Dieselbe VM führt Quellprogramme
und geladene Kompilate aus. Versionen 13 und unbekannte Versionen werden
mit Angabe der gefundenen und unterstützten Version abgewiesen.
Serialisierung mit einfachem eigenem Writer/Reader (kein serde nötig,
Format bleibt stabil und dokumentiert); unbekannte Formatversionen
werden mit Meldung abgewiesen. Instruktions-Encoding: 1 Opcode-Byte +
Operanden little-endian, Opcode-Bytes gruppenweise mit Lücken vergeben
(`crates/tb-vm/src/bytecode.rs` ist die normative Liste).
Alle Zahlen sind little-endian, Ganzzahlen mit Vorzeichen im Zweierkomplement,
`f32`/`f64` nach IEEE 754. `bool` ist genau ein Byte (0/1). `string` bedeutet
`u32` UTF-8-Bytelänge, gefolgt von genau diesen Bytes ohne Abschlussnull.
Listen beginnen mit `u32` Elementanzahl; `[...]` bezeichnet die folgenden
wiederholten Elemente. Tabellen-IDs sind nullbasiert.
Der Header besteht aus `TBC\0` (4 Byte), Version `u16 = 4`, Flags `u16 = 0`,
Abschnittsanzahl `u32 = 9`. Darauf folgen neun Einträge mit Kennung (4 ASCII-Bytes),
absolutem Datei-Offset `u32` und Bytelänge `u32`. Der Writer schreibt die
folgenden Abschnitte in dieser Reihenfolge; der Reader findet sie über die Tabelle.
| Kennung | Payload in Reihenfolge |
|---|---|
| `MODN` | Projektname `string`, Vorgabe-OPTION-BASE `u8` (wie Modul 0) |
| `SRCS` | Modulanzahl `u32`, `[Name string, OPTION BASE u8 (0/1)]`; Quellenanzahl `u32`, `[Modul-ID u16, Dateipfad string]` |
| `CONS` | Anzahl `u32`, `[string]`; Stringkonstantenpool |
| `TYPS` | Anzahl `u32`, `[TYPE-Name string, Feldanzahl u32, [TypeInit]]` |
| `GLOB` | Slotanzahl `u32`, `[TypeInit, Debuggername string]` |
| `PROC` | Prozeduranzahl `u32`, `[Prozedurbeschreibung gemäß unten]` |
| `DATA` | Anzahl `u32`, `[Rohtext string, physische Zeile u32]` |
| `JMPT` | Tabellenanzahl `u32`, `[Zielanzahl u32, [Instruktionsindex u32]]` |
| `OBJS` | Objekt-, Ereignis- und Anfangsdaten gemäß unten |
Eine `PROC`-Beschreibung enthält: Name `string`, Modul-ID `u16`, Art `u8`
(0 Hauptprogramm, 1 SUB, 2 FUNCTION, 3 DEF FN), Parameteranzahl `u32`,
`[Parametername string, HTy, Array bool, BYREF bool]`, Rückgabetyp-vorhanden
`bool`, gegebenenfalls `HTy`, Aufrufparameteranzahl `u16`, Local-Anzahl `u32`,
`[TypeInit, Local-Name string]`, Instruktionsanzahl `u32`, Code-Bytelänge `u32`,
Code-Bytes. Die beiden Parameteranzahlen müssen übereinstimmen. Prozedur 0
enthält die zusammengeführten Modulrümpfe; weitere Prozeduren behalten ihre
Modul-ID. In Mehrmodulprojekten sind Prozedur-/globale Debuggernamen
`Modul!Name` mit dem aufgelösten Typ-Suffix bei implizit/suffixdeklarierten
Variablen. AS-deklarierte Namen bleiben suffixlos. Die Inspektion unterscheidet
explizite Suffixe; ohne Suffix muss der Basisname eindeutig sein. Aufrufe und
Variablenzugriffe verwenden ausschließlich IDs.
`TypeInit` belegt immer 5 Byte: Tag `u8` und Zusatz `u32`. Tags: 0 INTEGER,
1 LONG, 2 SINGLE, 3 DOUBLE, 4 CURRENCY, 5 STRING, 6 fester STRING (Zusatz:
Länge), 7 UDT (Zusatz: TYPE-ID, maximal 65535), 8 leerer Slot. Sonst ist der
Zusatz 0. UDT-Felder dürfen nur frühere TYPE-IDs referenzieren.
`HTy` ist ein `u8`-Tag mit denselben skalaren Tags 07; nur bei Tag 6 folgen
`u32` Länge und bei Tag 7 `u16` TYPE-ID. Tag 8 bezeichnet FORM, Tag 9 CONTROL.
Arrays werden separat im Parametersatz markiert; ihr Local-Slot beginnt leer.
`OBJS` enthält Objektanzahl `u32`, `[Name string, Klasse u8, Elternname string
(leer bei Wurzel), Array bool, Eltern-ID u16 (FFFF = keine)]`, dann
Ereignisanzahl `u32`, `[Objekt-ID u16, Ereignisname string, Prozedur-ID u16]`,
Startformular-ID `u16` (FFFF = keines), Anzahl Anfangsinstanzen `u32`,
`[Objekt-ID u16, Designindex i32, Eigenschaftsanzahl u32,
[Eigenschafts-ID u16, PropertyValue]]`. Eltern-IDs zeigen auf frühere Objekte;
gleichnamige Controls anderer Formulare bleiben dadurch getrennt. Auch
Designinstanzen mit Index ungleich 0 und sämtliche FRM-Anfangswerte sind enthalten.
Eigenschaften stehen in aufsteigender ID-Reihenfolge.
`PropertyValue` beginnt mit Tag `u8`: 0 Integer (`i32`), 1 Single (`f32`),
2 String (`string`), 3 Boolean (`bool`), 4 Objekt (`bool` vorhanden, falls ja
`u16` Objekt-ID und `bool` Index-vorhanden, gegebenenfalls `i32` Index),
5 Integer-Array (`u32` Anzahl, `[i32]`). Klassen-IDs 018 entsprechen in
Reihenfolge Form, CheckBox, ComboBox, CommandButton, DirListBox, DriveListBox,
FileListBox, Frame, HScrollBar, Label, ListBox, Menu, OptionButton, PictureBox,
TextBox, Timer, VScrollBar, Screen, Spin. Eigenschafts-/Methoden-IDs sind
Indizes der jeweiligen Klassentabellen in `tb-frontend/src/forms.rs`.
Code und Quellorte liegen inline in `PROC`; es gibt keine `CODE`-/`LINES`-
oder `FORMS`-Abschnitte. `Source(Datei-ID, Spalte)` steht unmittelbar vor
`Stmt(physische Zeile)`. Die VM liest diese Zuordnung auch bei einem Sprung
direkt auf `Stmt`. Globale DIM-Anweisungen tragen `InitStmt(physische Zeile)`: Fehlerorte und
Debugger bleiben aktiv, Ereignisse werden bis nach den Initialisierungen
zurückgestellt. Zeile 0 ist eine synthetische Initialisierungsgrenze.
`SetErl(BASIC-Label)` aktualisiert getrennt davon die numerische Zeilennummer
für ERL. Include-Dateien behalten ihren Pfad und physischen Quellort sowie
die ID ihres einbindenden Moduls. `add_module_breakpoint(module, line)` und
`set_step(true)` halten an diesen Grenzen; `current_module`, `current_file`
und `current_source_pos` liefern den Halt-/Fehlerort. `inspect` akzeptiert
lokale, im aktuellen Modul sichtbare und explizit qualifizierte Variablennamen.
Der Reader prüft Version, Flags, eindeutige bekannte Abschnitte,
Längen/Überlappungen, vollständige Payloads, Typ-/Objekt-/Prozedur-/Quell-IDs,
Sprungziele und Anfangseigenschaften vor der Ausführung. Beschädigte Daten
liefern einen Ladefehler. Das Format ist kein Sandbox-Format für fremden
Programmcode; ein vollständiger Stack-/Kontrollflussverifizierer ist nicht Teil
von TBC. Ein gültiges vom Writer erzeugtes Kompilat lässt sich bytegleich
laden und erneut serialisieren.
## Eigenständige Executables
`tbc build --exe` kopiert den vorkompilierten Runner (dieselbe
Geplant, noch nicht implementiert: `tbc build --exe` kopiert den vorkompilierten Runner (dieselbe
tb-vm/tb-runtime/tb-ui-Bibliothek wie die IDE) und hängt das `.tbc` als
Ressource an (Anhängen ans Binary + Fußzeile mit Offset/Magic; portabel für
alle drei Plattformen). Alternative — `include_bytes!` + Cargo-Build beim
@@ -165,9 +232,19 @@ Nutzer — verworfen: erfordert Rust-Toolchain beim Anwender.
Vorwärtsreferenzen (Prozeduren, Labels) über Fixup-Listen statt zweitem
Pass. Die Sprache ist dafür gemacht — das Vorbild kompilierte auf
1992er-Hardware gefühlt sofort.
- Modulweise inkrementell: `.BAS`/`.FRM` werden unabhängig zu Bytecode-
Einheiten übersetzt und beim Build nur zusammengebunden; die IDE
recompiliert nur geänderte Module (Hash über Quelltext).
- `.BAS`/`.FRM` werden getrennt geparst, semantisch aufgelöst und zu Bytecode-
Einheiten übersetzt. Der Projektlink versetzt Slots, Prozeduren, Typen,
Strings, DATA und Sprungtabellen. Modulrümpfe laufen in Projektreihenfolge;
ihre globalen Initialisierungen liegen vor dem ersten Ereignis. Eindeutige
externe Prozeduren werden mit dem DEFtype-Kontext ihres Ursprungs importiert;
prozedurlokale DEFtype-Angaben beeinflussen keine anderen Prozeduren.
Importierte Konstanten werden im Ursprung gefaltet, auch bei transitiven
Modulabhängigkeiten. TYPE-Abhängigkeiten behalten ihr ursprüngliches Layout;
interne qualifizierte Typnamen verhindern Konflikte mit abweichenden lokalen
Definitionen. Echte lokale Duplikate werden diagnostiziert. SHARED
bleibt modullokal. COMMON-Variablen desselben Blocks und derselben vollständigen
Variablenidentität teilen einen Slot; Elementtyp, Rang und bekannte Grenzen
müssen kompatibel sein. Ein inkrementeller IDE-Cache ist weiterhin geplant.
- Keine Optimierungspasses. Erlaubt sind nur Gratis-Optimierungen im
Codegen (Konstantenfaltung im Ausdruck, Peephole beim Emit).
- Budget als Test verankern (Phase 2): Benchmark-Projekt (~50k Zeilen)
@@ -292,7 +369,7 @@ via `CINT`/`CLNG`/`CSNG`/`CDBL`/`CCUR` — identische Semantik):
`-` voran und hängt stets ein Leerzeichen an; `STR$` nur das führende
Leerzeichen/`-`, kein nachgestelltes.
## Opcode-Satz (Feindesign, 2026-09-02)
## Opcode-Satz (Formatversion 4, 2026-09-05)
In-Memory führt die VM dekodierte Instruktionen (`Vec<Instr>`, ein Rust-
Enum mit eingebetteten Operanden — Wort-Dispatch, keine Byte-Dekodierung
@@ -300,26 +377,205 @@ im heißen Pfad); die `.tbc`-Serialisierung bildet jede Instruktion auf
1 Opcode-Byte + Operanden (little-endian) ab. Typkürzel: `I2`=INTEGER,
`I4`=LONG, `R4`=SINGLE, `R8`=DOUBLE, `CY`=CURRENCY, `STR`=STRING.
| Gruppe | Instruktionen | Bemerkung |
|---|---|---|
| Anweisungsgrenze | `Stmt(line:u32)` · `SetErl(n:u32)` | `Stmt` prüft das Tick-Flag-Wort (Breakpoint/Einzelschritt/Abbruch), aktualisiert Zeile und Resume-Punkt; `SetErl` bei numerischen Zeilennummern |
| Konstanten/Stack | `PushInt(i16)` `PushLng(i32)` `PushSng(f32)` `PushDbl(f64)` `PushCur(i64)` `PushStr(pool:u16)` · `Dup` `Pop` | Stringpool dedupliziert |
| Variablen | `LoadGlobal/StoreGlobal(u16)` · `LoadLocal/StoreLocal(u16)` · `MakeRefGlobal/MakeRefLocal(u16)` · `LoadRef/StoreRef(u16)` | Slots statisch aufgelöst; `*Ref` bedienen BYREF-Parameter (Referenz-Werte) |
| Arrays | `DimGlobal/DimLocal{slot,dims,elem}` `RedimGlobal/RedimLocal{…}` `EraseGlobal/EraseLocal(u16)` · `LoadElem/StoreElem{dims:u8}` · `MakeRefElem{dims:u8}` | Grenzen auf dem Stack (lo/hi je Dimension als LONG); Indexprüfung → Fehler 9 |
| UDT | `LoadField/StoreField(u16)` · `MakeRefField(u16)` | Feldindex aus Typtabelle; verschachtelt durch Verkettung |
| Arithmetik | `Add/Sub/Mul{I2,I4,R4,R8,CY}` · `Neg{…}` · `Div{R4,R8}` · `IDiv/Mod{I2,I4}` · `PowR8` (`^` rechnet in DOUBLE, SINGLE-Ergebnis per Conv) | monomorph; Ganzzahl-/CY-Überlauf → Fehler 6, Division durch 0 → Fehler 11 |
| Konvertierung | `Conv_<src>_<dst>` für alle 20 geordneten Paare aus {I2,I4,R4,R8,CY} | Semantik exakt nach Matrix (Rundung/Überlauf) |
| Logik | `Not/And/Or/Xor/Eqv/Imp{I2,I4}` | bitweise |
| Vergleich | `Cmp{Eq,Ne,Lt,Le,Gt,Ge}{I2,I4,R4,R8,CY,STR}` | Ergebnis INTEGER 1/0 |
| Strings | `Concat` | `Rc<str>`, Kopien sind Pointer-Kopien |
| Kontrollfluss | `Jump(u32)` `JumpIfFalse(u32)` `JumpIfTrue(u32)` | Ziele absolut (Instruktionsindex) innerhalb der Code-Einheit; Fixups beim Emit |
| GOSUB | `Gosub(u32)` `RetGosub` `RetGosubTo(u32)` | GOSUB-Stack pro Frame; leer → Fehler 3 |
| Berechnete Sprünge | `OnJump{table:u16,gosub:bool}` | Sprungtabellen im Modul; 0/zu groß: kein Sprung; negativ/>255 → Fehler 5 |
| Prozeduren | `Call(proc:u16)` `RetProc` `RetFn` `CallBuiltin{id:u16,argc:u8}` | Argumente links→rechts auf dem Stack (Werte oder Referenzen); `RetFn` transportiert den Funktionswert über den Frame-Abbau |
| Fehler | `OnErrorGoto(u32)` `OnErrorLocal(u32)` `OnErrorDisable` `OnErrorLocalDisable` · `Resume0` `ResumeNext` `ResumeLabel(u32)` · `RaiseError` | Scoping siehe unten |
| DATA | `ReadData(art)` `Restore(u32)` | art 0 = String, 1 = Zahl (DOUBLE, dann Conv nach Matrix); Ende → Fehler 4, unkonvertierbar → Fehler 13 |
| E/A | `Input{…}` sowie Builtins (`PRINT`-Familie über `CallBuiltin`) | Konsolenwirkung ausschließlich über das `Host`-Trait |
| Ende | `End` `StopInstr` `SystemInstr` | Verhalten siehe Runner |
Die folgende Tabelle führt jeden serialisierbaren Opcode einzeln auf;
Gesamtbytes schließen das Opcode-Byte ein. `bool`, `TypeInit` und primitive
Breiten sind im Containerabschnitt definiert. `CmpOp` belegt ein Byte:
0 gleich, 1 ungleich, 2 kleiner, 3 kleiner/gleich, 4 größer, 5 größer/gleich.
Nicht aufgeführte Opcode-Bytes sind ungültig.
| Byte | Instruktion | Operanden in Reihenfolge | Gesamtbytes |
|---|---|---|---|
| `0x00` | `Stmt` | line: u32 | 5 |
| `0x01` | `SetErl` | label: u32 | 5 |
| `0x02` | `End` | — | 1 |
| `0x03` | `StopInstr` | — | 1 |
| `0x04` | `SystemInstr` | — | 1 |
| `0x05` | `Unsupported` | name: u16 | 3 |
| `0x06` | `Source` | source: u32, column: u32 | 9 |
| `0x07` | `InitStmt` | line: u32 | 5 |
| `0x10` | `PushInt` | value: i16 | 3 |
| `0x11` | `PushLng` | value: i32 | 5 |
| `0x12` | `PushSng` | value: f32 | 5 |
| `0x13` | `PushDbl` | value: f64 | 9 |
| `0x14` | `PushCur` | value: i64 | 9 |
| `0x15` | `PushStr` | pool: u16 | 3 |
| `0x16` | `Dup` | — | 1 |
| `0x17` | `Pop` | — | 1 |
| `0x18` | `PushUdtId` | type: u16 | 3 |
| `0x20` | `LoadGlobal` | slot: u16 | 3 |
| `0x21` | `StoreGlobal` | slot: u16 | 3 |
| `0x22` | `LoadLocal` | slot: u16 | 3 |
| `0x23` | `StoreLocal` | slot: u16 | 3 |
| `0x24` | `LoadRef` | slot: u16 | 3 |
| `0x25` | `StoreRef` | slot: u16 | 3 |
| `0x26` | `MakeRefGlobal` | slot: u16 | 3 |
| `0x27` | `MakeRefLocal` | slot: u16 | 3 |
| `0x28` | `MakeRefElem` | dimensions: u8 | 2 |
| `0x29` | `MakeRefField` | field: u16 | 3 |
| `0x30` | `LoadArr` | global: bool, slot: u16, dimensions: u8, element: TypeInit | 10 |
| `0x31` | `LoadElem` | dimensions: u8 | 2 |
| `0x32` | `StoreElem` | dimensions: u8 | 2 |
| `0x33` | `DimArr` | global: bool, slot: u16, dimensions: u8, element: TypeInit | 10 |
| `0x34` | `RedimArr` | global: bool, slot: u16, dimensions: u8, element: TypeInit | 10 |
| `0x35` | `EraseSlot` | global: bool, slot: u16 | 4 |
| `0x36` | `LoadField` | field: u16 | 3 |
| `0x37` | `StoreField` | field: u16 | 3 |
| `0x38` | `CopyRec` | — | 1 |
| `0x39` | `ArrBound` | lower: bool | 2 |
| `0x3A` | `FixStr` | length: u32 | 5 |
| `0x3B` | `CommonArr` | global: bool, slot: u16, dimensions: u8, element: TypeInit | 10 |
| `0x40` | `AddI2` | — | 1 |
| `0x41` | `AddI4` | — | 1 |
| `0x42` | `AddR4` | — | 1 |
| `0x43` | `AddR8` | — | 1 |
| `0x44` | `AddCy` | — | 1 |
| `0x45` | `SubI2` | — | 1 |
| `0x46` | `SubI4` | — | 1 |
| `0x47` | `SubR4` | — | 1 |
| `0x48` | `SubR8` | — | 1 |
| `0x49` | `SubCy` | — | 1 |
| `0x4A` | `MulI2` | — | 1 |
| `0x4B` | `MulI4` | — | 1 |
| `0x4C` | `MulR4` | — | 1 |
| `0x4D` | `MulR8` | — | 1 |
| `0x4E` | `MulCy` | — | 1 |
| `0x4F` | `NegI2` | — | 1 |
| `0x50` | `NegI4` | — | 1 |
| `0x51` | `NegR4` | — | 1 |
| `0x52` | `NegR8` | — | 1 |
| `0x53` | `NegCy` | — | 1 |
| `0x54` | `DivR4` | — | 1 |
| `0x55` | `DivR8` | — | 1 |
| `0x56` | `IDivI2` | — | 1 |
| `0x57` | `IDivI4` | — | 1 |
| `0x58` | `ModI2` | — | 1 |
| `0x59` | `ModI4` | — | 1 |
| `0x5A` | `PowR8` | — | 1 |
| `0x5B` | `Concat` | — | 1 |
| `0x60` | `ConvI2I4` | — | 1 |
| `0x61` | `ConvI2R4` | — | 1 |
| `0x62` | `ConvI2R8` | — | 1 |
| `0x63` | `ConvI2Cy` | — | 1 |
| `0x64` | `ConvI4I2` | — | 1 |
| `0x65` | `ConvI4R4` | — | 1 |
| `0x66` | `ConvI4R8` | — | 1 |
| `0x67` | `ConvI4Cy` | — | 1 |
| `0x68` | `ConvR4I2` | — | 1 |
| `0x69` | `ConvR4I4` | — | 1 |
| `0x6A` | `ConvR4R8` | — | 1 |
| `0x6B` | `ConvR4Cy` | — | 1 |
| `0x6C` | `ConvR8I2` | — | 1 |
| `0x6D` | `ConvR8I4` | — | 1 |
| `0x6E` | `ConvR8R4` | — | 1 |
| `0x6F` | `ConvR8Cy` | — | 1 |
| `0x70` | `ConvCyI2` | — | 1 |
| `0x71` | `ConvCyI4` | — | 1 |
| `0x72` | `ConvCyR4` | — | 1 |
| `0x73` | `ConvCyR8` | — | 1 |
| `0x80` | `NotI2` | — | 1 |
| `0x81` | `NotI4` | — | 1 |
| `0x82` | `AndI2` | — | 1 |
| `0x83` | `AndI4` | — | 1 |
| `0x84` | `OrI2` | — | 1 |
| `0x85` | `OrI4` | — | 1 |
| `0x86` | `XorI2` | — | 1 |
| `0x87` | `XorI4` | — | 1 |
| `0x88` | `EqvI2` | — | 1 |
| `0x89` | `EqvI4` | — | 1 |
| `0x8A` | `ImpI2` | — | 1 |
| `0x8B` | `ImpI4` | — | 1 |
| `0x90` | `CmpI2` | compare: CmpOp | 2 |
| `0x91` | `CmpI4` | compare: CmpOp | 2 |
| `0x92` | `CmpR4` | compare: CmpOp | 2 |
| `0x93` | `CmpR8` | compare: CmpOp | 2 |
| `0x94` | `CmpCy` | compare: CmpOp | 2 |
| `0x95` | `CmpStr` | compare: CmpOp | 2 |
| `0xA0` | `Jump` | target: u32 | 5 |
| `0xA1` | `JumpIfFalse` | target: u32 | 5 |
| `0xA2` | `JumpIfTrue` | target: u32 | 5 |
| `0xA3` | `Gosub` | target: u32 | 5 |
| `0xA4` | `RetGosub` | — | 1 |
| `0xA5` | `RetGosubTo` | target: u32 | 5 |
| `0xA6` | `OnJump` | table: u16, gosub: bool | 4 |
| `0xA7` | `Run` | kind: u8 | 2 |
| `0xB0` | `Call` | proc: u16, argc: u8 | 4 |
| `0xB1` | `RetProc` | — | 1 |
| `0xB2` | `RetFn` | — | 1 |
| `0xB3` | `CallBuiltin` | builtin: u16, argc: u8 | 4 |
| `0xB4` | `LoadObjectProperty` | object: u16, property: u16, indexed: bool | 6 |
| `0xB5` | `StoreObjectProperty` | object: u16, property: u16, indexed: bool | 6 |
| `0xB6` | `PushObject` | object: u16, indexed: bool | 4 |
| `0xB7` | `TypeOf` | class: u8 | 2 |
| `0xB8` | `ObjectMethod` | object: u16, method: u16, argc_flags: u8 | 6 |
| `0xB9` | `ObjectLoad` | object: u16, unload: bool, indexed: bool | 5 |
| `0xBA` | `LoadDynamicObjectProperty` | name: u16 | 3 |
| `0xBB` | `StoreDynamicObjectProperty` | name: u16 | 3 |
| `0xBC` | `LoadObjectIndexedProperty` | object: u16, property_flags: u16 | 5 |
| `0xBD` | `ObjectMethodFn` | object: u16, method: u16, argc_flags: u8 | 6 |
| `0xBE` | `StoreObjectIndexedProperty` | object: u16, property_flags: u16 | 5 |
| `0xC0` | `OnErrorGoto` | target: u32 | 5 |
| `0xC1` | `OnErrorLocal` | target: u32 | 5 |
| `0xC2` | `OnErrorDisable` | — | 1 |
| `0xC3` | `OnErrorLocalDisable` | — | 1 |
| `0xC4` | `OnErrorResumeNext` | local: bool | 2 |
| `0xC5` | `Resume0` | — | 1 |
| `0xC6` | `ResumeNext` | — | 1 |
| `0xC7` | `ResumeLabel` | target: u32 | 5 |
| `0xC8` | `RaiseError` | — | 1 |
| `0xC9` | `LoadErr` | — | 1 |
| `0xCA` | `LoadErl` | — | 1 |
| `0xCB` | `SetErr` | — | 1 |
| `0xD0` | `ReadData` | kind: u8 | 2 |
| `0xD1` | `Restore` | data: u32 | 5 |
| `0xD2` | `Input` | argc: u8, line_mode: bool, prompt: u16, question: bool | 6 |
| `0xD3` | `InputFile` | argc: u8, line_mode: bool | 3 |
| `0xD4` | `GetPut` | put: bool, has_record: bool, kind: u8, extra: u16 | 6 |
| `0xD5` | `Field` | count: u8 | 2 |
| `0xD6` | `LsetRset` | right: bool | 2 |
| `0xE0` | `TrapDefine` | kind: u8, target: u32 | 6 |
| `0xE1` | `TrapDisable` | kind: u8 | 2 |
| `0xE2` | `TrapSet` | kind: u8, state: u8 | 3 |
| `0xE3` | `EventSwitch` | enabled: bool | 2 |
| `0xE4` | `Doevents` | — | 1 |
| `0xE5` | `Sleep` | has_seconds: bool | 2 |
`target` bezeichnet einen Instruktionsindex innerhalb der aktuellen Prozedur;
`OnErrorGoto` zeigt in Prozedur 0. `table` referenziert `JMPT`, `proc` die
Prozedurtabelle, `pool`/`name`/`prompt` den Stringpool. `global=true` wählt
`GLOB`, sonst einen Local-Slot. Arrayindizes und Dimensionsgrenzen (je lo/hi)
liegen auf dem Stack. `ArrBound(lower)` unterscheidet LBOUND/UBOUND;
`MakeRef*` erzeugt Referenzen, `CopyRec` eine UDT-Wertkopie. `PushUdtId` legt
eine beim Linken versetzte TYPE-ID als LONG für ISAM auf den Stack.
`Call`/`CallBuiltin` konsumieren `argc` Argumente in Quellreihenfolge.
`builtin` ist die feste ID aus `tb-runtime/src/builtins.rs::ids`; `RetFn`
transportiert den Funktionswert. `ObjectMethod`/`ObjectMethodFn` verwenden
klassenlokale Methodenindizes, keine Stringpool-IDs. Das höchste Bit von
`argc_flags` kennzeichnet einen zusätzlichen Control-Arrayindex vor den
Argumenten; die unteren 7 Bit enthalten die Argumentanzahl. Bei indizierten
Eigenschaften markiert Bit 15 von `property_flags` den zusätzlichen
Control-Arrayindex; die unteren 15 Bit sind die Eigenschafts-ID. Der
Eigenschaftsindex liegt anschließend auf dem Stack. `indexed` bedeutet
ansonsten ebenfalls Control-Arrayindex auf dem Stack. Dynamische
Eigenschaftszugriffe verwenden den Namen im Stringpool und einen Objektwert
vom Stack. `TypeOf(class)` verwendet die oben genannten Klassen-IDs.
`TrapDefine`/`TrapDisable`/`TrapSet`: `kind` ist 0 KEY, 1 TIMER, 2 UEVENT,
3 SIGNAL; die Kennung bzw. TIMER-Dauer kommt vom Stack. `state` ist 0 ON,
1 OFF, 2 STOP. `Doevents` legt 0 ab; `Sleep(true)` nimmt Sekunden vom Stack.
`OnErrorResumeNext(local)` wählt Frame- oder Modulhandler. `RaiseError` und
`SetErr` lesen einen Fehlercode vom Stack; `LoadErr`/`LoadErl` legen ihren Wert ab.
`Run(kind)` ist 0 Neustart, 1 numerische Startzeile, 2 Dateiname; bei 1/2
liegt das Ziel auf dem Stack. `ReadData(kind)` ist 0 STRING, 1 DOUBLE.
`Input`/`InputFile` lesen `argc` Referenzen; bei Dateiinput liegt die
Dateinummer darunter. `line_mode` wählt LINE INPUT, `prompt=FFFF` bedeutet
keinen Prompt, `question` ergänzt das Fragezeichen. `GetPut` unterscheidet
GET/PUT und eine optionale Recordnummer auf dem Stack. `kind` ist 0 ohne
Variable, 1 INTEGER, 2 LONG, 3 SINGLE, 4 DOUBLE, 5 CURRENCY, 6 fester STRING,
7 UDT, 8 variabler STRING; `extra` enthält bei 6 die Länge, bei 7 die TYPE-ID,
sonst 0. `Field` konsumiert Dateinummer und `count` Paare (Länge, Referenz).
`LsetRset(right)` konsumiert Referenz und Wert.
FOR/NEXT wird ohne Spezial-Opcodes kompiliert: Grenz-/Schrittwert einmal
in versteckte Slots ausgewertet; bei konstantem STEP wählt der Codegen
@@ -334,6 +590,8 @@ als Vergleichs-/Sprungkette abgesenkt.
- `ON ERROR GOTO x` setzt den **modulweiten** Handler — auch aus einer
Prozedur heraus. `ON LOCAL ERROR GOTO x` setzt einen **Frame-lokalen**
Handler, der den modulweiten für die Dauer des Prozedurlaufs verdeckt.
Jedes Quellmodul besitzt einen eigenen Modulhandler; `GOTO 0` in einer
Bibliothek deaktiviert daher keinen Handler des aufrufenden Moduls.
`… GOTO 0` deaktiviert den jeweiligen Handler.
- Fehlerfall: erst die Frame-Kette von innen nach außen nach
LOCAL-Handlern absuchen, sonst modulweiter Handler; Stack wird bis zum
@@ -365,3 +623,8 @@ denen die Original-Hilfe schweigt, sind am 2026-09-04 als
`tests/compat/konvertierung.bas` ausführbar verankert. Sollte je eine
belastbare Fundstelle auftauchen, die ihnen widerspricht, gilt sie —
die Entscheidungen sind Lückenfüller, keine Setzungen gegen das Vorbild.
`CommonArr` dimensioniert einen leeren COMMON-Array-Slot einmal. Bei bereits
vorhandenem Array prüft es Elementtyp und die ausgewerteten Grenzen, ohne
Werte oder Handle zu ersetzen (Konflikt: Fehler 13). Statisch erkennbare
COMMON-Typ-/Grenzenkonflikte werden bereits beim Linken diagnostiziert.