Ereigniszustellung und Hostgrenzen korrigieren und Change archivieren
This commit is contained in:
@@ -78,11 +78,27 @@ struct Frame {
|
||||
waiting_form: Option<u16>,
|
||||
pending_show: Option<u16>,
|
||||
form_event: Option<FormEventReturn>,
|
||||
handler_start: bool,
|
||||
eingabe: Option<KonsolenEingabe>,
|
||||
}
|
||||
|
||||
struct KonsolenEingabe {
|
||||
text: String,
|
||||
/// None = Zeile bis Enter; Some = noch erwartete Tasten für INPUT$.
|
||||
rest: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Zustellpunkt {
|
||||
Anweisung,
|
||||
Kooperativ,
|
||||
Konsole,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum FormEventReturn {
|
||||
Normal,
|
||||
Load,
|
||||
Unload(u16),
|
||||
}
|
||||
|
||||
@@ -225,6 +241,8 @@ impl Vm {
|
||||
waiting_form: None,
|
||||
pending_show: None,
|
||||
form_event: None,
|
||||
handler_start: false,
|
||||
eingabe: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -262,36 +280,15 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
fn take_dialog_input(&mut self) -> VecDeque<Ereignis> {
|
||||
let mut events = VecDeque::new();
|
||||
events.extend(
|
||||
self.rt
|
||||
.tasten
|
||||
.drain(..)
|
||||
.map(|(key, shift)| Ereignis::Taste(key, shift)),
|
||||
);
|
||||
events.extend(self.rt.maus.drain(..).map(Ereignis::Maus));
|
||||
events
|
||||
fn take_dialog_input(&mut self) -> VecDeque<tb_runtime::builtins::Eingabe> {
|
||||
std::mem::take(&mut self.rt.eingaben)
|
||||
}
|
||||
|
||||
fn restore_dialog_input(&mut self, events: VecDeque<Ereignis>) {
|
||||
for event in events {
|
||||
match event {
|
||||
Ereignis::Taste(key, shift) => self.rt.tasten.push_back((key, shift)),
|
||||
Ereignis::Maus(event) => self.rt.maus.push_back(event),
|
||||
Ereignis::Abbruch => self.rt.abbruch = true,
|
||||
Ereignis::Ende => self.rt.ende = true,
|
||||
Ereignis::Signal(number) => {
|
||||
if number == 1
|
||||
&& !self
|
||||
.rt
|
||||
.traps
|
||||
.melden(tb_runtime::traps::Quelle::Signal(number))
|
||||
{
|
||||
self.rt.abbruch = true;
|
||||
}
|
||||
}
|
||||
Ereignis::Groesse { cols, rows } => self.rt.screen.resize(cols, rows),
|
||||
fn restore_dialog_input(&mut self, events: VecDeque<tb_runtime::builtins::Eingabe>) {
|
||||
for input in events {
|
||||
match input.ereignis {
|
||||
Ereignis::Taste(..) | Ereignis::Maus(_) => self.rt.eingaben.push_back(input),
|
||||
event => self.rt.ereignis(event),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -563,13 +560,47 @@ impl Vm {
|
||||
);
|
||||
if let Some(frame) = self.frames.last_mut() {
|
||||
frame.form_event = Some(on_return);
|
||||
frame.handler_start = true;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn dispatch_next_form_event(&mut self) -> bool {
|
||||
while let Some(event) = self.forms.next_event() {
|
||||
if self.dispatch_form_event(event, FormEventReturn::Normal) {
|
||||
fn dispatch_next_form_event(&mut self, load_only: bool) -> bool {
|
||||
let mut i = 0;
|
||||
while let Some(event) = self.forms.events.get(i) {
|
||||
if load_only && !event.name.eq_ignore_ascii_case("LOAD") {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let laeuft = self
|
||||
.module
|
||||
.event_procs
|
||||
.iter()
|
||||
.find(|binding| {
|
||||
binding.object == event.object
|
||||
&& binding.event.eq_ignore_ascii_case(&event.name)
|
||||
})
|
||||
.is_some_and(|binding| {
|
||||
self.frames.iter().any(|frame| {
|
||||
frame.form_event.is_some() && frame.proc == binding.proc as usize
|
||||
})
|
||||
});
|
||||
// Gesperrte Timer und laufende Prozeduren bleiben anstehend.
|
||||
// Menübedienung und explizite modale Kooperation müssen trotzdem
|
||||
// andere Handler erreichen können.
|
||||
if laeuft || (self.forms.menu_is_open() && event.name.eq_ignore_ascii_case("TIMER")) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let event = self.forms.events.remove(i).unwrap();
|
||||
if self.dispatch_form_event(
|
||||
event,
|
||||
if load_only {
|
||||
FormEventReturn::Load
|
||||
} else {
|
||||
FormEventReturn::Normal
|
||||
},
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -577,6 +608,12 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn request_unload(&mut self, object: u16) -> Result<(), RuntimeError> {
|
||||
if self.frames.iter().any(|frame| {
|
||||
matches!(frame.form_event,
|
||||
Some(FormEventReturn::Unload(running)) if running == object)
|
||||
}) {
|
||||
return Ok(());
|
||||
}
|
||||
let event = FormEvent {
|
||||
object,
|
||||
array_index: None,
|
||||
@@ -590,80 +627,136 @@ impl Vm {
|
||||
}
|
||||
|
||||
fn tick(&mut self, host: &mut dyn Host) {
|
||||
self.rt.traps.suspendieren(self.forms.menu_is_open());
|
||||
self.forms.render(&mut self.rt.screen);
|
||||
if self.forms.menu_is_open() {
|
||||
host.present(&self.rt.screen);
|
||||
self.rt.pump(host, false);
|
||||
} else {
|
||||
self.rt.tick(host);
|
||||
host.present(&self.rt.screen);
|
||||
self.rt.pump(host, false);
|
||||
if !self.forms.menu_is_open() && self.rt.traps.naechste_frist().is_some() {
|
||||
self.rt.traps.zeit_pruefen(host.jetzt_ms());
|
||||
}
|
||||
}
|
||||
|
||||
fn forms_zustellen(&mut self, host: &mut dyn Host) -> bool {
|
||||
self.forms
|
||||
.resize(self.rt.screen.cols(), self.rt.screen.rows());
|
||||
self.forms.timers(host.jetzt_ms());
|
||||
if self.forms.active_form().is_some() {
|
||||
while let Some((key, shift)) = self.rt.tasten.pop_front() {
|
||||
self.forms.handle_key(&key, shift);
|
||||
/// Ein gemeinsamer Zustellpfad für Statements, DOEVENTS, SLEEP und
|
||||
/// blockierende Eingabe. Nur explizite Kooperation verschachtelt Forms.
|
||||
fn zustellen(&mut self, host: &mut dyn Host, punkt: Zustellpunkt) -> bool {
|
||||
let kooperativ = punkt != Zustellpunkt::Anweisung;
|
||||
'zustellung: loop {
|
||||
self.rt.traps.suspendieren(self.forms.menu_is_open());
|
||||
self.forms
|
||||
.resize(self.rt.screen.cols(), self.rt.screen.rows());
|
||||
if self.forms.next_deadline().is_some() {
|
||||
self.forms.timers(host.jetzt_ms());
|
||||
}
|
||||
}
|
||||
let now_ms = host.jetzt_ms();
|
||||
while let Some(m) = self.rt.maus.pop_front() {
|
||||
self.forms.handle_mouse_at(m, now_ms);
|
||||
}
|
||||
self.forms.render(&mut self.rt.screen);
|
||||
self.dispatch_next_form_event()
|
||||
}
|
||||
|
||||
/// 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.forms.menu_is_open() || !self.rt.traps.aktiv() {
|
||||
let forms_frei = kooperativ || !self.frames.iter().any(|f| f.form_event.is_some());
|
||||
if forms_frei && self.dispatch_next_form_event(false) {
|
||||
return true;
|
||||
}
|
||||
if self.rt.traps.aktiv() {
|
||||
self.rt.tasten_traps_pruefen();
|
||||
if (kooperativ || self.tick_zaehler.is_multiple_of(64))
|
||||
&& self.rt.traps.naechste_frist().is_some()
|
||||
{
|
||||
self.rt.traps.zeit_pruefen(host.jetzt_ms());
|
||||
}
|
||||
if let Some((q, ziel)) = self.rt.traps.naechstes() {
|
||||
self.trap_frame_aufsetzen(q, ziel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Unverbrauchte Konsolentasten bleiben liegen. Maus ohne Formular
|
||||
// wird verworfen; mit Formular wird jede Eingabe samt ihrer Handler
|
||||
// abgearbeitet, bevor die nächste Eingabe angenommen wird.
|
||||
let mut i = 0;
|
||||
while i < self.rt.eingaben.len() {
|
||||
if !self.forms.menu_is_open() && self.rt.taste_trappen(i) {
|
||||
if let Some((q, ziel)) = self.rt.traps.naechstes() {
|
||||
self.trap_frame_aufsetzen(q, ziel);
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if punkt == Zustellpunkt::Konsole
|
||||
&& matches!(self.rt.eingaben[i].ereignis, Ereignis::Taste(..))
|
||||
{
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if self.forms.active_form().is_some() {
|
||||
if !forms_frei {
|
||||
break;
|
||||
}
|
||||
match self.rt.eingaben.remove(i).unwrap().ereignis {
|
||||
Ereignis::Taste(key, shift) => {
|
||||
let menu_war_offen = self.forms.menu_is_open();
|
||||
self.forms.handle_key(&key, shift);
|
||||
self.rt.traps.suspendieren(self.forms.menu_is_open());
|
||||
if menu_war_offen && !self.forms.menu_is_open() {
|
||||
continue 'zustellung;
|
||||
}
|
||||
}
|
||||
Ereignis::Maus(m) => {
|
||||
if m.zeile > 0
|
||||
&& m.zeile <= self.rt.screen.rows()
|
||||
&& m.spalte > 0
|
||||
&& m.spalte <= self.rt.screen.cols()
|
||||
{
|
||||
let now = if self.forms.mouse_needs_time(m) {
|
||||
host.jetzt_ms()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.forms.handle_mouse_at(m, now);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
if self.dispatch_next_form_event(false) {
|
||||
return true;
|
||||
}
|
||||
} else if matches!(self.rt.eingaben[i].ereignis, Ereignis::Maus(_)) {
|
||||
self.rt.eingaben.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
self.forms.render(&mut self.rt.screen);
|
||||
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 warten(&mut self, host: &mut dyn Host, deadline: Option<u64>) {
|
||||
let deadline = deadline
|
||||
.into_iter()
|
||||
.chain(self.forms.next_deadline())
|
||||
.chain(if self.forms.menu_is_open() {
|
||||
None
|
||||
} else {
|
||||
self.rt.traps.naechste_frist()
|
||||
})
|
||||
.min();
|
||||
if let Some(event) = host.warten(deadline) {
|
||||
self.rt.ereignis(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ein zugestelltes Ereignis beendet SLEEP; Fristen und Warten gehören
|
||||
/// ausschließlich dem Host, einschließlich einer virtuellen Uhr.
|
||||
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();
|
||||
let deadline =
|
||||
(sekunden > 0.0).then(|| host.jetzt_ms().saturating_add((sekunden * 1000.0) as u64));
|
||||
loop {
|
||||
self.tick(host);
|
||||
if self.zustellen(host) {
|
||||
if self.zustellen(host, Zustellpunkt::Kooperativ) || self.rt.ende || self.rt.abbruch {
|
||||
return;
|
||||
}
|
||||
if self.rt.ende || self.rt.abbruch {
|
||||
if let Some(deadline) = deadline {
|
||||
if host.jetzt_ms() >= deadline {
|
||||
return;
|
||||
}
|
||||
} else if self.rt.naechste_taste(host, false).is_some() {
|
||||
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));
|
||||
self.warten(host, deadline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,6 +779,8 @@ impl Vm {
|
||||
waiting_form: None,
|
||||
pending_show: None,
|
||||
form_event: None,
|
||||
handler_start: true,
|
||||
eingabe: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -699,7 +794,11 @@ impl Vm {
|
||||
|
||||
fn pop_frame(&mut self) {
|
||||
let f = self.frames.pop().expect("Frame-Unterlauf");
|
||||
self.locals.truncate(f.locals_base);
|
||||
if let Some(q) = f.trap {
|
||||
self.rt.traps.handler_beendet(q);
|
||||
} else {
|
||||
self.locals.truncate(f.locals_base);
|
||||
}
|
||||
self.stack.truncate(f.stack_base);
|
||||
}
|
||||
|
||||
@@ -806,14 +905,14 @@ impl Vm {
|
||||
pub fn run_visible_forms(&mut self, host: &mut dyn Host) -> RunEvent {
|
||||
while self.forms.has_visible_forms() {
|
||||
self.tick(host);
|
||||
let dispatched = self.forms_zustellen(host);
|
||||
let dispatched = self.zustellen(host, Zustellpunkt::Kooperativ);
|
||||
if dispatched {
|
||||
match self.run(host) {
|
||||
RunEvent::Ended => {}
|
||||
event => return event,
|
||||
}
|
||||
}
|
||||
if self.rt.ende {
|
||||
if !dispatched && self.rt.ende {
|
||||
return RunEvent::Ended;
|
||||
}
|
||||
if self.rt.abbruch {
|
||||
@@ -821,7 +920,9 @@ impl Vm {
|
||||
line: self.current_line(),
|
||||
};
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
if !dispatched && self.forms.has_visible_forms() {
|
||||
self.warten(host, None);
|
||||
}
|
||||
}
|
||||
RunEvent::Ended
|
||||
}
|
||||
@@ -833,11 +934,18 @@ impl Vm {
|
||||
self.frames.last_mut().unwrap().waiting_form = None;
|
||||
} else {
|
||||
self.tick(host);
|
||||
self.forms_zustellen(host);
|
||||
if self.rt.ende {
|
||||
let dispatched = self.zustellen(host, Zustellpunkt::Kooperativ);
|
||||
if !dispatched && self.rt.ende {
|
||||
return RunEvent::Ended;
|
||||
}
|
||||
std::thread::yield_now();
|
||||
if self.rt.abbruch {
|
||||
return RunEvent::Interrupted {
|
||||
line: self.current_line(),
|
||||
};
|
||||
}
|
||||
if !dispatched {
|
||||
self.warten(host, None);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -898,6 +1006,7 @@ impl Vm {
|
||||
(f.stack_base, f.last_stmt_pc, f.proc)
|
||||
};
|
||||
self.stack.truncate(stack_base);
|
||||
self.frames.last_mut().unwrap().eingabe = None;
|
||||
self.err = code;
|
||||
match handler {
|
||||
Handler::Goto(pc) => {
|
||||
@@ -1131,27 +1240,11 @@ impl Vm {
|
||||
self.tick(host);
|
||||
self.rt.screen.veraenderung_quittieren();
|
||||
}
|
||||
if self.forms_zustellen(host) {
|
||||
let erster_eintritt =
|
||||
std::mem::take(&mut self.frames.last_mut().unwrap().handler_start);
|
||||
if !erster_eintritt && self.zustellen(host, Zustellpunkt::Anweisung) {
|
||||
return Ok(Flow::Normal);
|
||||
}
|
||||
// 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.is_multiple_of(64) {
|
||||
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 }));
|
||||
@@ -1193,7 +1286,7 @@ impl Vm {
|
||||
1 => tb_runtime::traps::Zustand::Aus,
|
||||
_ => tb_runtime::traps::Zustand::Gestoppt,
|
||||
};
|
||||
let jetzt = host.jetzt_ms();
|
||||
let jetzt = if art == ART_TIMER { host.jetzt_ms() } else { 0 };
|
||||
self.rt.traps.setzen(q, z, jetzt);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
@@ -1203,8 +1296,7 @@ impl Vm {
|
||||
// lässt den Wert unberührt.
|
||||
self.stack.push(Value::Int(0));
|
||||
self.tick(host);
|
||||
self.forms_zustellen(host);
|
||||
self.zustellen(host);
|
||||
self.zustellen(host, Zustellpunkt::Kooperativ);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::Sleep(mit_arg) => {
|
||||
@@ -1258,7 +1350,7 @@ impl Vm {
|
||||
};
|
||||
if !self.forms.is_loaded_at(object, index) {
|
||||
self.forms.ensure_loaded_at(object, index)?;
|
||||
if index.is_none() && self.dispatch_next_form_event() {
|
||||
if index.is_none() && self.dispatch_next_form_event(true) {
|
||||
let caller = self.frames.len() - 2;
|
||||
self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
|
||||
return Ok(Flow::Normal);
|
||||
@@ -1280,7 +1372,7 @@ impl Vm {
|
||||
if index.is_none() {
|
||||
self.push(value.clone());
|
||||
}
|
||||
if index.is_none() && self.dispatch_next_form_event() {
|
||||
if index.is_none() && self.dispatch_next_form_event(true) {
|
||||
let caller = self.frames.len() - 2;
|
||||
self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
|
||||
return Ok(Flow::Normal);
|
||||
@@ -1304,7 +1396,7 @@ impl Vm {
|
||||
};
|
||||
self.forms.ensure_loaded_at(object, index)?;
|
||||
self.push(Value::Obj(object, index));
|
||||
self.dispatch_next_form_event();
|
||||
self.dispatch_next_form_event(true);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::LoadDynamicObjectProperty(property) => {
|
||||
@@ -1434,7 +1526,7 @@ impl Vm {
|
||||
&& !self.forms.is_loaded(object)
|
||||
{
|
||||
self.forms.ensure_loaded(object)?;
|
||||
if self.dispatch_next_form_event() {
|
||||
if self.dispatch_next_form_event(true) {
|
||||
let caller = self.frames.len() - 2;
|
||||
self.frames[caller].pending_show = Some(object);
|
||||
self.frames[caller].pc = self.frames[caller].pc.saturating_sub(1);
|
||||
@@ -1493,7 +1585,7 @@ impl Vm {
|
||||
self.forms.object_method_at(object, index, name, args)?;
|
||||
}
|
||||
}
|
||||
self.dispatch_next_form_event();
|
||||
self.dispatch_next_form_event(true);
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::ObjectLoad(object, unload, has_index) => {
|
||||
@@ -1508,7 +1600,7 @@ impl Vm {
|
||||
self.request_unload(object)?;
|
||||
} else {
|
||||
self.forms.ensure_loaded(object)?;
|
||||
self.dispatch_next_form_event();
|
||||
self.dispatch_next_form_event(true);
|
||||
}
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
@@ -2058,6 +2150,8 @@ impl Vm {
|
||||
self.pop_frame();
|
||||
}
|
||||
let f = self.frames.last_mut().unwrap();
|
||||
f.eingabe = None;
|
||||
self.stack.truncate(f.stack_base);
|
||||
f.pc = t as usize;
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
@@ -2122,8 +2216,10 @@ impl Vm {
|
||||
if let Some(FormEventReturn::Unload(object)) = event {
|
||||
self.forms.unload_with(object, |c| *c = cancel)?;
|
||||
}
|
||||
if event.is_some() {
|
||||
self.dispatch_next_form_event();
|
||||
if matches!(event, Some(FormEventReturn::Normal))
|
||||
&& !self.frames.iter().any(|f| f.form_event.is_some())
|
||||
{
|
||||
self.dispatch_next_form_event(false);
|
||||
}
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
@@ -2134,6 +2230,38 @@ impl Vm {
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::CallBuiltin(id, argc) => {
|
||||
if id == ids::RANDOMIZE && argc == 0 {
|
||||
if self.frames.last().unwrap().eingabe.is_none() {
|
||||
self.rt
|
||||
.screen
|
||||
.print("Random Number Seed (-32768 to 32767)? ");
|
||||
}
|
||||
let caller = self.frames.len() - 1;
|
||||
if let Some(text) = self.konsoleneingabe(host, None)? {
|
||||
self.rt.saat_setzen(tb_runtime::format::val(&text));
|
||||
} else {
|
||||
self.frames[caller].pc = pc;
|
||||
}
|
||||
return Ok(Flow::Normal);
|
||||
}
|
||||
if id == ids::INPUT_S && argc == 1 {
|
||||
let n = match self.stack.last() {
|
||||
Some(Value::Int(n)) => i32::from(*n),
|
||||
Some(Value::Lng(n)) => *n,
|
||||
_ => return Err(RuntimeError::TYPE_MISMATCH),
|
||||
};
|
||||
if n < 0 {
|
||||
return Err(RuntimeError::ILLEGAL_FUNCTION_CALL);
|
||||
}
|
||||
let caller = self.frames.len() - 1;
|
||||
if let Some(text) = self.konsoleneingabe(host, Some(n as usize))? {
|
||||
self.pop()?;
|
||||
self.push(Value::Str(Rc::from(text)));
|
||||
} else {
|
||||
self.frames[caller].pc = pc;
|
||||
}
|
||||
return Ok(Flow::Normal);
|
||||
}
|
||||
let mut args = Vec::with_capacity(argc as usize);
|
||||
for _ in 0..argc {
|
||||
args.push(self.pop()?);
|
||||
@@ -2223,7 +2351,10 @@ impl Vm {
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::Input(argc, line_mode, prompt, question) => {
|
||||
self.do_input(argc, line_mode, prompt, question, host)?;
|
||||
let caller = self.frames.len() - 1;
|
||||
if !self.do_input(argc, line_mode, prompt, question, host)? {
|
||||
self.frames[caller].pc = pc;
|
||||
}
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
I::InputFile(argc, line_mode) => {
|
||||
@@ -2272,6 +2403,64 @@ impl Vm {
|
||||
Ok(Flow::Normal)
|
||||
}
|
||||
|
||||
/// Fortsetzbarer Leser: Text und Restlänge gehören zum unterbrochenen
|
||||
/// Frame; Referenzen/Argumente bleiben bis zum Abschluss auf dessen Stack.
|
||||
fn konsoleneingabe(
|
||||
&mut self,
|
||||
host: &mut dyn Host,
|
||||
rest: Option<usize>,
|
||||
) -> Result<Option<String>, RuntimeError> {
|
||||
let caller = self.frames.len() - 1;
|
||||
self.frames[caller]
|
||||
.eingabe
|
||||
.get_or_insert_with(|| KonsolenEingabe {
|
||||
text: String::new(),
|
||||
rest,
|
||||
});
|
||||
loop {
|
||||
if self.frames[caller].eingabe.as_ref().unwrap().rest == Some(0) {
|
||||
return Ok(Some(self.frames[caller].eingabe.take().unwrap().text));
|
||||
}
|
||||
self.tick(host);
|
||||
if self.zustellen(host, Zustellpunkt::Konsole) {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(taste) = self.rt.gepufferte_taste() {
|
||||
let eingabe = self.frames[caller].eingabe.as_mut().unwrap();
|
||||
if let Some(rest) = &mut eingabe.rest {
|
||||
eingabe.text.push_str(&taste);
|
||||
*rest -= 1;
|
||||
} else {
|
||||
match taste.as_str() {
|
||||
tb_runtime::host::taste::ENTER => {
|
||||
self.rt.screen.print("\n");
|
||||
return Ok(Some(self.frames[caller].eingabe.take().unwrap().text));
|
||||
}
|
||||
tb_runtime::host::taste::BACKSPACE => {
|
||||
if eingabe.text.pop().is_some() {
|
||||
self.rt.screen.rueckschritt();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if taste.chars().count() == 1
|
||||
&& taste.chars().next().is_some_and(|c| c as u32 >= 0x20)
|
||||
{
|
||||
eingabe.text.push_str(&taste);
|
||||
self.rt.screen.print(&taste);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if self.rt.ende || self.rt.abbruch {
|
||||
self.frames[caller].eingabe = None;
|
||||
return Err(RuntimeError(62));
|
||||
}
|
||||
self.warten(host, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_input(
|
||||
&mut self,
|
||||
argc: u8,
|
||||
@@ -2279,38 +2468,41 @@ impl Vm {
|
||||
prompt: u16,
|
||||
question: bool,
|
||||
host: &mut dyn Host,
|
||||
) -> Result<(), RuntimeError> {
|
||||
// Referenzen in Reihenfolge einsammeln.
|
||||
let mut refs = Vec::with_capacity(argc as usize);
|
||||
for _ in 0..argc {
|
||||
match self.pop()? {
|
||||
Value::Ref(r) => refs.push(r),
|
||||
_ => return Err(RuntimeError::TYPE_MISMATCH),
|
||||
}
|
||||
}
|
||||
refs.reverse();
|
||||
|
||||
) -> Result<bool, RuntimeError> {
|
||||
let start = self
|
||||
.stack
|
||||
.len()
|
||||
.checked_sub(argc as usize)
|
||||
.ok_or(RuntimeError(51))?;
|
||||
let refs = self.stack[start..]
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Value::Ref(r) => Ok(r.clone()),
|
||||
_ => Err(RuntimeError::TYPE_MISMATCH),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
loop {
|
||||
if prompt != 0xFFFF {
|
||||
let p = self.module.strings[prompt as usize].clone();
|
||||
self.rt.screen.print(&p);
|
||||
if self.frames.last().unwrap().eingabe.is_none() {
|
||||
if prompt != 0xFFFF {
|
||||
self.rt.screen.print(&self.module.strings[prompt as usize]);
|
||||
}
|
||||
if question {
|
||||
self.rt.screen.print("? ");
|
||||
}
|
||||
}
|
||||
if question {
|
||||
self.rt.screen.print("? ");
|
||||
}
|
||||
let Some(mut line) = self.rt.read_line(host) else {
|
||||
return Err(RuntimeError(62)); // Input past end of file
|
||||
let Some(line) = self.konsoleneingabe(host, None)? else {
|
||||
return Ok(false);
|
||||
};
|
||||
if line_mode {
|
||||
let r = refs.first().ok_or(RuntimeError(51))?;
|
||||
self.write_ref(r, Value::Str(Rc::from(line.as_str())))?;
|
||||
return Ok(());
|
||||
self.write_ref(r, Value::Str(Rc::from(line)))?;
|
||||
self.stack.truncate(start);
|
||||
return Ok(true);
|
||||
}
|
||||
// Felder trennen (Quotes respektieren).
|
||||
let fields = split_input_fields(&line);
|
||||
if fields.len() != refs.len() {
|
||||
self.rt.screen.print("Redo from start\n");
|
||||
line.clear();
|
||||
continue;
|
||||
}
|
||||
// Konvertieren nach aktuellem Zieltyp; Fehler → Redo.
|
||||
@@ -2333,7 +2525,8 @@ impl Vm {
|
||||
for (r, v) in refs.iter().zip(vals) {
|
||||
self.write_ref(r, v)?;
|
||||
}
|
||||
return Ok(());
|
||||
self.stack.truncate(self.stack.len() - argc as usize);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user