Phase 2 abgeschlossen: Bytecode, TBVM, Runtime-Scheibe, tbc run

- Sema zum Lowering-Pass umgebaut: typisiertes HIR (Slots, explizite
  Konvertierungsknoten) als Codegen-Eingabe; BYREF verlangt exakten Typ
- Bytecode-Feindesign umgesetzt: monomorpher Opcode-Satz,
  .tbc-Container (Formatversion 1) mit eigenem Writer/Reader
- Codegenerator HIR -> Bytecode (Fixup-Listen, keine globalen Passes)
- TBVM-Interpreter: Kontrollfluss, GOSUB-Stack je Frame, BYREF/BYVAL,
  STATIC, DEF FN, DATA/READ/RESTORE, ON [LOCAL] ERROR/RESUME/ERR/ERL,
  Breakpoints/Einzelschritt/Inspektion, STOP fortsetzbar
- Runtime-Scheibe: Host-Trait (Konsole/Capture), Builtin-Tabelle,
  Konvertierungsmatrix, PRINT-Formatierung/Druckzonen, Stringfunktionen
- tbc run/build/check mit Exit-Codes nach Entscheidung D6
- Korpus-Harness (byte-genauer Vergleich) + 3 neue Korpusdateien
  (konvertierung, fehlerbehandlung, byref); 137 Tests gruen
- Benchmarks: Einzelmodul 1,2 ms / Projekt 49.760 Zeilen 124 ms
  (Budgets eingehalten), VM ~5 Mio Schleifeniterationen/s
- Doku fortgeschrieben (tbvm-design, sprachreferenz, PLAN);
  verlagerte Punkte als explizite Aufgaben in Phase 3
- OpenSpec-Change phase-2-bytecode-vm (27/27 Tasks)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 11:28:07 +02:00
parent da23d52036
commit f7e57b0bd8
42 changed files with 9225 additions and 484 deletions

View File

@@ -1,13 +1,110 @@
//! `tbc` — Standalone-Compiler von Terminal Basic.
//!
//! Wandelt Quelldateien und Projekte in binäre Ergebnisse (`.tbc`-Bytecode
//! bzw. eigenständig ausführbare Programme) um. Geplante Unterbefehle
//! (siehe PLAN.md):
//! - `tbc build <projekt|datei.bas>` Kompilieren zu binärem Ergebnis
//! - `tbc run <datei.bas>` Kompilieren und sofort ausführen
//! - `tbc check <datei.bas>` Nur Syntax-/Semantikprüfung
//! Unterbefehle (Phase 2):
//! - `tbc run <datei.bas>` Kompilieren und sofort ausführen
//! - `tbc build <datei.bas>` Kompilieren zu `datei.tbc`
//! - `tbc check <datei.bas>` Nur Syntax-/Semantikprüfung
//!
//! Exit-Codes von `run` (Entscheidung D6, docs/tbvm-design.md):
//! 0 = END/SYSTEM/Programmende · 3 = STOP · 2 = Laufzeitfehler ·
//! 1 = Compile-Fehler/Bedienfehler.
fn main() -> anyhow::Result<()> {
println!("tbc — Terminal Basic Compiler (Projektrahmen, noch ohne Funktion)");
Ok(())
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use tb_runtime::host::ConsoleHost;
use tb_vm::interp::{RunEvent, Vm};
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
match args.first().map(String::as_str) {
Some("run") => cmd_run(&args[1..]),
Some("build") => cmd_build(&args[1..]),
Some("check") => cmd_check(&args[1..]),
_ => {
eprintln!("Aufruf: tbc run|build|check <datei.bas>");
ExitCode::from(1)
}
}
}
fn module_name(path: &Path) -> String {
path.file_stem()
.map(|s| s.to_string_lossy().to_uppercase())
.unwrap_or_else(|| "MODUL".into())
}
fn compile(path_arg: Option<&String>) -> Result<(PathBuf, tb_vm::bytecode::CompiledModule), ExitCode> {
let Some(path) = path_arg else {
eprintln!("Aufruf: tbc run|build|check <datei.bas>");
return Err(ExitCode::from(1));
};
let path = PathBuf::from(path);
let source = match std::fs::read_to_string(&path) {
Ok(s) => s,
Err(e) => {
eprintln!("{}: {e}", path.display());
return Err(ExitCode::from(1));
}
};
match tb_vm::compile_source(&module_name(&path), &source) {
Ok(m) => Ok((path, m)),
Err(diags) => {
for d in &diags {
eprintln!("{}:{d}", path.display());
}
eprintln!("{} Fehler.", diags.len());
Err(ExitCode::from(1))
}
}
}
fn cmd_check(args: &[String]) -> ExitCode {
match compile(args.first()) {
Ok(_) => ExitCode::SUCCESS,
Err(code) => code,
}
}
fn cmd_build(args: &[String]) -> ExitCode {
let (path, module) = match compile(args.first()) {
Ok(x) => x,
Err(code) => return code,
};
let out = path.with_extension("tbc");
match std::fs::write(&out, module.to_tbc()) {
Ok(()) => {
println!("{}", out.display());
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("{}: {e}", out.display());
ExitCode::from(1)
}
}
}
fn cmd_run(args: &[String]) -> ExitCode {
let (_path, module) = match compile(args.first()) {
Ok(x) => x,
Err(code) => return code,
};
let mut vm = Vm::new(module);
vm.rt.command = args[1..].join(" ");
let mut host = ConsoleHost;
match vm.run(&mut host) {
RunEvent::Ended => ExitCode::SUCCESS,
RunEvent::Stopped { line } => {
// STOP außerhalb der IDE: Meldung + Exit-Code ≠ 0 (D6).
eprintln!("STOP in line {line}");
ExitCode::from(3)
}
RunEvent::Error { code, line, message } => {
eprintln!("Runtime error {code}: {message} in line {line}");
ExitCode::from(2)
}
// Ohne Debugger-Flags treten diese Ereignisse nicht auf.
RunEvent::Breakpoint { .. } | RunEvent::Stepped { .. } | RunEvent::Interrupted { .. } => {
ExitCode::from(2)
}
}
}