qubit_state_machine/state_machine_builder.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Builder for immutable state machine rules.
11
12use std::collections::{HashMap, HashSet};
13use std::fmt::Debug;
14use std::hash::Hash;
15
16use crate::{StateMachine, StateMachineBuildError, Transition};
17
18/// Builder used to define and validate finite state machine rules.
19///
20/// `S` is the state type and `E` is the event type. Configuration methods
21/// consume and return the builder so rule definitions can be chained. The built
22/// [`StateMachine`] is immutable.
23#[derive(Debug, Clone)]
24pub struct StateMachineBuilder<S, E>
25where
26 S: Copy + Eq + Hash + Debug,
27 E: Copy + Eq + Hash + Debug,
28{
29 pub(crate) states: HashSet<S>,
30 pub(crate) initial_states: HashSet<S>,
31 pub(crate) final_states: HashSet<S>,
32 pub(crate) transitions: Vec<Transition<S, E>>,
33}
34
35impl<S, E> StateMachineBuilder<S, E>
36where
37 S: Copy + Eq + Hash + Debug + 'static,
38 E: Copy + Eq + Hash + Debug + 'static,
39{
40 /// Creates an empty state machine builder.
41 ///
42 /// # Returns
43 /// A builder with no states or transitions.
44 pub fn new() -> Self {
45 Self {
46 states: HashSet::new(),
47 initial_states: HashSet::new(),
48 final_states: HashSet::new(),
49 transitions: Vec::new(),
50 }
51 }
52
53 /// Adds a state to the state machine definition.
54 ///
55 /// # Parameters
56 /// - `state`: State to register.
57 ///
58 /// # Returns
59 /// The updated builder.
60 pub fn add_state(mut self, state: S) -> Self {
61 self.states.insert(state);
62 self
63 }
64
65 /// Adds multiple states to the state machine definition.
66 ///
67 /// # Parameters
68 /// - `states`: States to register.
69 ///
70 /// # Returns
71 /// The updated builder.
72 pub fn add_states(mut self, states: &[S]) -> Self {
73 self.states.extend(states.iter().copied());
74 self
75 }
76
77 /// Marks a state as an initial state.
78 ///
79 /// The state must also be registered through [`add_state`](Self::add_state)
80 /// or [`add_states`](Self::add_states) before [`build`](Self::build) is
81 /// called.
82 ///
83 /// # Parameters
84 /// - `state`: Initial state to add.
85 ///
86 /// # Returns
87 /// The updated builder.
88 pub fn set_initial_state(mut self, state: S) -> Self {
89 self.initial_states.insert(state);
90 self
91 }
92
93 /// Marks multiple states as initial states.
94 ///
95 /// # Parameters
96 /// - `states`: Initial states to add.
97 ///
98 /// # Returns
99 /// The updated builder.
100 pub fn set_initial_states(mut self, states: &[S]) -> Self {
101 self.initial_states.extend(states.iter().copied());
102 self
103 }
104
105 /// Marks a state as a final state.
106 ///
107 /// The state must also be registered through [`add_state`](Self::add_state)
108 /// or [`add_states`](Self::add_states) before [`build`](Self::build) is
109 /// called.
110 ///
111 /// # Parameters
112 /// - `state`: Final state to add.
113 ///
114 /// # Returns
115 /// The updated builder.
116 pub fn set_final_state(mut self, state: S) -> Self {
117 self.final_states.insert(state);
118 self
119 }
120
121 /// Marks multiple states as final states.
122 ///
123 /// # Parameters
124 /// - `states`: Final states to add.
125 ///
126 /// # Returns
127 /// The updated builder.
128 pub fn set_final_states(mut self, states: &[S]) -> Self {
129 self.final_states.extend(states.iter().copied());
130 self
131 }
132
133 /// Adds a transition by source state, event, and target state.
134 ///
135 /// Source and target states must be registered before
136 /// [`build`](Self::build) is called. Adding the same transition more than
137 /// once is allowed. Adding the same `(source, event)` with a different
138 /// target is rejected during build.
139 ///
140 /// # Parameters
141 /// - `source`: State before the event is applied.
142 /// - `event`: Event that triggers the transition.
143 /// - `target`: State after the transition succeeds.
144 ///
145 /// # Returns
146 /// The updated builder.
147 pub fn add_transition(self, source: S, event: E, target: S) -> Self {
148 self.add_transition_value(Transition::new(source, event, target))
149 }
150
151 /// Adds a transition value.
152 ///
153 /// # Parameters
154 /// - `transition`: Transition to add to the state machine definition.
155 ///
156 /// # Returns
157 /// The updated builder.
158 pub fn add_transition_value(mut self, transition: Transition<S, E>) -> Self {
159 self.transitions.push(transition);
160 self
161 }
162
163 /// Builds an immutable state machine after validating the rule set.
164 ///
165 /// # Returns
166 /// A validated immutable state machine.
167 ///
168 /// # Errors
169 /// Returns a [`StateMachineBuildError`] when an initial state, final state,
170 /// transition source, or transition target is not registered, or when two
171 /// transitions map the same `(source, event)` pair to different targets.
172 pub fn build(self) -> Result<StateMachine<S, E>, StateMachineBuildError<S, E>> {
173 self.validate_registered_states()?;
174
175 let mut transition_set = HashSet::new();
176 let mut transition_map = HashMap::new();
177 for transition in &self.transitions {
178 let transition = *transition;
179 self.validate_transition(transition)?;
180 Self::insert_transition(transition, &mut transition_set, &mut transition_map)?;
181 }
182
183 Ok(StateMachine::new(self, transition_set, transition_map))
184 }
185
186 /// Validates that initial and final states are registered.
187 ///
188 /// # Returns
189 /// `Ok(())` when all configured state sets refer to registered states.
190 ///
191 /// # Errors
192 /// Returns the first unregistered initial or final state encountered.
193 fn validate_registered_states(&self) -> Result<(), StateMachineBuildError<S, E>> {
194 for state in &self.initial_states {
195 if !self.states.contains(state) {
196 return Err(StateMachineBuildError::InitialStateNotRegistered { state: *state });
197 }
198 }
199 for state in &self.final_states {
200 if !self.states.contains(state) {
201 return Err(StateMachineBuildError::FinalStateNotRegistered { state: *state });
202 }
203 }
204 Ok(())
205 }
206
207 /// Validates that a transition only references registered states.
208 ///
209 /// # Parameters
210 /// - `transition`: Transition to validate.
211 ///
212 /// # Returns
213 /// `Ok(())` when the transition source and target are registered.
214 ///
215 /// # Errors
216 /// Returns the missing source or target as a build error.
217 fn validate_transition(
218 &self,
219 transition: Transition<S, E>,
220 ) -> Result<(), StateMachineBuildError<S, E>> {
221 if !self.states.contains(&transition.source()) {
222 return Err(StateMachineBuildError::TransitionSourceNotRegistered {
223 source: transition.source(),
224 event: transition.event(),
225 target: transition.target(),
226 });
227 }
228 if !self.states.contains(&transition.target()) {
229 return Err(StateMachineBuildError::TransitionTargetNotRegistered {
230 source: transition.source(),
231 event: transition.event(),
232 target: transition.target(),
233 });
234 }
235 Ok(())
236 }
237
238 /// Inserts a transition into the set and lookup table.
239 ///
240 /// # Parameters
241 /// - `transition`: Transition to insert.
242 /// - `transition_set`: Set used for public transition inspection.
243 /// - `transition_map`: Lookup table used for event triggering.
244 ///
245 /// # Returns
246 /// `Ok(())` when the transition is inserted or is an exact duplicate.
247 ///
248 /// # Errors
249 /// Returns a duplicate-transition error if the same source and event already
250 /// point to a different target.
251 fn insert_transition(
252 transition: Transition<S, E>,
253 transition_set: &mut HashSet<Transition<S, E>>,
254 transition_map: &mut HashMap<(S, E), S>,
255 ) -> Result<(), StateMachineBuildError<S, E>> {
256 let source = transition.source();
257 let event = transition.event();
258 let target = transition.target();
259 if let Some(existing_target) = transition_map.get(&(source, event))
260 && *existing_target != target
261 {
262 return Err(StateMachineBuildError::DuplicateTransition {
263 source,
264 event,
265 existing_target: *existing_target,
266 new_target: target,
267 });
268 }
269 transition_set.insert(transition);
270 transition_map.insert((source, event), target);
271 Ok(())
272 }
273}
274
275impl<S, E> Default for StateMachineBuilder<S, E>
276where
277 S: Copy + Eq + Hash + Debug + 'static,
278 E: Copy + Eq + Hash + Debug + 'static,
279{
280 /// Creates an empty state machine builder.
281 fn default() -> Self {
282 Self::new()
283 }
284}