Files
TerminalBasic/docs/tbvm-design.md
Chili Palmer 52ccbb5848 Phase 3 (Kern): Laufzeitbibliothek, Bildschirm und Datei-E/A
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>
2026-09-04 06:54:19 +02:00

307 lines
17 KiB
Markdown
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.
# 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.