Phase 4: Ereignisschleife und klassische Traps
This commit is contained in:
@@ -15,9 +15,8 @@ use std::rc::Rc;
|
||||
use tb_runtime::builtins::{builtin_table, RtState};
|
||||
use tb_runtime::errors::RuntimeError;
|
||||
use tb_runtime::host::Host;
|
||||
use tb_runtime::value::{
|
||||
self, default_value, ArrayObj, RecordObj, TypeInit, Value, VarRef,
|
||||
};
|
||||
use tb_runtime::traps::Quelle;
|
||||
use tb_runtime::value::{self, default_value, ArrayObj, RecordObj, TypeInit, Value, VarRef};
|
||||
|
||||
/// Warum die VM die Kontrolle abgibt.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -54,6 +53,11 @@ struct Frame {
|
||||
/// Instruktionsindex der zuletzt begonnenen Anweisung (`Stmt`).
|
||||
last_stmt_pc: usize,
|
||||
line: u32,
|
||||
/// Gesetzt, wenn dieser Frame der Handler eines Ereignis-Traps ist.
|
||||
/// Er läuft im Modulrumpf und **teilt dessen Locals** — ein eigener
|
||||
/// Satz würde dem Handler leere Modulvariablen zeigen. Sein `RETURN`
|
||||
/// beendet den Handler (design.md, D2).
|
||||
trap: Option<Quelle>,
|
||||
}
|
||||
|
||||
pub struct Vm {
|
||||
@@ -65,7 +69,12 @@ pub struct Vm {
|
||||
pub rt: RtState,
|
||||
// Fehlerzustand
|
||||
err: u16,
|
||||
/// Wert von `ERL`: die Zeilennummer, in der der Fehler auftrat. Wird
|
||||
/// **nur** beim Auslösen gesetzt — liefe sie mit `zeile_nr` mit, meldete
|
||||
/// ein Handler auf einer nummerierten Zeile seine eigene Nummer.
|
||||
erl: u32,
|
||||
/// Zuletzt durchlaufene numerische Zeilennummer (0 = keine).
|
||||
zeile_nr: u32,
|
||||
module_handler: Handler,
|
||||
in_handler: bool,
|
||||
resume_pc: usize,
|
||||
@@ -77,6 +86,34 @@ pub struct Vm {
|
||||
data_ptr: usize,
|
||||
}
|
||||
|
||||
/// Quellenarten, wie der Codegenerator sie kodiert.
|
||||
const ART_KEY: u8 = 0;
|
||||
const ART_TIMER: u8 = 1;
|
||||
const ART_UEVENT: u8 = 2;
|
||||
const ART_SIGNAL: u8 = 3;
|
||||
|
||||
/// Art + Kennung → Quelle. Die Kennung kann ein Ausdruck sein, deshalb
|
||||
/// wird der Wertebereich hier noch einmal geprüft (Fehler 5).
|
||||
///
|
||||
/// Das Zeitintervall gehört **nicht** hierher: `TIMER ON` trägt keine
|
||||
/// Kennung, geprüft wird es nur an `ON TIMER(n&)`.
|
||||
fn trap_quelle(art: u8, n: i32) -> Result<Quelle, RuntimeError> {
|
||||
let ok = match art {
|
||||
ART_KEY => matches!(n, 0..=25 | 30 | 31),
|
||||
ART_SIGNAL => (1..=2).contains(&n),
|
||||
_ => true,
|
||||
};
|
||||
if !ok {
|
||||
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||
}
|
||||
Ok(match art {
|
||||
ART_KEY => Quelle::Key(n as u8),
|
||||
ART_TIMER => Quelle::Timer,
|
||||
ART_UEVENT => Quelle::UEvent,
|
||||
_ => Quelle::Signal(n as u8),
|
||||
})
|
||||
}
|
||||
|
||||
enum Flow {
|
||||
Normal,
|
||||
Event(RunEvent),
|
||||
@@ -97,6 +134,7 @@ impl Vm {
|
||||
rt: RtState::default(),
|
||||
err: 0,
|
||||
erl: 0,
|
||||
zeile_nr: 0,
|
||||
module_handler: Handler::None,
|
||||
in_handler: false,
|
||||
resume_pc: 0,
|
||||
@@ -134,9 +172,88 @@ impl Vm {
|
||||
local_handler: Handler::None,
|
||||
last_stmt_pc: 0,
|
||||
line: 0,
|
||||
trap: None,
|
||||
});
|
||||
}
|
||||
|
||||
/// Anstehendes Ereignis zustellen, falls eines fällig ist. Liefert
|
||||
/// `true`, wenn ein Handler aufgesetzt wurde.
|
||||
fn zustellen(&mut self, host: &mut dyn Host) -> bool {
|
||||
if !self.rt.traps.aktiv() {
|
||||
return false;
|
||||
}
|
||||
let jetzt = host.jetzt_ms();
|
||||
self.rt.traps.zeit_pruefen(jetzt);
|
||||
self.rt.tasten_traps_pruefen();
|
||||
if let Some((q, ziel)) = self.rt.traps.naechstes() {
|
||||
self.trap_frame_aufsetzen(q, ziel);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// `SLEEP [n]`: wartet `n` Sekunden, ohne Argument bis zum nächsten
|
||||
/// Tastendruck. Ein zugestelltes Ereignis beendet das Warten
|
||||
/// (Sprachreferenz §8) — der Handler läuft, und danach geht es hinter
|
||||
/// dem `SLEEP` weiter.
|
||||
fn schlafen(&mut self, host: &mut dyn Host, sekunden: f64) {
|
||||
let start = host.jetzt_ms();
|
||||
let dauer_ms = (sekunden.max(0.0) * 1000.0) as u64;
|
||||
// ponytail: zusätzlich eine echte Zeitschranke, damit ein Host mit
|
||||
// stehender Uhr (Testhost) nicht ewig wartet. Ceiling: bei einer
|
||||
// virtuellen Uhr endet `SLEEP` nach der realen Dauer, wenn die
|
||||
// virtuelle nie vorrückt.
|
||||
let real_start = std::time::Instant::now();
|
||||
loop {
|
||||
self.rt.tick(host);
|
||||
if self.zustellen(host) {
|
||||
return;
|
||||
}
|
||||
if self.rt.ende || self.rt.abbruch {
|
||||
return;
|
||||
}
|
||||
if sekunden <= 0.0 {
|
||||
// Ohne Argument: bis zum Tastendruck.
|
||||
if self.rt.naechste_taste(host, false).is_some() {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let vergangen = host.jetzt_ms().saturating_sub(start);
|
||||
if vergangen >= dauer_ms || real_start.elapsed().as_millis() as u64 >= dauer_ms {
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
|
||||
/// Trap-Handler aufsetzen: ein Frame auf den Modulrumpf, der dessen
|
||||
/// Locals mitbenutzt, mit eigenem `GOSUB`-Stapel und einer Rückmarke
|
||||
/// auf die unterbrochene Stelle (design.md, D2).
|
||||
fn trap_frame_aufsetzen(&mut self, q: Quelle, ziel: u32) {
|
||||
let locals_base = self.frames[0].locals_base;
|
||||
let stack_base = self.stack.len();
|
||||
self.frames.push(Frame {
|
||||
proc: 0,
|
||||
pc: ziel as usize,
|
||||
locals_base,
|
||||
stack_base,
|
||||
gosub: Vec::new(),
|
||||
local_handler: Handler::None,
|
||||
last_stmt_pc: 0,
|
||||
line: 0,
|
||||
trap: Some(q),
|
||||
});
|
||||
}
|
||||
|
||||
/// Trap-Frame abräumen. Anders als `pop_frame` bleiben die Locals
|
||||
/// stehen — sie gehören dem Modulrumpf, nicht dem Handler.
|
||||
fn trap_frame_abraeumen(&mut self) -> Option<Quelle> {
|
||||
let f = self.frames.pop()?;
|
||||
self.stack.truncate(f.stack_base);
|
||||
f.trap
|
||||
}
|
||||
|
||||
fn pop_frame(&mut self) {
|
||||
let f = self.frames.pop().expect("Frame-Unterlauf");
|
||||
self.locals.truncate(f.locals_base);
|
||||
@@ -273,6 +390,8 @@ impl Vm {
|
||||
// Fehler im Handler: fatal, keine Kaskade.
|
||||
return Some(self.error_event(code, line));
|
||||
}
|
||||
// `ERL` friert auf der Zeile ein, in der es passiert ist.
|
||||
self.erl = self.zeile_nr;
|
||||
// Frame-Kette von innen nach außen nach LOCAL-Handlern absuchen.
|
||||
let mut target: Option<(usize, Handler)> = None;
|
||||
for (depth, f) in self.frames.iter().enumerate().rev() {
|
||||
@@ -523,6 +642,24 @@ impl Vm {
|
||||
self.rt.tick(host);
|
||||
self.rt.screen.veraenderung_quittieren();
|
||||
}
|
||||
// Ereigniszustellung (design.md, D1): nur wenn überhaupt
|
||||
// ein Trap definiert ist — sonst kostet die Grenze nichts.
|
||||
if self.rt.traps.aktiv() {
|
||||
// ponytail: die Uhr nur alle 64 Anweisungen lesen, die
|
||||
// Tastenschlange dagegen jedes Mal. Ceiling: die
|
||||
// Auflösung eines Zeit-Traps ist damit 64 Anweisungen;
|
||||
// reicht das nicht, wird daraus ein eigener Zähler je
|
||||
// Trap.
|
||||
if self.tick_zaehler % 64 == 0 {
|
||||
let jetzt = host.jetzt_ms();
|
||||
self.rt.traps.zeit_pruefen(jetzt);
|
||||
}
|
||||
self.rt.tasten_traps_pruefen();
|
||||
if let Some((q, ziel)) = self.rt.traps.naechstes() {
|
||||
self.trap_frame_aufsetzen(q, ziel);
|
||||
return Ok(Flow::Normal);
|
||||
}
|
||||
}
|
||||
if self.flags != 0 {
|
||||
if self.flags & F_STEP != 0 {
|
||||
return Ok(Flow::Event(RunEvent::Stepped { line }));
|
||||
@@ -536,12 +673,64 @@ impl Vm {
|
||||
}
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
// ---- Ereignis-Traps (Sprachreferenz §8) ----
|
||||
I::TrapDefine(art, ziel) => {
|
||||
let n = self.pop_i32()?;
|
||||
let q = trap_quelle(art, n)?;
|
||||
if art == ART_TIMER {
|
||||
if !(1..=86_400).contains(&n) {
|
||||
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||
}
|
||||
let jetzt = host.jetzt_ms();
|
||||
self.rt.traps.timer_intervall(n as u64, jetzt);
|
||||
}
|
||||
self.rt.traps.definieren(q, Some(ziel));
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::TrapDisable(art) => {
|
||||
let n = self.pop_i32()?;
|
||||
let q = trap_quelle(art, n)?;
|
||||
self.rt.traps.definieren(q, None);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::TrapSet(art, zustand) => {
|
||||
let n = self.pop_i32()?;
|
||||
let q = trap_quelle(art, n)?;
|
||||
let z = match zustand {
|
||||
0 => tb_runtime::traps::Zustand::An,
|
||||
1 => tb_runtime::traps::Zustand::Aus,
|
||||
_ => tb_runtime::traps::Zustand::Gestoppt,
|
||||
};
|
||||
let jetzt = host.jetzt_ms();
|
||||
self.rt.traps.setzen(q, z, jetzt);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::Doevents => {
|
||||
// Wert zuerst ablegen: ein hier zugestellter Handler
|
||||
// bekommt seinen eigenen Stapelabschnitt darüber und
|
||||
// lässt den Wert unberührt.
|
||||
self.stack.push(Value::Int(0));
|
||||
self.rt.tick(host);
|
||||
self.zustellen(host);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::Sleep(mit_arg) => {
|
||||
let sekunden = if mit_arg { self.pop_f64()? } else { 0.0 };
|
||||
self.schlafen(host, sekunden);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::EventSwitch(an) => {
|
||||
self.rt.traps.event(an);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::SetErl(n) => {
|
||||
self.erl = n;
|
||||
self.zeile_nr = n;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::End | I::SystemInstr => Ok(Flow::Event(RunEvent::Ended)),
|
||||
I::StopInstr => Ok(Flow::Event(RunEvent::Stopped { line: self.current_line() })),
|
||||
I::StopInstr => Ok(Flow::Event(RunEvent::Stopped {
|
||||
line: self.current_line(),
|
||||
})),
|
||||
I::Unsupported(_) => Err(RuntimeError(73)), // Advanced feature
|
||||
|
||||
I::PushInt(v) => {
|
||||
@@ -1086,6 +1275,14 @@ impl Vm {
|
||||
f.pc = ret;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
// Leerer GOSUB-Stapel in einem Trap-Frame: das `RETURN`
|
||||
// beendet den Handler und führt an der unterbrochenen
|
||||
// Stelle weiter.
|
||||
None if f.trap.is_some() => {
|
||||
let q = self.trap_frame_abraeumen().unwrap();
|
||||
self.rt.traps.handler_beendet(q);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
None => Err(RuntimeError::RETURN_WITHOUT_GOSUB),
|
||||
}
|
||||
}
|
||||
@@ -1096,6 +1293,20 @@ impl Vm {
|
||||
f.pc = t as usize;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
// `RETURN label` aus einem Trap-Handler: der Handler
|
||||
// endet, die unterbrochene Aufrufkette wird verworfen
|
||||
// und der Modulrumpf läuft am Label weiter. Das ist die
|
||||
// klassische — und bewusst rabiate — Bedeutung.
|
||||
None if f.trap.is_some() => {
|
||||
let q = self.trap_frame_abraeumen().unwrap();
|
||||
self.rt.traps.handler_beendet(q);
|
||||
while self.frames.len() > 1 {
|
||||
self.pop_frame();
|
||||
}
|
||||
let f = self.frames.last_mut().unwrap();
|
||||
f.pc = t as usize;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
None => Err(RuntimeError::RETURN_WITHOUT_GOSUB),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user