//! A hand-written `gen_statem`, written directly against the chunk-1 primitives //! in `smarm::statem` — no macro. This is the RFC 017 `Switch` example, and its //! purpose is to be the **evaluation artifact**: it shows exactly the shape the //! deferred `statem!` macro would have to generate, so we can judge what the //! macro actually buys before committing to building one. //! //! Run with: `cargo run --example statem_switch` //! //! Sections are tagged // USER (you'd hand-write this with or without a macro) //! and // MACRO (the boilerplate a `statem!` would emit for you). use smarm::run; use smarm::statem::{self, Cx, Machine, Reply, Resolution, StatemRef}; // ---- USER: the four hand-written types ------------------------------------ // In the macro world these are unchanged — the macro never generates them. #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum Switch { Off, On, } struct Counts { flips: u32, enters: u32, } enum SwitchCast { Flip, } enum SwitchCall { GetCount(Reply), GetEnters(Reply), } // ---- MACRO: the unified event --------------------------------------------- // The user's two message enums folded together. Chunk 1 has no internal // variants yet; chunks 2–3 add `StateTimeout` / `Timeout(name)` here. enum Ev { Cast(SwitchCast), Call(SwitchCall), } // ---- MACRO: the machine struct + state cell ------------------------------- // `state` is the private cell; the `handle` body below is its sole writer. struct SwitchSm { state: Switch, data: Counts, } impl SwitchSm { // MACRO: `Switch::start` in the RFC; spawns the actor, hands back a ref. fn start(init: Switch, data: Counts) -> StatemRef { statem::spawn(SwitchSm { state: init, data }) } // MACRO: the `enter` dispatch, assembled from the per-state `enter` arms. // USER wrote the arm bodies (`data.enters += 1`); the macro wrote the match // and the `()` return. `enter` runs side effects only — it cannot transition. fn enter(&mut self, s: Switch, _cx: &mut Cx) { match s { Switch::Off => self.data.enters += 1, Switch::On => self.data.enters += 1, } } } impl Machine for SwitchSm { type Ev = Ev; // MACRO: run the initial state's enter. fn on_start(&mut self, cx: &mut Cx) { let s = self.state; self.enter(s, cx); } fn handle(&mut self, ev: Ev, cx: &mut Cx) { let prev = self.state; // MACRO: the `(state, event)` dispatch table. USER wrote each arm tail // (the body + the ending state tag); the macro qualified bare tags // (`On` -> `Switch::On`), wrapped them in `.into()`, assembled the match, // and — in the real macro — would have edge-checked each tail tag // against the `transitions { Off => On, On => Off }` table at expand // time. Here that check is on the honour system. let next: Resolution = match (self.state, ev) { (Switch::Off, Ev::Cast(SwitchCast::Flip)) => { self.data.flips += 1; Switch::On.into() } (Switch::On, Ev::Cast(SwitchCast::Flip)) => Switch::Off.into(), (Switch::Off, Ev::Call(SwitchCall::GetCount(r))) => { r.reply(self.data.flips); Switch::Off.into() // stay = return the current tag } (Switch::On, Ev::Call(SwitchCall::GetCount(r))) => { r.reply(self.data.flips); Switch::On.into() } (Switch::Off, Ev::Call(SwitchCall::GetEnters(r))) => { r.reply(self.data.enters); Switch::Off.into() } (Switch::On, Ev::Call(SwitchCall::GetEnters(r))) => { r.reply(self.data.enters); Switch::On.into() } // The macro would emit `_ => Resolution::Unhandled` here for a // machine whose table is non-exhaustive; this one covers every // (state, event) pair, so an added arm would be unreachable. }; // MACRO: the resolution dispatch — identical in every generated machine. match next { Resolution::To(s) if s == prev => {} // stay: no enter, no reset Resolution::To(s) => { self.state = s; // <- sole writer of the state cell // chunk 2: self.timers.clear_state_timeout(); self.enter(s, cx); // chunk 3: cx.replay(&mut self.postponed); } Resolution::Postpone => {} // chunk 3 Resolution::Unhandled => cx.on_unhandled(), } } } fn main() { run(|| { // USER: the client side. With a macro these would be `sw.cast(..)` / // `sw.call(SwitchCall::GetCount)`; without it, the `Ev::Cast` / `Ev::Call` // wrap is explicit — note how mechanical it is. let sw = SwitchSm::start(Switch::Off, Counts { flips: 0, enters: 0 }); sw.send(Ev::Cast(SwitchCast::Flip)).unwrap(); // Off -> On sw.send(Ev::Cast(SwitchCast::Flip)).unwrap(); // On -> Off let flips = sw.call(|r| Ev::Call(SwitchCall::GetCount(r))).unwrap(); let enters = sw.call(|r| Ev::Call(SwitchCall::GetEnters(r))).unwrap(); // RFC's stated end state after two flips: Counts { flips: 1, enters: 3 } // (initial Off entry + On entry + Off entry). println!("after two flips: flips={flips}, enters={enters}"); assert_eq!(flips, 1, "turned On once across the two flips"); assert_eq!(enters, 3, "initial Off + On + Off entries"); println!("ok"); }); }