Phase 4: Ereignisschleife und klassische Traps

This commit is contained in:
2026-09-04 15:56:47 +02:00
parent 060674b466
commit 5cf5a6582c
40 changed files with 4024 additions and 176 deletions

View File

@@ -95,6 +95,24 @@ impl ProcCtx {
fn here(&self) -> u32 {
self.code.len() as u32
}
/// Label auf die unmittelbar zuvor emittierte `Stmt`-Grenze binden.
///
/// Schleifen brauchen im Kreis eine Anweisungsgrenze — sonst wird dort
/// nie ein Ereignis zugestellt, kein Breakpoint erreicht und kein
/// Abbruch bemerkt (`FOR i = 1 TO 10000: NEXT` als Warteschleife).
/// Wo die Grenze ohnehin direkt vor dem Schleifenkopf liegt, zeigt der
/// Rücksprung einfach auf sie, statt eine zweite zu emittieren.
fn bind_auf_stmt(&mut self, label: u16) {
let pc = self.code.len().saturating_sub(1) as u32;
debug_assert!(
matches!(self.code.last(), Some(Instr::Stmt(_))),
"bind_auf_stmt ohne vorangehende Anweisungsgrenze"
);
if (label as usize) >= self.label_pc.len() {
self.label_pc.resize(label as usize + 1, None);
}
self.label_pc[label as usize] = Some(pc);
}
fn bind(&mut self, label: u16) {
let pc = self.here();
if (label as usize) >= self.label_pc.len() {
@@ -170,7 +188,8 @@ impl Codegen {
| Instr::RetGosubTo(t)
| Instr::ResumeLabel(t)
| Instr::OnErrorGoto(t)
| Instr::OnErrorLocal(t) => *t = pc,
| Instr::OnErrorLocal(t)
| Instr::TrapDefine(_, t) => *t = pc,
other => unreachable!("Fixup auf {other:?}"),
}
}
@@ -330,9 +349,14 @@ impl Codegen {
ctx.bind(l_end);
}
}
HStmtKind::Loop { pre, post, body, exit_label } => {
HStmtKind::Loop {
pre,
post,
body,
exit_label,
} => {
let l_start = ctx.new_label();
ctx.bind(l_start);
ctx.bind_auf_stmt(l_start);
if let Some((is_until, cond)) = pre {
self.expr(ctx, cond);
if *is_until {
@@ -370,19 +394,49 @@ impl Codegen {
exit_label,
} => {
self.gen_for(
ctx, proc, var, *ty, from, to, step.as_ref(), *limit_slot, *step_slot, body,
ctx,
proc,
var,
*ty,
from,
to,
step.as_ref(),
*limit_slot,
*step_slot,
body,
*exit_label,
stmt.line,
);
}
HStmtKind::Goto(l) => ctx.emit_jump(Instr::Jump(0), *l),
HStmtKind::Gosub(l) => ctx.emit_jump(Instr::Gosub(0), *l),
HStmtKind::OnGoto { sel, gosub, targets } => {
HStmtKind::OnGoto {
sel,
gosub,
targets,
} => {
self.expr(ctx, sel);
let table = self.jump_tables.len();
self.jump_tables.push(Vec::new());
ctx.table_fixups.push((table, targets.clone()));
ctx.emit(Instr::OnJump(table as u16, *gosub));
}
HStmtKind::TrapDef { art, index, ziel } => {
self.expr(ctx, index);
match ziel {
Some(l) => ctx.emit_jump(Instr::TrapDefine(*art, 0), *l),
None => ctx.emit(Instr::TrapDisable(*art)),
}
}
HStmtKind::TrapSet {
art,
index,
zustand,
} => {
self.expr(ctx, index);
ctx.emit(Instr::TrapSet(*art, *zustand));
}
HStmtKind::EventSwitch(an) => ctx.emit(Instr::EventSwitch(*an)),
HStmtKind::ReturnGosub(target) => match target {
None => ctx.emit(Instr::RetGosub),
Some(l) => ctx.emit_jump(Instr::RetGosubTo(0), *l),
@@ -398,6 +452,17 @@ impl Codegen {
for a in args {
self.expr(ctx, a);
}
// `DOEVENTS` und `SLEEP` sind Zustellpunkte und damit Sache
// der Ausführung, nicht der Bibliothek: ein Builtin kann
// keine Ereignisprozedur des Programms aufrufen
// (design.md, D1).
if let Some(i) = zustellpunkt_instr(*b, args.len()) {
ctx.emit(i);
if matches!(b, Builtin::Doevents) {
ctx.emit(Instr::Pop);
}
return;
}
let id = builtin_id(*b);
ctx.emit(Instr::CallBuiltin(id, args.len() as u8));
if builtin_returns_value(*b) {
@@ -484,6 +549,7 @@ impl Codegen {
step_slot: Option<VarSlot>,
body: &[HStmt],
exit_label: u16,
line: u32,
) {
// Startwert, Grenze, ggf. Schritt einmal auswerten.
self.store_place(ctx, var, |cg, ctx| cg.expr(ctx, from));
@@ -501,6 +567,15 @@ impl Codegen {
let l_test = ctx.new_label();
let l_body = ctx.new_label();
ctx.bind(l_test);
// Anweisungsgrenze für den Rücksprung von `NEXT`. Sie kann nicht
// auf die Grenze des `FOR` zeigen — dort stünde die Initialisierung
// noch einmal.
//
// ponytail: gemeldet wird die Zeile des `FOR`, nicht die des
// `NEXT` — die trägt das HIR nicht. Ceiling: braucht der Debugger
// in Phase 5 die genaue Zeile, bekommt `HStmtKind::For` ein Feld
// `next_line`.
ctx.emit(Instr::Stmt(line));
match const_step {
Some(s) => {
// Vergleichsrichtung zur Compilezeit.
@@ -625,6 +700,10 @@ impl Codegen {
for a in args {
self.expr(ctx, a);
}
if let Some(i) = zustellpunkt_instr(*b, args.len()) {
ctx.emit(i);
return;
}
ctx.emit(Instr::CallBuiltin(builtin_id(*b), args.len() as u8));
}
HExpr::ArrayBound { lower, place, dim } => {
@@ -934,6 +1013,15 @@ fn emit_conv(ctx: &mut ProcCtx, from: NumTy, to: NumTy) {
/// Abbildung `hir::Builtin` → stabiler Tabellenindex der Laufzeit.
/// Erschöpfendes `match`: neue Builtins zwingen hier zur Pflege.
/// `DOEVENTS`/`SLEEP` → eigene Instruktion statt Builtin-Aufruf.
fn zustellpunkt_instr(b: Builtin, argc: usize) -> Option<Instr> {
match b {
Builtin::Doevents => Some(Instr::Doevents),
Builtin::Sleep => Some(Instr::Sleep(argc > 0)),
_ => None,
}
}
fn builtin_id(b: Builtin) -> u16 {
match b {
Builtin::Len => ids::LEN,
@@ -1060,6 +1148,7 @@ fn builtin_id(b: Builtin) -> u16 {
Builtin::CommandS => ids::COMMAND_S,
Builtin::Doevents => ids::DOEVENTS,
Builtin::Sleep => ids::SLEEP,
Builtin::SetUEvent => ids::SETUEVENT,
Builtin::Beep => ids::BEEP,
// ISAM
Builtin::IsamOpen => ids::ISAM_OPEN,