544 lines
18 KiB
Rust
544 lines
18 KiB
Rust
//! Zustände der klassischen Ereignis-Traps (Sprachreferenz §8).
|
||
//!
|
||
//! Die Fachlogik liegt hier, ohne VM und ohne Host: welche Quelle in
|
||
//! welchem Zustand ist, was ansteht und was als Nächstes zuzustellen wäre.
|
||
//! Wer das Sprungziel anspringt, entscheidet die VM.
|
||
//!
|
||
//! Belege und Festlegungen: `umfang-und-form.md` des Changes
|
||
//! `phase-4-ereignisschleife`.
|
||
|
||
/// Eine Ereignisquelle. Die Reihenfolge der Varianten ist zugleich die
|
||
/// Zustellreihenfolge bei gleichzeitiger Fälligkeit (Festlegung, weil das
|
||
/// Vorbild dazu nichts hergibt): Tasten zuerst, weil sie an einer
|
||
/// Benutzereingabe hängen.
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||
pub enum Quelle {
|
||
/// `ON KEY(n)` — n aus 0, 1–25, 30–31.
|
||
Key(u8),
|
||
/// `ON TIMER(n)`.
|
||
Timer,
|
||
/// `ON UEVENT`.
|
||
UEvent,
|
||
/// `ON SIGNAL(n)` — n aus 1–2.
|
||
Signal(u8),
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||
pub enum Zustand {
|
||
/// Zustellen.
|
||
An,
|
||
/// Verwerfen — auch das bereits Gemerkte.
|
||
#[default]
|
||
Aus,
|
||
/// Höchstens eines merken und beim nächsten `ON` zustellen.
|
||
Gestoppt,
|
||
}
|
||
|
||
/// Ein definierter Trap: Ziel, Zustand, ob etwas ansteht.
|
||
#[derive(Debug, Clone, Copy)]
|
||
struct Trap {
|
||
quelle: Quelle,
|
||
/// Sprungziel im Modulrumpf. `None` = `GOSUB 0`, also abgeschaltet.
|
||
ziel: Option<u32>,
|
||
zustand: Zustand,
|
||
/// Höchstens ein gemerktes Ereignis (Vorbild: „remembered").
|
||
anstehend: bool,
|
||
/// Läuft der Handler dieser Quelle gerade?
|
||
laeuft: bool,
|
||
/// Wurde im laufenden Handler ausdrücklich `OFF` ausgeführt? Dann
|
||
/// unterbleibt das selbsttätige `ON` beim `RETURN`.
|
||
off_im_handler: bool,
|
||
}
|
||
|
||
/// Alle Traps eines Moduls.
|
||
#[derive(Debug, Default)]
|
||
pub struct Traps {
|
||
traps: Vec<Trap>,
|
||
/// `EVENT OFF` klammert einen Abschnitt ohne Ereignisprüfung. Kein
|
||
/// dritter Zustand — den kennt das Vorbild nicht.
|
||
event_an: bool,
|
||
/// Menüsperre ohne Änderung der expliziten ON/OFF/STOP-Zustände.
|
||
suspendiert: bool,
|
||
/// Fälligkeit des Zeit-Traps: Intervall und der Zeitpunkt, an dem er
|
||
/// das nächste Mal fällig wird (beides in Millisekunden).
|
||
timer_intervall_ms: u64,
|
||
timer_faellig_ms: u64,
|
||
/// Benutzerdefinierte Tasten 15–25: Umschaltzustand und Scancode aus
|
||
/// `KEY n, CHR$(flag) + CHR$(scancode)`.
|
||
benutzertasten: [Option<(u8, u8)>; 11],
|
||
/// Gibt es überhaupt einen nicht abgeschalteten Trap? Gepflegt statt
|
||
/// gerechnet: die VM fragt das an jeder Anweisungsgrenze (design.md, D4).
|
||
aktiv: bool,
|
||
}
|
||
|
||
impl Traps {
|
||
pub fn neu() -> Self {
|
||
Traps {
|
||
event_an: true,
|
||
..Default::default()
|
||
}
|
||
}
|
||
|
||
fn finde(&mut self, q: Quelle) -> Option<&mut Trap> {
|
||
self.traps.iter_mut().find(|t| t.quelle == q)
|
||
}
|
||
|
||
/// `ON <quelle> GOSUB ziel`. `ziel = None` schaltet den Trap ab
|
||
/// (`GOSUB 0`). Der Zustand bleibt, was er war — das Einschalten ist
|
||
/// Sache der Steueranweisung.
|
||
pub fn definieren(&mut self, q: Quelle, ziel: Option<u32>) {
|
||
if let Some(t) = self.finde(q) {
|
||
t.ziel = ziel;
|
||
if ziel.is_none() {
|
||
t.anstehend = false;
|
||
}
|
||
self.aktiv = self.traps.iter().any(|t| t.ziel.is_some());
|
||
return;
|
||
}
|
||
self.aktiv = self.aktiv || ziel.is_some();
|
||
self.traps.push(Trap {
|
||
quelle: q,
|
||
ziel,
|
||
zustand: Zustand::Aus,
|
||
anstehend: false,
|
||
laeuft: false,
|
||
off_im_handler: false,
|
||
});
|
||
}
|
||
|
||
/// Intervall des Zeit-Traps setzen (Sekunden, 1–86 400) und die erste
|
||
/// Fälligkeit von `jetzt_ms` aus rechnen.
|
||
pub fn timer_intervall(&mut self, sekunden: u64, jetzt_ms: u64) {
|
||
self.timer_intervall_ms = sekunden.saturating_mul(1000);
|
||
self.timer_faellig_ms = jetzt_ms.saturating_add(self.timer_intervall_ms);
|
||
}
|
||
|
||
/// Steueranweisung `<quelle> ON|OFF|STOP`. Ohne vorangehende
|
||
/// Trap-Definition bleibt sie wirkungslos, aber fehlerfrei.
|
||
pub fn setzen(&mut self, q: Quelle, z: Zustand, jetzt_ms: u64) {
|
||
let neu_faellig = matches!(q, Quelle::Timer) && z == Zustand::An;
|
||
let intervall = self.timer_intervall_ms;
|
||
if let Some(t) = self.finde(q) {
|
||
if z == Zustand::Aus {
|
||
// „OFF … Events occurring while trapping is off are
|
||
// ignored" und verwirft auch das unter STOP Gemerkte.
|
||
t.anstehend = false;
|
||
if t.laeuft {
|
||
t.off_im_handler = true;
|
||
}
|
||
}
|
||
t.zustand = z;
|
||
}
|
||
// Ein neu eingeschalteter Zeit-Trap zählt ab jetzt.
|
||
if neu_faellig && intervall > 0 {
|
||
self.timer_faellig_ms = jetzt_ms.saturating_add(intervall);
|
||
}
|
||
}
|
||
|
||
/// `EVENT ON` / `EVENT OFF`.
|
||
pub fn event(&mut self, an: bool) {
|
||
if !an {
|
||
for trap in &mut self.traps {
|
||
trap.anstehend = false;
|
||
}
|
||
}
|
||
self.event_an = an;
|
||
}
|
||
|
||
/// Ein Ereignis ist eingetreten. Es wird nur gemerkt, wenn die Quelle
|
||
/// einen Trap hat und nicht auf `OFF` steht.
|
||
pub fn melden(&mut self, q: Quelle) -> bool {
|
||
if !self.event_an {
|
||
return false;
|
||
}
|
||
if let Some(t) = self.finde(q) {
|
||
if t.ziel.is_some() && t.zustand != Zustand::Aus {
|
||
t.anstehend = true;
|
||
return true;
|
||
}
|
||
}
|
||
false
|
||
}
|
||
|
||
/// Zeit prüfen und den Zeit-Trap melden, wenn die Frist verstrichen ist.
|
||
/// Übersprungene Fristen zählen als eine — das Vorbild merkt sich nur
|
||
/// eines.
|
||
pub fn zeit_pruefen(&mut self, jetzt_ms: u64) {
|
||
if self.timer_intervall_ms == 0 || jetzt_ms < self.timer_faellig_ms {
|
||
return;
|
||
}
|
||
// Nächste Fälligkeit vom jetzigen Zeitpunkt aus, nicht kumulativ:
|
||
// sonst holt ein langer Handler die verpassten Fristen nach.
|
||
self.timer_faellig_ms = jetzt_ms.saturating_add(self.timer_intervall_ms);
|
||
self.melden(Quelle::Timer);
|
||
}
|
||
|
||
/// Gibt es einen definierten, nicht abgeschalteten Trap? Solange nicht,
|
||
/// kostet die Prüfung an der Anweisungsgrenze nichts (design.md, D4).
|
||
#[inline]
|
||
pub fn aktiv(&self) -> bool {
|
||
self.aktiv
|
||
}
|
||
|
||
pub fn suspendieren(&mut self, gesperrt: bool) {
|
||
self.suspendiert = gesperrt;
|
||
}
|
||
|
||
pub fn ist_an(&self, q: Quelle) -> bool {
|
||
self.traps
|
||
.iter()
|
||
.any(|t| t.quelle == q && t.zustand == Zustand::An)
|
||
}
|
||
|
||
/// Nächste Hostfrist, sofern der Zeit-Trap überhaupt Zeit benötigt.
|
||
pub fn naechste_frist(&self) -> Option<u64> {
|
||
(self.event_an
|
||
&& !self.suspendiert
|
||
&& self.timer_intervall_ms > 0
|
||
&& self.traps.iter().any(|t| {
|
||
t.quelle == Quelle::Timer && t.ziel.is_some() && t.zustand != Zustand::Aus
|
||
}))
|
||
.then_some(self.timer_faellig_ms)
|
||
}
|
||
|
||
/// Benutzerdefinierte Taste erklären (`KEY n, CHR$(flag)+CHR$(scan)`,
|
||
/// n = 15–25). `flag` ist das Tastaturflagbyte des Vorbilds.
|
||
pub fn benutzertaste(&mut self, n: u8, flag: u8, scancode: u8) {
|
||
if (15..=25).contains(&n) {
|
||
self.benutzertasten[n as usize - 15] = Some((flag_zu_umschalt(flag), scancode));
|
||
}
|
||
}
|
||
|
||
/// Welcher `KEY`-Trap fängt diesen Tastendruck? Geprüft wird gegen den
|
||
/// Scancode und — bei den benutzerdefinierten Tasten — zusätzlich gegen
|
||
/// den Umschaltzustand. `KEY(0)` fängt alle Tasten der Tabelle.
|
||
///
|
||
/// Liefert die Trap-Nummer, nicht nur „ja/nein": der Aufrufer muss
|
||
/// wissen, welche Quelle er melden soll.
|
||
pub fn taste_faengt(&self, scancode: u8, shift: u8) -> Option<u8> {
|
||
if !self.event_an || self.suspendiert {
|
||
return None;
|
||
}
|
||
let treffer = |k: u8| -> bool {
|
||
match k {
|
||
0 => vordefinierte_taste(scancode).is_some(),
|
||
1..=14 | 30 | 31 => vordefinierte_taste(scancode) == Some(k),
|
||
15..=25 => self.benutzertasten[k as usize - 15] == Some((shift, scancode)),
|
||
_ => false,
|
||
}
|
||
};
|
||
self.traps
|
||
.iter()
|
||
.filter(|t| t.ziel.is_some() && t.zustand != Zustand::Aus && !t.laeuft)
|
||
.filter_map(|t| match t.quelle {
|
||
Quelle::Key(k) if treffer(k) => Some(k),
|
||
_ => None,
|
||
})
|
||
// Ein genauer Trap geht dem Sammeltrap `KEY(0)` vor.
|
||
.min_by_key(|&k| if k == 0 { u8::MAX } else { k })
|
||
}
|
||
|
||
/// Nächstes zuzustellendes Ereignis: Quelle und Sprungziel. Liefert
|
||
/// höchstens eines je Aufruf; die übrigen bleiben anstehend.
|
||
pub fn naechstes(&mut self) -> Option<(Quelle, u32)> {
|
||
if !self.event_an || self.suspendiert {
|
||
return None;
|
||
}
|
||
let mut kandidaten: Vec<usize> = (0..self.traps.len())
|
||
.filter(|&i| {
|
||
let t = &self.traps[i];
|
||
t.anstehend && t.zustand == Zustand::An && !t.laeuft && t.ziel.is_some()
|
||
})
|
||
.collect();
|
||
kandidaten.sort_by_key(|&i| self.traps[i].quelle);
|
||
let i = *kandidaten.first()?;
|
||
let t = &mut self.traps[i];
|
||
t.anstehend = false;
|
||
// „an automatic TIMER STOP is executed so that recursive traps
|
||
// cannot take place" — gilt für jede Quelle.
|
||
t.laeuft = true;
|
||
t.off_im_handler = false;
|
||
Some((t.quelle, t.ziel.unwrap()))
|
||
}
|
||
|
||
/// `RETURN` aus dem Handler: „automatically performs a TIMER ON
|
||
/// statement unless an explicit TIMER OFF was performed inside".
|
||
pub fn handler_beendet(&mut self, q: Quelle) {
|
||
if let Some(t) = self.finde(q) {
|
||
t.laeuft = false;
|
||
if t.off_im_handler {
|
||
t.zustand = Zustand::Aus;
|
||
t.anstehend = false;
|
||
} else {
|
||
t.zustand = Zustand::An;
|
||
}
|
||
t.off_im_handler = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Tastaturflagbyte des Vorbilds → unser Bitfeld (1 Umschalt, 2 Strg,
|
||
/// 4 Alt). Links und rechts sind im Terminal nicht unterscheidbar, beide
|
||
/// Umschaltbits gelten deshalb gleich; NumLock und CapsLock spielen für
|
||
/// einen Trap keine Rolle (Festlegung, siehe Sprachreferenz §8).
|
||
fn flag_zu_umschalt(flag: u8) -> u8 {
|
||
let mut u = 0;
|
||
if flag & 0b11 != 0 {
|
||
u |= 1;
|
||
}
|
||
if flag & 0b100 != 0 {
|
||
u |= 2;
|
||
}
|
||
if flag & 0b1000 != 0 {
|
||
u |= 4;
|
||
}
|
||
u
|
||
}
|
||
|
||
/// Scancode → Nummer des vordefinierten Traps (`ON KEY(1)`–`ON KEY(14)`,
|
||
/// `ON KEY(30)`/`ON KEY(31)`), Tabelle aus der Original-Hilfe.
|
||
fn vordefinierte_taste(scancode: u8) -> Option<u8> {
|
||
Some(match scancode {
|
||
0x3B..=0x44 => scancode - 0x3A, // F1–F10 → 1–10
|
||
0x48 => 11, // Pfeil hoch
|
||
0x4B => 12, // Pfeil links
|
||
0x4D => 13, // Pfeil rechts
|
||
0x50 => 14, // Pfeil runter
|
||
0x85 => 30, // F11
|
||
0x86 => 31, // F12
|
||
_ => return None,
|
||
})
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn mit_timer() -> Traps {
|
||
let mut t = Traps::neu();
|
||
t.definieren(Quelle::Timer, Some(100));
|
||
t.timer_intervall(1, 0);
|
||
t
|
||
}
|
||
|
||
#[test]
|
||
fn ohne_trap_kostet_nichts() {
|
||
let mut t = Traps::neu();
|
||
assert!(!t.aktiv());
|
||
assert_eq!(t.naechstes(), None);
|
||
t.definieren(Quelle::Timer, Some(7));
|
||
assert!(t.aktiv());
|
||
}
|
||
|
||
#[test]
|
||
fn aus_verwirft_auch_das_gemerkte() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::Gestoppt, 0);
|
||
t.zeit_pruefen(1000);
|
||
t.setzen(Quelle::Timer, Zustand::Aus, 1000);
|
||
t.setzen(Quelle::Timer, Zustand::An, 1000);
|
||
assert_eq!(t.naechstes(), None, "OFF verwirft das unter STOP Gemerkte");
|
||
}
|
||
|
||
#[test]
|
||
fn gestoppt_merkt_genau_eines() {
|
||
let mut t = Traps::neu();
|
||
t.definieren(Quelle::Key(1), Some(42));
|
||
t.setzen(Quelle::Key(1), Zustand::Gestoppt, 0);
|
||
t.melden(Quelle::Key(1));
|
||
t.melden(Quelle::Key(1));
|
||
t.melden(Quelle::Key(1));
|
||
t.setzen(Quelle::Key(1), Zustand::An, 0);
|
||
assert_eq!(t.naechstes(), Some((Quelle::Key(1), 42)));
|
||
t.handler_beendet(Quelle::Key(1));
|
||
assert_eq!(t.naechstes(), None, "gemerkt wird hoechstens eines");
|
||
}
|
||
|
||
#[test]
|
||
fn aus_verwirft_kuenftige() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::Aus, 0);
|
||
t.zeit_pruefen(5000);
|
||
t.setzen(Quelle::Timer, Zustand::An, 5000);
|
||
assert_eq!(t.naechstes(), None);
|
||
}
|
||
|
||
#[test]
|
||
fn zeit_wird_erst_nach_der_frist_faellig() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::An, 0);
|
||
t.zeit_pruefen(999);
|
||
assert_eq!(t.naechstes(), None);
|
||
t.zeit_pruefen(1000);
|
||
assert_eq!(t.naechstes(), Some((Quelle::Timer, 100)));
|
||
}
|
||
|
||
#[test]
|
||
fn uebersprungene_fristen_zaehlen_als_eine() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::An, 0);
|
||
t.zeit_pruefen(10_000); // zehn Fristen auf einmal
|
||
assert_eq!(t.naechstes(), Some((Quelle::Timer, 100)));
|
||
t.handler_beendet(Quelle::Timer);
|
||
t.zeit_pruefen(10_000);
|
||
assert_eq!(t.naechstes(), None, "nicht nachgeholt");
|
||
}
|
||
|
||
#[test]
|
||
fn kein_wiedereintritt_waehrend_der_handler_laeuft() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::An, 0);
|
||
t.zeit_pruefen(1000);
|
||
assert_eq!(t.naechstes(), Some((Quelle::Timer, 100)));
|
||
t.zeit_pruefen(2000);
|
||
assert_eq!(t.naechstes(), None, "kein Wiedereintritt");
|
||
t.handler_beendet(Quelle::Timer);
|
||
assert_eq!(t.naechstes(), Some((Quelle::Timer, 100)), "danach einmal");
|
||
}
|
||
|
||
#[test]
|
||
fn off_im_handler_ueberlebt_das_return() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::An, 0);
|
||
t.zeit_pruefen(1000);
|
||
assert_eq!(t.naechstes(), Some((Quelle::Timer, 100)));
|
||
t.setzen(Quelle::Timer, Zustand::Aus, 1000); // OFF im Handler
|
||
t.handler_beendet(Quelle::Timer);
|
||
t.zeit_pruefen(9000);
|
||
assert_eq!(t.naechstes(), None, "RETURN schaltet nicht wieder ein");
|
||
}
|
||
|
||
#[test]
|
||
fn return_schaltet_sonst_selbsttaetig_ein() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::An, 0);
|
||
t.zeit_pruefen(1000);
|
||
t.naechstes().unwrap();
|
||
t.handler_beendet(Quelle::Timer);
|
||
t.zeit_pruefen(2000);
|
||
assert_eq!(t.naechstes(), Some((Quelle::Timer, 100)));
|
||
}
|
||
|
||
#[test]
|
||
fn event_off_verwirft_ereignisse_und_laesst_die_zustaende() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::An, 0);
|
||
t.event(false);
|
||
t.zeit_pruefen(1000);
|
||
assert_eq!(t.naechstes(), None);
|
||
t.event(true);
|
||
assert_eq!(
|
||
t.naechstes(),
|
||
None,
|
||
"EVENT OFF muss Eingetretenes verwerfen"
|
||
);
|
||
t.zeit_pruefen(2000);
|
||
assert_eq!(
|
||
t.naechstes(),
|
||
Some((Quelle::Timer, 100)),
|
||
"TIMER ON blieb erhalten"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn reihenfolge_key_vor_timer_vor_uevent_vor_signal() {
|
||
let mut t = Traps::neu();
|
||
for (q, ziel) in [
|
||
(Quelle::Signal(1), 4),
|
||
(Quelle::UEvent, 3),
|
||
(Quelle::Timer, 2),
|
||
(Quelle::Key(5), 1),
|
||
] {
|
||
t.definieren(q, Some(ziel));
|
||
t.setzen(q, Zustand::An, 0);
|
||
t.melden(q);
|
||
}
|
||
let mut reihe = Vec::new();
|
||
while let Some((q, _)) = t.naechstes() {
|
||
reihe.push(q);
|
||
t.handler_beendet(q);
|
||
}
|
||
assert_eq!(
|
||
reihe,
|
||
vec![
|
||
Quelle::Key(5),
|
||
Quelle::Timer,
|
||
Quelle::UEvent,
|
||
Quelle::Signal(1)
|
||
]
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn key_null_faengt_alle_tasten_der_tabelle() {
|
||
let mut t = Traps::neu();
|
||
t.definieren(Quelle::Key(0), Some(9));
|
||
t.setzen(Quelle::Key(0), Zustand::An, 0);
|
||
assert_eq!(t.taste_faengt(0x3B, 0), Some(0), "F1");
|
||
assert_eq!(t.taste_faengt(0x48, 0), Some(0), "Pfeil hoch");
|
||
assert_eq!(t.taste_faengt(0x1E, 0), None, "A ist keine Trap-Taste");
|
||
t.setzen(Quelle::Key(0), Zustand::Aus, 0);
|
||
assert_eq!(t.taste_faengt(0x3B, 0), None, "OFF gibt die Taste frei");
|
||
}
|
||
|
||
#[test]
|
||
fn vordefinierte_taste_wird_ueber_den_scancode_erkannt() {
|
||
let mut t = Traps::neu();
|
||
t.definieren(Quelle::Key(1), Some(1));
|
||
t.setzen(Quelle::Key(1), Zustand::An, 0);
|
||
assert_eq!(t.taste_faengt(0x3B, 0), Some(1), "F1");
|
||
assert_eq!(t.taste_faengt(0x3C, 0), None, "F2 hat keinen Trap");
|
||
}
|
||
|
||
#[test]
|
||
fn benutzertaste_braucht_umschaltzustand_und_scancode() {
|
||
let mut t = Traps::neu();
|
||
t.definieren(Quelle::Key(15), Some(5));
|
||
t.setzen(Quelle::Key(15), Zustand::An, 0);
|
||
// KEY 15, CHR$(4) + CHR$(30) — Strg+A
|
||
t.benutzertaste(15, 4, 0x1E);
|
||
assert_eq!(t.taste_faengt(0x1E, 2), Some(15), "Strg+A");
|
||
assert_eq!(t.taste_faengt(0x1E, 0), None, "A ohne Strg nicht");
|
||
assert_eq!(t.taste_faengt(0x1F, 2), None, "Strg+S nicht");
|
||
}
|
||
|
||
#[test]
|
||
fn beide_umschaltbits_gelten_gleich() {
|
||
let mut t = Traps::neu();
|
||
t.definieren(Quelle::Key(16), Some(6));
|
||
t.setzen(Quelle::Key(16), Zustand::An, 0);
|
||
t.benutzertaste(16, 1, 0x1E); // rechte Umschalttaste
|
||
assert_eq!(t.taste_faengt(0x1E, 1), Some(16));
|
||
let mut t2 = Traps::neu();
|
||
t2.definieren(Quelle::Key(16), Some(6));
|
||
t2.setzen(Quelle::Key(16), Zustand::An, 0);
|
||
t2.benutzertaste(16, 2, 0x1E); // linke Umschalttaste
|
||
assert_eq!(t2.taste_faengt(0x1E, 1), Some(16));
|
||
}
|
||
|
||
#[test]
|
||
fn genauer_trap_geht_dem_sammeltrap_vor() {
|
||
let mut t = Traps::neu();
|
||
for (q, z) in [(Quelle::Key(0), 9), (Quelle::Key(1), 1)] {
|
||
t.definieren(q, Some(z));
|
||
t.setzen(q, Zustand::An, 0);
|
||
}
|
||
assert_eq!(
|
||
t.taste_faengt(0x3B, 0),
|
||
Some(1),
|
||
"F1 hat einen eigenen Trap"
|
||
);
|
||
assert_eq!(t.taste_faengt(0x3C, 0), Some(0), "F2 faellt an KEY(0)");
|
||
}
|
||
|
||
#[test]
|
||
fn gosub_null_schaltet_den_trap_ab() {
|
||
let mut t = mit_timer();
|
||
t.setzen(Quelle::Timer, Zustand::An, 0);
|
||
t.definieren(Quelle::Timer, None);
|
||
t.zeit_pruefen(5000);
|
||
assert_eq!(t.naechstes(), None);
|
||
assert!(!t.aktiv());
|
||
}
|
||
}
|