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