This commit is contained in:
2026-07-11 00:14:41 +02:00
parent 87dd8e1190
commit c9e463bb9a
15 changed files with 183 additions and 51 deletions
+39 -7
View File
@@ -27,7 +27,12 @@
//! Dispatch-Reihenfolge: erst Signal-Lookup; steht der Name in der Table,
//! laufen alle gemappten Actions. Sonst wird der String direkt als
//! Builtin-Aufruf probiert — so wirken Ink-Tags wie `# set has_key true`
//! ohne Umweg über signals.toml. Unbekannte Verben werden still ignoriert.
//! ohne Umweg über signals.toml.
//!
//! Unbekannte Verben: aus einer signals.toml-*Action* sind sie immer ein
//! Tippfehler und werden nach stderr gemeldet. Im Ink-Tag-Fall-Through bleiben
//! sie hingegen still — Tags dürfen bewusst reine Metadaten sein, die (noch)
//! kein Verb konsumiert (z.B. ein späteres `# speaker …`).
use std::collections::HashMap;
use std::fs::read_to_string;
@@ -51,17 +56,27 @@ pub fn signal_key(name: &str) -> &str {
}
pub fn dispatch(signal: &str, ctx: &mut ActionCtx) {
// Signal-Name in der Table? Dann alle gemappten Actions ausführen.
// Signal-Name in der Table? Dann alle gemappten Actions ausführen. Ein
// unbekanntes Verb in einer Action ist hier immer ein Tippfehler → melden.
if let Some(actions) = ctx.signals.get(signal) {
let actions = actions.clone(); // entkoppelt vom ctx.signals-Borrow
for a in &actions { execute(a, ctx); }
for a in &actions {
if !execute(a, ctx) {
eprintln!("[signal] {signal:?}: unbekanntes Verb in Action {a:?} \
— Tippfehler in signals.toml?");
}
}
return;
}
// Sonst direkt als Builtin probieren (Ink-Tag-Pfad).
execute(signal, ctx);
// Sonst direkt als Builtin probieren (Ink-Tag-Pfad). Unbekanntes bleibt
// hier still (Tags dürfen bloße Metadaten sein) — Rückgabe verworfen.
let _ = execute(signal, ctx);
}
fn execute(cmd: &str, ctx: &mut ActionCtx) {
/// Führt einen einzelnen Befehl (`verb args`) aus. Rückgabe: `true`, wenn das
/// Verb bekannt war und lief; `false` bei unbekanntem Verb (der Aufrufer
/// entscheidet, ob das eine Meldung wert ist — siehe `dispatch`).
fn execute(cmd: &str, ctx: &mut ActionCtx) -> bool {
// $self → ctx.instance_name. Substituieren bevor wir Verb/Args splitten,
// damit Tokens wie `hide_object $self` einheitlich funktionieren. Ohne
// instance_name (z.B. `init`-Signal) bleibt `$self` stehen — die Action
@@ -87,8 +102,9 @@ fn execute(cmd: &str, ctx: &mut ActionCtx) {
"mode" => if let Some(t) = ModeTarget::parse(args) {
ctx.actions.push(Action::SetMode(t));
},
_ => { /* Unbekannter Verb/Tag → still ignorieren */ }
_ => return false, // Unbekanntes Verb — der Aufrufer meldet ggf.
}
true
}
/// Parst das signals.toml-Subset: `[signal]`-Header gefolgt von einem
@@ -195,6 +211,22 @@ mod tests {
assert!(game.actions.is_empty());
}
#[test]
fn typo_verb_in_table_is_tolerated_and_reported() {
// Ein Tippfehler-Verb in einer Table-Action wird gemeldet (stderr) und
// bleibt ohne Wirkung, während die gültige Nachbar-Action weiter läuft.
let mut signals = Signals::new();
signals.insert("thing".into(), vec![
"st has_key true".into(), // Tippfehler für `set` → No-Op + Meldung
"set ok true".into(), // gültig → wirkt
]);
let mut game = Game::new(signals);
let mut ctx = game.action_ctx(None);
dispatch("thing", &mut ctx);
assert!(!game.kv.contains_key("has_key"), "Tippfehler darf nichts setzen");
assert!(game.kv["ok"].coerce_to_bool().unwrap(), "gültige Action wirkt weiter");
}
#[test]
fn unknown_signal_falls_through_to_builtin() {
let mut game = Game::new(Signals::new());