Setzt den OpenSpec-Change phase-3-runtime-bildschirm um (77/77 Aufgaben). Abdeckung laut Inventar: 195 implementiert, 37 offen, 53 Non-Feature. Vollstaendigkeits-Inventar - docs/inventar.md mit 285 Eintraegen aus den Themenlisten von bas7advr.hlp und qb45advr.hlp, je mit Status und Fundstelle - crates/tb-frontend/tests/inventar.rs haelt die Tabelle in beide Richtungen gegen den Code; der Abdeckungsstand kann nicht veralten Bruchschritt (Puffer, Host, Korpus) - Cell/TextScreen ziehen ratatui-frei nach tb-runtime::screen; tb-ui behaelt Farbabbildung, Widget und den neuen Terminal-Host - Host wechselt vom Zeichenstrom auf Anzeige des Zellenpuffers plus Ereignisse (Taste, Groessenaenderung, Abbruch) - Korpusvergleich auf getrimmten Bildschirm-Snapshot; die acht bestehenden Sollausgaben blieben dabei unveraendert Groessenunabhaengigkeit - 80x25 ist nirgends mehr eine feste Grenze; Groessenaenderungen waehrend der Ausfuehrung werden zugestellt (Inhalt oben links erhalten, Cursor und VIEW PRINT geklemmt) - Korpusnachweis: dasselbe Programm bei 80x25 und 120x40 mit je eigener Sollausgabe, plus ein Programm mit Groessenwechsel mitten im Lauf Bibliothek - Breite Unicode-Zeichen belegen zwei Zellen (Cursor, POS, Randumbruch) - Bildschirm: CLS, COLOR, LOCATE, WIDTH, VIEW PRINT, SCREEN, CSRLIN, POS - Tastatur: INKEY$, INPUT$, Funktionstasten-Makros (KEY n / LIST / ON) - PRINT USING, LPRINT USING, FORMAT$, SetFormatCC - Mathematik mit kompatiblem PRNG (gleiche Saat, gleiche Folge) - Datum und Zeit mit Serienwerten ab 1899 - Finanzmathematik: FV#, PV#, Pmt#, IPmt#, PPmt#, NPer#, Rate#, NPV#, IRR#, MIRR#, SLN#, SYD#, DDB# - Datei-E/A: sequenziell, RANDOM (Recordpuffer und UDT-Variablen), BINARY, FIELD/LSET/RSET, Statusfunktionen, Dateisystem, MK$/CV - System: ENVIRON, FRE, CLEAR, TRON/TROFF, STACK, ERDEV, ERR-Anweisung Altlasten aus Phase 2 - ON ERROR GOTO auf Modulebene ist aus Prozeduren ansprechbar (prozeduruebergreifender Fixup im Codegenerator) - DATA behaelt seinen Rohtext (Gross-/Kleinschreibung, innerer Leerraum) - Die vier TODO-verify-Zellen der Konvertierungsmatrix sind aufgeloest Vom Inventar aufgedeckte Fehler - Zwoelf Non-Features wies der Compiler entgegen der Phase-1-Spec nicht ab (CALLS, SSEG, POINT, VIEW, COM, PEN, STRIG, STICK, die String*-Routinen, LINE und OPEN "COMn:") -- geschlossen - Drei Gruppen fehlten im urspruenglichen Umfang: Finanzmathematik, Record-Konvertierung, Rest der Dateisystemfunktionen - bas7advr.hlp allein ist keine vollstaendige Quelle; das Inventar bildet die Vereinigung mit qb45advr.hlp Neue Changes - phase-3-isam: schliesst Phase 3 ab (Speicherschicht redb) - phase-3-ortszeit: zieht die UTC-Abweichung zurueck Neue Abhaengigkeit: unicode-width. Dokumentiert: sprachreferenz.md und tbvm-design.md sind TODO-frei, docs/bibliothek.md neu, PLAN.md fortgeschrieben. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
307 lines
17 KiB
Markdown
307 lines
17 KiB
Markdown
# TBVM — Designentwurf
|
||
|
||
Eigene Stack-basierte Bytecode-VM (Entscheidung siehe PLAN.md, 0.2).
|
||
Die VM ist in `tb` (IDE) und in von `tbc` erzeugte Executables
|
||
eingebettet — identischer Code, identisches Verhalten, schneller Turnaround.
|
||
|
||
## Werte-Modell
|
||
|
||
Tagged Enum, kein NaN-Boxing (Einfachheit und Debugbarkeit vor Mikro-
|
||
performance):
|
||
|
||
```rust
|
||
enum Value {
|
||
Int(i16), // INTEGER
|
||
Long(i32), // LONG
|
||
Single(f32), // SINGLE
|
||
Double(f64), // DOUBLE
|
||
Currency(i64), // CURRENCY, Festkomma ×10 000
|
||
Str(Rc<str>), // STRING (immutabel geteilt; Copy-on-Write bei MID$-Anweisung)
|
||
// Arrays und TYPE-Instanzen leben im Heap-Bereich der VM,
|
||
// Variablen-Slots referenzieren sie per Handle (Rc<RefCell<…>>).
|
||
}
|
||
```
|
||
|
||
- Kein GC nötig: der Dialekt kennt keine Zyklen (keine Objektreferenzen in
|
||
TYPEs, keine Closures) → `Rc` genügt.
|
||
- Feste Strings (`STRING * n`) und TYPE-Records werden als eigene
|
||
Speicherobjekte mit fester Zeichen-/Elementstruktur geführt.
|
||
|
||
## Bytecode
|
||
|
||
- Stack-Maschine, Opcodes 1 Byte + Operanden variabler Länge (u8/u16/u32,
|
||
little-endian).
|
||
- **Modul-Struktur:** Konstantenpool (Strings, Zahlen), Typtabelle
|
||
(TYPE-Layouts), Prozedurtabelle (Signatur, Locals-Anzahl, Code-Offset),
|
||
globale Slots, DATA-Segment (für READ/RESTORE), Zeilentabelle.
|
||
- **Zeilentabelle:** Abbildung Code-Offset → (Moduldatei, Zeile). Grundlage
|
||
für `ERL`, Fehlermeldungen, Breakpoints und Einzelschritt.
|
||
- **Aufrufkonventionen:**
|
||
- SUB/FUNCTION: eigener Frame (Locals-Slots, Operandenstack-Basis);
|
||
BYREF-Parameter als Referenz-Slots (Handle auf Variablen-Slot).
|
||
- GOSUB: **kein** Frame — Rücksprungadresse auf separatem GOSUB-Stack im
|
||
aktuellen Frame (RETURN prüft diesen zuerst).
|
||
- **Fehlerbehandlung:** Zwei Ebenen (Scoping geklärt 2026-09-04).
|
||
`ON LOCAL ERROR GOTO x` setzt einen Handler im aktuellen Frame; sein
|
||
Sprungziel muss im selben Rumpf liegen. `ON ERROR GOTO x` **ohne**
|
||
`LOCAL` setzt den modulweiten Handler — auch aus einer Prozedur heraus,
|
||
und sein Sprungziel liegt dann im Modulrumpf (der Codegenerator löst es
|
||
über Prozedur 0 auf, die zuerst übersetzt wird).
|
||
Ein Laufzeitfehler sucht zuerst lokale Handler die Aufrufkette hinauf,
|
||
dann den modulweiten; beim modulweiten wird bis Frame 0 abgewickelt,
|
||
weshalb `RESUME NEXT` dort hinter dem Prozeduraufruf fortsetzt. Der
|
||
Handler setzt `ERR`/`ERL`; `RESUME` nutzt den gemerkten
|
||
Anweisungs-Offset. Ein Fehler im Handler ist fatal (keine Kaskaden).
|
||
|
||
## Ausführungsmodell / Unterbrechbarkeit
|
||
|
||
- Die Interpreterschleife läuft in **Ticks**: nach jeder Anweisung (Grenze
|
||
aus der Zeilentabelle) prüft sie ein Flag-Set: Breakpoint? Einzelschritt?
|
||
Strg+Untbr? Ereignis-Queue nicht leer und Zustellung erlaubt?
|
||
- Ereigniszustellung (Forms, Timer) erfolgt kooperativ: nur an
|
||
Zustellpunkten (`DOEVENTS`, `SLEEP`, blockierende Eingabe, Ende einer
|
||
Ereignisprozedur) — wie im Vorbild.
|
||
- Die VM ist eine gewöhnliche zustandsbehaftete Struktur, `step()`-basiert;
|
||
die einbettende Schleife (IDE-Debugger oder Runner) treibt sie. Kein
|
||
eigener Thread nötig; Terminal-Events werden zwischen Ticks gepollt.
|
||
|
||
## `.tbc`-Container (Stand Phase 2, Formatversion 1)
|
||
|
||
```
|
||
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)
|
||
```
|
||
|
||
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).
|
||
|
||
## Eigenständige Executables
|
||
|
||
`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
|
||
Nutzer — verworfen: erfordert Rust-Toolchain beim Anwender.
|
||
|
||
## Performance
|
||
|
||
**Compiler (Instant-Compile, Anforderung siehe PLAN.md):**
|
||
- Single-Pass pro Modul: Lexen, Parsen und Codegen in einem Durchlauf;
|
||
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).
|
||
- 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)
|
||
muss unter 1 s kompilieren (Release-Build der Toolchain, Referenzrechner);
|
||
einzelnes Modul < 50 ms.
|
||
|
||
**VM-Ausführung:**
|
||
- Alle Namen werden zur Compilezeit aufgelöst: Variablen/Parameter sind
|
||
Slot-Indizes, Prozeduren Tabellenindizes — zur Laufzeit keine
|
||
Hash-Lookups.
|
||
- Statische Typen des Dialekts ausnutzen: typisierte Opcodes
|
||
(`ADD_I16`, `ADD_F64`, `CONCAT` …) statt generischem Dispatch über
|
||
`Value`-Tags in heißen Pfaden.
|
||
- Dichte Opcodes, `match`-Dispatch in einer engen Schleife; Tick-Prüfung
|
||
(Events/Breakpoints) nur an Anweisungsgrenzen über ein einzelnes
|
||
Flag-Wort, nicht pro Opcode.
|
||
- Strings immutabel via `Rc<str>` (Kopien sind Pointer-Kopien);
|
||
Konstantenpool dedupliziert.
|
||
- Messlatte (Phase 2, Benchmarks in `benches/`): typische
|
||
Schleifen-/String-Lasten mindestens ~100× schneller als das Vorbild auf
|
||
Originalhardware; Richtwert grob Lua-Interpreter-Klasse, gemessen und
|
||
dokumentiert statt geraten.
|
||
|
||
**Benchmark-Ergebnisse (2026-09-02, Release-Build, Entwicklerrechner
|
||
Windows 11, `cargo bench -p tb-vm`):**
|
||
|
||
- Compile-Budget (`benches/compile.rs`):
|
||
- Einzelmodul, 508 Zeilen: **1,2 ms** (Budget 50 ms — eingehalten)
|
||
- Projekt, 49 760 Zeilen über 20 Module: **124 ms** (Budget 1 000 ms —
|
||
eingehalten); Durchsatz ≈ 400 000 Zeilen/s
|
||
- VM-Durchsatz (`benches/vm.rs`):
|
||
- INTEGER-Schleife (Arithmetik + MOD): ≈ 4,8 Mio Iterationen/s
|
||
- DOUBLE-Schleife: ≈ 5,2 Mio Iterationen/s
|
||
- SUB-Aufrufe mit BYREF: ≈ 5,2 Mio Aufrufe/s
|
||
- String-Lasten (MID$/INSTR/Verkettung): ≈ 0,8 Mio Runden/s
|
||
**Nachmessung nach Phase 3 (2026-09-04, macOS, Apple Silicon,
|
||
`cargo bench -p tb-vm`)** — die Zustellpunkte des neuen `Host` (Anzeige
|
||
nur bei geändertem Bildschirm, Ereignisabholung alle 1024 Anweisungen)
|
||
kosten nichts Messbares:
|
||
|
||
- Compile-Budget: Einzelmodul 508 Zeilen **0,77 ms**; Projekt 49 760
|
||
Zeilen **53 ms** (≈ 950 000 Zeilen/s)
|
||
- VM-Durchsatz: INTEGER-Schleife **11,0 Mio/s**, DOUBLE **12,2 Mio/s**,
|
||
SUB-Aufrufe **10,7 Mio/s**, String-Lasten **2,4 Mio Runden/s**
|
||
|
||
(Die höheren Zahlen gegenüber 2026-09-02 gehen auf die andere Maschine
|
||
zurück, nicht auf eine Optimierung — gemessen wurde beide Male derselbe
|
||
Benchmark.)
|
||
|
||
- Einordnung: bei ≈ 13 Instruktionen je Schleifeniteration entspricht
|
||
das ≈ 60 Mio Instruktionen/s — Vorbild-Programme auf 1992er-Hardware
|
||
werden um weit mehr als den Faktor 100 übertroffen; für 80×25-
|
||
Terminalprogramme mehr als ausreichend. Ein Performance-Pass ist erst
|
||
bei Bedarf vorgesehen (Phase 6).
|
||
|
||
## Zahlenkonvertierungs-Matrix (Feindesign, 2026-09-02)
|
||
|
||
Grundlage: Original-Hilfe (Topics „Arithmetic Operators", „CINT/CLNG",
|
||
„MOD", „Numeric Types"). Vier Punkte sind dort **nicht** geregelt; sie
|
||
wurden am 2026-09-04 entschieden und sind unten als *Projektentscheidung*
|
||
gekennzeichnet — sie gelten damit als bewusste Festlegung, nicht als
|
||
rekonstruiertes Vorbildverhalten. Der Korpustest `konvertierung.bas`
|
||
verankert die Matrix ausführbar.
|
||
|
||
**Rangfolge für Promotion:** INTEGER < LONG < CURRENCY < SINGLE < DOUBLE.
|
||
Ausnahme: CURRENCY gemischt mit SINGLE oder DOUBLE ergibt **DOUBLE**
|
||
(nicht SINGLE). *Projektentscheidung 2026-09-04:* Die Original-Hilfe
|
||
regelt diesen Fall nicht. CURRENCY trägt 19 signifikante Stellen, SINGLE
|
||
nur 7 — eine Promotion nach SINGLE zerstörte genau die Genauigkeit, für
|
||
die es den Typ gibt. DOUBLE (16 Stellen) ist die nächstbeste Wahl.
|
||
|
||
**Ergebnistyp binärer Operatoren** (l op r; T = promoteter Typ):
|
||
|
||
| Operator | Ergebnistyp | Besonderheiten |
|
||
|---|---|---|
|
||
| `+ - *` | T; gleiche Typen bleiben erhalten | Ganzzahl-/CURRENCY-Überlauf → Fehler 6 (auch INTEGER+INTEGER promotet **nicht**) |
|
||
| `/` | DOUBLE, wenn ein Operand DOUBLE oder CURRENCY, sonst SINGLE | Division durch 0 → Fehler 11 |
|
||
| `\` `MOD` | INTEGER, wenn beide Operanden INTEGER, sonst LONG | Operanden werden **vorher** kaufmännisch (banker's) auf Ganzzahl gerundet; außerhalb LONG → Fehler 6; Divisor 0 → Fehler 11; `MOD`-Vorzeichen wie Dividend |
|
||
| `^` | DOUBLE, wenn ein Operand DOUBLE oder CURRENCY, sonst SINGLE (*Projektentscheidung 2026-09-04*) | Rechnung intern in DOUBLE; 0 ^ negativ → Fehler 11; negative Basis mit nicht-ganzem Exponent → Fehler 5 |
|
||
| `= <> < <= > >=` | INTEGER (−1/0) | Vergleich im promoteten Typ; Strings codepoint-weise |
|
||
| `NOT AND OR XOR EQV IMP` | INTEGER, wenn alle Operanden INTEGER, sonst LONG | bitweise; Operanden vorher banker's-gerundet auf Ganzzahl, außerhalb LONG → Fehler 6 |
|
||
| unäres `-` | Typ des Operanden | −(−32768) INTEGER → Fehler 6 |
|
||
|
||
**Konvertierung Quelle → Ziel** (implizit bei Zuweisung/Argument, explizit
|
||
via `CINT`/`CLNG`/`CSNG`/`CDBL`/`CCUR` — identische Semantik):
|
||
|
||
| von \ nach | INTEGER | LONG | SINGLE | DOUBLE | CURRENCY |
|
||
|---|---|---|---|---|---|
|
||
| INTEGER | — | exakt | exakt | exakt | ×10 000 |
|
||
| LONG | Bereich prüfen → 6 | — | nächstgelegener f32 | exakt | ×10 000 |
|
||
| SINGLE | banker's + Bereich → 6 | banker's + Bereich → 6 | — | exakt (Widening) | banker's auf 4 Nachkommastellen + Bereich → 6 |
|
||
| DOUBLE | banker's + Bereich → 6 | banker's + Bereich → 6 | Bereich prüfen → 6, sonst nächstgelegener f32 | — | banker's auf 4 Nachkommastellen + Bereich → 6 |
|
||
| CURRENCY | ÷10 000, banker's + Bereich → 6 | ÷10 000, banker's + Bereich → 6 | ÷10 000.0 | ÷10 000.0 | — |
|
||
|
||
- Banker's Rounding: zur nächsten Ganzzahl, bei .5 zur **geraden**
|
||
(Original-Hilfe: CINT). Gilt für alle Gleitkomma→Ganzzahl-Übergänge
|
||
inklusive `\`/`MOD`/Logik-Operanden und CURRENCY-Skalierung.
|
||
- Zahl ↔ String nie implizit (Compile-Zeit „Type mismatch"); explizit nur
|
||
über `STR$`/`VAL`.
|
||
|
||
**Textdarstellung (PRINT und STR$):**
|
||
|
||
- INTEGER/LONG: dezimal ohne Dezimalpunkt.
|
||
- SINGLE: bis 7 signifikante Stellen, kürzeste Darstellung, keine
|
||
führende Null vor dem Dezimalpunkt (`.5`), keine nachgestellten
|
||
Nullen; außerhalb des Festformat-Bereichs Exponentialform `mE±xx`.
|
||
*Projektentscheidung 2026-09-04:* Die Original-Hilfe nennt keine
|
||
Schwelle. Gewählt ist der Dezimalexponent < −7 oder ≥ 7 — damit bleibt
|
||
jeder Wert im Festformat, der sich mit den 7 signifikanten Stellen des
|
||
Typs verlustfrei schreiben lässt (`9999999` fest, `1E+07`
|
||
exponentiell; `.0000001` fest, `1E-08` exponentiell).
|
||
- DOUBLE: wie SINGLE mit bis 16 signifikanten Stellen und `D±xx` als
|
||
Exponentmarke.
|
||
- CURRENCY: Festformat mit bis zu 4 Nachkommastellen, nachgestellte
|
||
Nullen entfallen (`2.5000@` → `2.5`, `1.10@` → `1.1`).
|
||
*Projektentscheidung 2026-09-04:* Die Original-Hilfe nennt kein
|
||
Ausgabeformat. Gewählt ist dieselbe Regel wie bei den übrigen
|
||
Zahlentypen — nachgestellte Nullen entfallen —, damit `PRINT` über alle
|
||
Typen einheitlich wirkt. Wer feste Nachkommastellen braucht, nutzt
|
||
`PRINT USING`.
|
||
- `PRINT` stellt nicht-negativen Zahlen ein Leerzeichen, negativen ein
|
||
`-` voran und hängt stets ein Leerzeichen an; `STR$` nur das führende
|
||
Leerzeichen/`-`, kein nachgestelltes.
|
||
|
||
## Opcode-Satz (Feindesign, 2026-09-02)
|
||
|
||
In-Memory führt die VM dekodierte Instruktionen (`Vec<Instr>`, ein Rust-
|
||
Enum mit eingebetteten Operanden — Wort-Dispatch, keine Byte-Dekodierung
|
||
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 |
|
||
|
||
FOR/NEXT wird ohne Spezial-Opcodes kompiliert: Grenz-/Schrittwert einmal
|
||
in versteckte Slots ausgewertet; bei konstantem STEP wählt der Codegen
|
||
die Vergleichsrichtung zur Compilezeit, sonst verzweigt ein einmalig
|
||
berechnetes Vorzeichen-Flag auf zwei Vergleichssequenzen. Inkrement mit
|
||
regulärer typisierter Addition (Überlauf → Fehler 6, wie Vorbild).
|
||
`SELECT CASE` wertet den Selektor in einen versteckten Slot aus und wird
|
||
als Vergleichs-/Sprungkette abgesenkt.
|
||
|
||
## Fehlerbehandlung: Scoping (Feindesign, 2026-09-02)
|
||
|
||
- `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.
|
||
`… 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
|
||
Handler-Frame abgewickelt, Anweisungs-Offset und Frame für `RESUME`
|
||
gemerkt, `ERR`/`ERL` gesetzt. Ohne Handler: Abbruch mit Meldung und
|
||
Quellzeile.
|
||
- Fehler **während aktiver Behandlung** (zwischen Handler-Eintritt und
|
||
`RESUME`): sofortiger Abbruch, keine Kaskade (Vorbild).
|
||
- `ERL` liefert die zuletzt durchlaufene **numerische** Zeilennummer
|
||
(Labels zählen nicht; ohne numerische Zeilen: 0).
|
||
|
||
## Runner-Verhalten `STOP`/`CONT` (Entscheidung 2026-09-02)
|
||
|
||
- `END`, `SYSTEM` und das Erreichen des Programmendes beenden den
|
||
Prozess mit Exit-Code 0.
|
||
- `STOP` außerhalb der IDE terminiert mit der Meldung `STOP in line n`
|
||
(bzw. Modul/Zeile) und Exit-Code ≠ 0. Offene Dateien werden wie bei
|
||
`END` geschlossen.
|
||
- `CONT` ist ein Direktmodus-Konzept der IDE (Phase 5): dort suspendiert
|
||
`STOP` die VM (`step()`-Architektur), `CONT` setzt fort. Im Kompilat
|
||
existiert kein `CONT`.
|
||
|
||
## Offene Punkte
|
||
|
||
Keine — die Punkte aus Phase 0 (Opcode-Satz, Konvertierungsmatrix,
|
||
`STOP`/`CONT` im Runner) sind oben ausgearbeitet. Die vier Stellen, zu
|
||
denen die Original-Hilfe schweigt, sind am 2026-09-04 als
|
||
*Projektentscheidung* festgelegt, an Ort und Stelle begründet und über
|
||
`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.
|