Files
TerminalBasic/docs/tbvm-design.md

21 KiB
Raw Blame History

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):

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 VM läuft auf dem aufrufenden Thread. Vm::run liefert bei STOP, Breakpoint, Einzelschritt, Programmende oder Fehler ein RunEvent an den Runner zurück. Stmt markiert Anweisungsgrenzen; es gibt keine Prüfung nach jedem Opcode.

Die gemeinsame Zustellentscheidung gilt an Anweisungsgrenzen, bei DOEVENTS, während SLEEP und während blockierender Konsoleneingabe. Eine zugestellte Ereignisprozedur beginnt ihre erste Anweisung, bevor ein weiterer Handler sie unterbrechen darf. Forms-Handler laufen normalerweise bis zur Rückkehr; explizites DOEVENTS und blockierendes Warten können andere Handler verschachteln, aber nie dieselbe laufende Forms-Ereignisprozedur. Die Rückkehr aus dem äußersten normalen Forms-Handler arbeitet wartende Forms-Ereignisse geordnet ab. Objekt-Opcodes stellen nur erforderliche synchrone LOAD-Handler zu; deren Rückkehr zieht keine unabhängigen Ereignisse mitten in eine Anweisung vor. Ein offenes Menü sperrt klassische Traps und Forms-Timer, ohne ON/OFF/STOP zu verändern. Beim Menüschließen wird die Zustellbarkeit vor erneutem Warten geprüft.

INPUT, LINE INPUT, INPUT$ und RANDOMIZE ohne Argument teilen einen fortsetzbaren Konsolenleser. Teiltext und Restlänge liegen im aufrufenden Frame; Referenzen und Ausdrucksoperanden bleiben bis zum Abschluss auf dem Stack. Nach jedem Hostereignis kann die VM einen Handler ausführen und danach am Eingabe-Opcode fortsetzen, auch über STOP/CONT und verschachtelte Eingaben hinweg. Fehler-Unwinding und RETURN label verwerfen die abgebrochene Eingabe. Während dieses Lesens gehören ungetrappte Tasten der Konsole.

RtState::eingaben hält Tasten und Maus in Hostreihenfolge, auch beim Übergeben an Dialoge. Die VM nimmt eine Forms-Eingabe samt ihren Handlern vor der nächsten Eingabe an. Konsolentasten bleiben lesbar; Maus ohne Formular oder außerhalb der Bildschirmfläche wird verworfen. KEY ON entnimmt getrappte Tasten. KEY STOP merkt höchstens einen Trap, markiert die betroffenen Eingaben als bereits gemerkt und lässt sie lesbar, auch nach ON. Vor der klassischen Quellenauswahl wird der getrappte Tastenanfang der Queue gemeldet: KEY behält seine Priorität vor TIMER/UEVENT/SIGNAL, ohne eine davor wartende Mauseingabe zu überholen. EOF beendet die Quelle; modale und modellose Formularschleifen arbeiten schon angenommene Ereignisse noch ab.

Host::jetzt_ms ist die einzige Uhr für Ereignisfristen. Die VM fragt sie nur für aktive Zeitquellen, SLEEP-Fristen und zeitabhängige Mausauswertung ab. Der klassische TIMER wird weiterhin bei Anzeigen, expliziter Kooperation und ansonsten alle 64 Anweisungsgrenzen geprüft. Host::warten(Option<u64>) wartet auf ein Ereignis oder die absolute Deadline derselben Uhr; None als Ergebnis meldet eine erreichte Deadline. Ohne Deadline liefert der Host erst ein Ereignis oder explizites Ereignis::Ende. Die VM verwendet weder Instant noch Sleep/Yield als Rückfallebene. Auch modale und nach Programmende sichtbare Forms verwenden diesen Wartepfad. CaptureHost liefert vorbereitete Ereignisse zuerst und rückt beim Warten andernfalls bis zur Deadline vor; ohne Deadline meldet er Eingabeende. Seine Uhr bleibt außerhalb dieser ausdrücklichen Warteoperation oder manueller Zeitänderungen stehen.

tb-ui hat standardmäßig keine Terminalabhängigkeiten. Das Feature terminal schaltet die Module host, screen, signale und die optionalen Dependencies crossterm, ratatui und signal-hook ein. tb-cli aktiviert es explizit; VM, Forms-Modell, FRM-Reader und Dialoge brauchen es nicht. Einbetter müssen die neue Host-Wartemethode implementieren. Dialog-Eingabequeues tragen Eingabe statt nackter Ereignis-Werte, damit KEY-STOP-Markierungen erhalten bleiben; Ereignisse lassen sich mit .into() einreihen.

Der separate Workspace tests/terminalfrei prüft Einbettung ohne Feature-Unifikation mit CLI oder IDE:

cargo run --manifest-path tests/terminalfrei/Cargo.toml --offline
cargo tree --manifest-path tests/terminalfrei/Cargo.toml --edges normal

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)

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.