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>
119 lines
5.2 KiB
Markdown
119 lines
5.2 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:** 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), setzt `ERR`/`ERL`, springt. `RESUME` nutzt 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`/`.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.
|
||
|
||
## Offene Punkte
|
||
|
||
- Opcode-Satz konkret ausformulieren (mit Phase 2)
|
||
- Zahlenkonvertierungs-Matrix (implizite Casts, Rundung, Überlauf) als Tabelle
|
||
- Verhalten von `STOP`/`CONT` im Runner (ohne IDE): Abbruch mit Meldung?
|