Phase 4: Ereignisschleife und klassische Traps
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user