diff --git a/examples/statem_fused.rs b/examples/statem_fused.rs index 1a95f6f..4897093 100644 --- a/examples/statem_fused.rs +++ b/examples/statem_fused.rs @@ -38,7 +38,7 @@ #![deny(dead_code, unreachable_patterns)] use smarm::run; -use smarm::statem::{spawn, Cx, Machine, Reply, Resolution, StatemRef}; +use smarm::gen_statem::{spawn, Cx, Machine, Reply, Resolution, StatemRef}; // === user types ============================================================ diff --git a/examples/statem_macro.rs b/examples/statem_macro.rs index 2a6cca0..6b961e4 100644 --- a/examples/statem_macro.rs +++ b/examples/statem_macro.rs @@ -19,7 +19,7 @@ use smarm::gen_statem; use smarm::run; -use smarm::statem::Reply; +use smarm::gen_statem::Reply; // === user types (identical to statem_fused.rs) ============================= diff --git a/examples/statem_switch.rs b/examples/statem_switch.rs deleted file mode 100644 index e034d4b..0000000 --- a/examples/statem_switch.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! 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"); - }); -} diff --git a/src/statem.rs b/src/gen_statem.rs similarity index 96% rename from src/statem.rs rename to src/gen_statem.rs index 033845a..18e4f24 100644 --- a/src/statem.rs +++ b/src/gen_statem.rs @@ -55,7 +55,7 @@ use std::marker::PhantomData; // Machine // --------------------------------------------------------------------------- -/// A finite state machine driven by the [`statem`](crate::statem) loop. +/// A finite state machine driven by the [`statem`](crate::gen_statem) loop. /// /// The implementor owns its current state tag and its persistent data. It is /// the **sole writer** of the state cell (the loop never touches it): both @@ -426,13 +426,13 @@ macro_rules! gen_statem { } impl $sm { - fn start(init: $State, data: $Data) -> $crate::statem::StatemRef<$sm> { - $crate::statem::spawn($sm { state: init, data }) + fn start(init: $State, data: $Data) -> $crate::gen_statem::StatemRef<$sm> { + $crate::gen_statem::spawn($sm { state: init, data }) } #[allow(unused_variables)] #[deny(unreachable_patterns)] - fn enter(&mut self, state: $State, $cx: &mut $crate::statem::Cx<$Ev>) { + fn enter(&mut self, state: $State, $cx: &mut $crate::gen_statem::Cx<$Ev>) { let $data = &mut self.data; match state { $( $est => { $ebody } ),+ @@ -440,10 +440,10 @@ macro_rules! gen_statem { } } - impl $crate::statem::Machine for $sm { + impl $crate::gen_statem::Machine for $sm { type Ev = $Ev; - fn on_start(&mut self, $cx: &mut $crate::statem::Cx<$Ev>) { + fn on_start(&mut self, $cx: &mut $crate::gen_statem::Cx<$Ev>) { let s = self.state; self.enter(s, $cx); } @@ -451,24 +451,24 @@ macro_rules! gen_statem { #[allow(unused_variables)] #[deny(unreachable_patterns)] // conflicting rows must fail even though // this match is external-macro-expanded - fn handle(&mut self, ev: $Ev, $cx: &mut $crate::statem::Cx<$Ev>) { + fn handle(&mut self, ev: $Ev, $cx: &mut $crate::gen_statem::Cx<$Ev>) { // Caller-named bindings (shared call-site hygiene, so row bodies // can see them): `$cur` = current state tag, `$data` = &mut Data. let $cur = self.state; let $data = &mut self.data; - let next: $crate::statem::Resolution<$State> = + let next: $crate::gen_statem::Resolution<$State> = $crate::gen_statem!(@arms ($Ev) ($cur, ev) [ ] $( on $st => { $($rows)* } )+); match next { - $crate::statem::Resolution::To(s) if s == $cur => {} - $crate::statem::Resolution::To(s) => { + $crate::gen_statem::Resolution::To(s) if s == $cur => {} + $crate::gen_statem::Resolution::To(s) => { self.state = s; // <- sole writer of the state cell self.enter(s, $cx); } - $crate::statem::Resolution::Postpone => { + $crate::gen_statem::Resolution::Postpone => { unreachable!("postpone is unreachable until chunk 3") } - $crate::statem::Resolution::Unhandled => $cx.on_unhandled(), + $crate::gen_statem::Resolution::Unhandled => $cx.on_unhandled(), } } } @@ -493,7 +493,7 @@ macro_rules! gen_statem { { cast $ev:pat $(if $g:expr)? => unhandled , $($rows:tt)* } { $($more:tt)* } ) => { $crate::gen_statem!(@rows ($Ev) ($ss, $se) - [ $($arms)* ($st, $Ev::Cast($ev)) $(if $g)? => $crate::statem::Resolution::Unhandled, ] + [ $($arms)* ($st, $Ev::Cast($ev)) $(if $g)? => $crate::gen_statem::Resolution::Unhandled, ] ($st) { $($rows)* } { $($more)* }) }; // cast, transition / stay / branch @@ -501,7 +501,7 @@ macro_rules! gen_statem { { cast $ev:pat $(if $g:expr)? => $tail:expr , $($rows:tt)* } { $($more:tt)* } ) => { $crate::gen_statem!(@rows ($Ev) ($ss, $se) - [ $($arms)* ($st, $Ev::Cast($ev)) $(if $g)? => $crate::statem::Resolution::To($tail.into()), ] + [ $($arms)* ($st, $Ev::Cast($ev)) $(if $g)? => $crate::gen_statem::Resolution::To($tail.into()), ] ($st) { $($rows)* } { $($more)* }) }; // call, explicit refusal @@ -509,7 +509,7 @@ macro_rules! gen_statem { { call $ev:pat $(if $g:expr)? => unhandled , $($rows:tt)* } { $($more:tt)* } ) => { $crate::gen_statem!(@rows ($Ev) ($ss, $se) - [ $($arms)* ($st, $Ev::Call($ev)) $(if $g)? => $crate::statem::Resolution::Unhandled, ] + [ $($arms)* ($st, $Ev::Call($ev)) $(if $g)? => $crate::gen_statem::Resolution::Unhandled, ] ($st) { $($rows)* } { $($more)* }) }; // call, transition / stay / branch @@ -517,7 +517,7 @@ macro_rules! gen_statem { { call $ev:pat $(if $g:expr)? => $tail:expr , $($rows:tt)* } { $($more:tt)* } ) => { $crate::gen_statem!(@rows ($Ev) ($ss, $se) - [ $($arms)* ($st, $Ev::Call($ev)) $(if $g)? => $crate::statem::Resolution::To($tail.into()), ] + [ $($arms)* ($st, $Ev::Call($ev)) $(if $g)? => $crate::gen_statem::Resolution::To($tail.into()), ] ($st) { $($rows)* } { $($more)* }) }; // this block is drained: hand the remaining on-blocks back to @arms @@ -530,7 +530,7 @@ macro_rules! gen_statem { #[cfg(test)] mod gen_statem_tests { - use crate::statem::Reply; + use crate::gen_statem::Reply; #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum Switch { diff --git a/src/lib.rs b/src/lib.rs index cc2e1fb..6c84019 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ pub mod registry; pub mod pg; pub mod link; pub mod gen_server; -pub mod statem; +pub mod gen_statem; pub mod introspect; #[cfg(feature = "observer")] pub mod observer; @@ -58,7 +58,7 @@ pub use gen_server::{ call, cast, shutdown, whereis_server, CallError, CallTimeoutError, CastError, GenServer, NamedServerBuilder, ServerBuilder, ServerCtx, ServerName, ServerRef, TimerHandle, Watcher, }; -pub use statem::{ +pub use gen_statem::{ CallError as StatemCallError, Cx, Machine, Reply, Resolution, SendError as StatemSendError, StatemRef, }; diff --git a/tests/statem.rs b/tests/statem.rs index ef986ce..6c7631b 100644 --- a/tests/statem.rs +++ b/tests/statem.rs @@ -2,11 +2,11 @@ //! hand-written machine, `enter` firing on start and on every real transition //! (but not on a stay), and the machine-down path when a handler panics. //! -//! These drive the `smarm::statem` primitives directly, the same way a +//! These drive the `smarm::gen_statem` primitives directly, the same way a //! `statem!`-generated machine eventually will. use smarm::run; -use smarm::statem::{self, CallError, Cx, Machine, Reply, Resolution, StatemRef}; +use smarm::gen_statem::{self, CallError, Cx, Machine, Reply, Resolution, StatemRef}; use std::sync::{Arc, Mutex}; // --------------------------------------------------------------------------- @@ -46,7 +46,7 @@ struct Sm { impl Sm { fn start(init: Switch) -> StatemRef { - statem::spawn(Sm { state: init, data: Counts { flips: 0, enters: 0 } }) + gen_statem::spawn(Sm { state: init, data: Counts { flips: 0, enters: 0 } }) } fn enter(&mut self, _s: Switch, _cx: &mut Cx) {