Skip to main content

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}