Frontend: - Lexer komplett: Typ-Suffixe, Literal-Typisierung (Entscheidung: > 7 signifikante Stellen -> DOUBLE), Hex/Oktal, Zeilenfortsetzung mit _, Strings mit ""-Escape, case-insensitive Keywords (Bibliotheksnamen bleiben Bezeichner) - AST fuer Module/Prozeduren/Anweisungen/Ausdruecke - Parser: fehlertolerant, zeilenorientiert; Kern-Anweisungssatz inkl. Bloecke, ON [LOCAL] ERROR, DEF FN (einzeilig); Datei-E/A als Phase-3-Platzhalter - Semantik: Symboltabellen, implizite Deklaration, DEFtype, OPTION EXPLICIT, Arrays, Builtin-Signaturen, Labelpruefung; Hardware-Features (PEEK/POKE/...) werden zur Compile-Zeit abgewiesen - Meilenstein: Testkorpus parst und wird typgeprueft (corpus.rs); 27 Tests Entscheidungen eingearbeitet: - Binaries heissen tb (IDE) und tbc (Compiler) - Dynamische Terminalgroesse statt 80x25 (Minimum 80x25, btop-artiger Hinweis darunter); tb-ui::screen mit resize(), Spike angepasst - Vollstaendigkeits-Leitplanke: 100% Sprache/Stdlib minus deklarierte Non-Features; Original-Doku als Guiding Principle; Inventar-Aufgabe - CURRENCY als i64-Festkomma; ISAM wird implementiert; breite Zeichen belegen 2 Zellen; GET/PUT-Strings als UTF-32; Blink als hell simuliert - LICENSE: MIT Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.2 KiB
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) →
Rcgenü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: Pro Frame ein Handler-Zustand (
ON ERROR GOTO x). Laufzeitfehler → VM sucht aktiven Handler im aktuellen Modulkontext (Vorbild: Handler sind modul-/prozedurlokal, TODO: exakte Scoping-Regel testen), setztERR/ERL, springt.RESUMEnutzt gemerkten Anweisungs-Offset.
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 (Entwurf)
Magic "TBC\0" · Formatversion u16 · Flags
Abschnittstabelle: [ (Kennung, Offset, Länge) ]
Abschnitte: CONSTS, TYPES, PROCS, CODE, DATA, LINES, FORMS (serialisierte .FRM-Beschreibungen)
Serialisierung mit einfachem eigenem Writer/Reader (kein serde nötig, Format bleibt stabil und dokumentiert).
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/.FRMwerden 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 überValue-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.
Offene Punkte
- Opcode-Satz konkret ausformulieren (mit Phase 2)
- Zahlenkonvertierungs-Matrix (implizite Casts, Rundung, Überlauf) als Tabelle
- Verhalten von
STOP/CONTim Runner (ohne IDE): Abbruch mit Meldung?