This commit is contained in:
Binary file not shown.
@@ -1,3 +1,5 @@
|
|||||||
|
use std::{collections::HashMap};
|
||||||
|
|
||||||
use crate::configotron::fsm::{masks::{mask_any_number, mask_number, mask_number_range}, Action, FSMBuilder, FiniteStateMachine, StateRef, StateType};
|
use crate::configotron::fsm::{masks::{mask_any_number, mask_number, mask_number_range}, Action, FSMBuilder, FiniteStateMachine, StateRef, StateType};
|
||||||
|
|
||||||
|
|
||||||
@@ -74,14 +76,16 @@ pub fn build_fsm_upper_bound(max: u32) -> Result<FiniteStateMachine, String> {
|
|||||||
scaffold.builder.add_link(&scaffold.last_side, &scaffold.error_state, Action::None, mask_any_number())?;
|
scaffold.builder.add_link(&scaffold.last_side, &scaffold.error_state, Action::None, mask_any_number())?;
|
||||||
scaffold.builder.add_link(&scaffold.goal_state, &scaffold.error_state, Action::None, mask_any_number())?;
|
scaffold.builder.add_link(&scaffold.goal_state, &scaffold.error_state, Action::None, mask_any_number())?;
|
||||||
|
|
||||||
|
scaffold.builder.set_state_type(&scaffold.start_state, StateType::Error);
|
||||||
|
|
||||||
let fsm = scaffold.builder.finish(&scaffold.error_state, Action::None, &scaffold.start_state)?;
|
let fsm = scaffold.builder.finish(&scaffold.error_state, Action::None, &scaffold.start_state)?;
|
||||||
Ok(fsm)
|
Ok(fsm)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_fsm_lower_bound(max: u32) -> Result<FiniteStateMachine, String> {
|
pub fn build_fsm_lower_bound(min: u32) -> Result<FiniteStateMachine, String> {
|
||||||
let mut scaffold = build_fsm_bound_selecton(max, StateType::Error)?;
|
let mut scaffold = build_fsm_bound_selecton(min, StateType::Error)?;
|
||||||
|
|
||||||
let last_digit = (max % 10) as u8;
|
let last_digit = (min % 10) as u8;
|
||||||
scaffold.builder.add_link(&scaffold.last_main, &scaffold.goal_state, Action::None, mask_number_range(last_digit, 9))?;
|
scaffold.builder.add_link(&scaffold.last_main, &scaffold.goal_state, Action::None, mask_number_range(last_digit, 9))?;
|
||||||
scaffold.builder.add_link(&scaffold.last_side, &scaffold.goal_state, Action::None, mask_any_number())?;
|
scaffold.builder.add_link(&scaffold.last_side, &scaffold.goal_state, Action::None, mask_any_number())?;
|
||||||
scaffold.builder.add_link(&scaffold.goal_state, &scaffold.goal_state, Action::None, mask_any_number())?;
|
scaffold.builder.add_link(&scaffold.goal_state, &scaffold.goal_state, Action::None, mask_any_number())?;
|
||||||
@@ -90,21 +94,113 @@ pub fn build_fsm_lower_bound(max: u32) -> Result<FiniteStateMachine, String> {
|
|||||||
Ok(fsm)
|
Ok(fsm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fsm_cartesian_product<F, G>(fsm1: &FiniteStateMachine, fsm2: &FiniteStateMachine, state_type_operator: F, action_operator: G) -> Result<FiniteStateMachine, String>
|
||||||
|
where F: Fn(StateType, StateType) -> StateType, G: Fn(Action, Action) -> Action {
|
||||||
|
let mut builder = FSMBuilder::new();
|
||||||
|
|
||||||
|
let mut states_matrix: HashMap<(StateRef, StateRef), StateRef> = HashMap::new();
|
||||||
|
|
||||||
|
// Create all States (Q1 x Q2)
|
||||||
|
for (
|
||||||
|
(state_ref1, state1),
|
||||||
|
(state_ref2, state2)
|
||||||
|
) in itertools::iproduct!(&fsm1.states, &fsm2.states) {
|
||||||
|
let name = format!("{}_{}", state1.name, state2.name);
|
||||||
|
states_matrix.insert((*state_ref1, *state_ref2), builder.add_named_state(name, state_type_operator(state1.state_type, state2.state_type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding Transitions
|
||||||
|
for (
|
||||||
|
(state_ref1, state1),
|
||||||
|
(state_ref2, state2)
|
||||||
|
) in itertools::iproduct!(&fsm1.states, &fsm2.states) {
|
||||||
|
let current_ref = match states_matrix.get(&(*state_ref1, *state_ref2)) {
|
||||||
|
Some(r) => r.clone(),
|
||||||
|
None => return Err(format!("State pair ({:?}, {:?}) not found in states_matrix [internal]", state_ref1, state_ref2)),
|
||||||
|
};
|
||||||
|
|
||||||
|
for ch in 0..128 {
|
||||||
|
let (next_state1, action1) = state1.transition[ch];
|
||||||
|
let (next_state2, action2) = state2.transition[ch];
|
||||||
|
let new_ref = match states_matrix.get(&(next_state1, next_state2)) {
|
||||||
|
Some(r) => r.clone(),
|
||||||
|
None => return Err(format!("State pair ({:?}, {:?}) not found in states_matrix [internal]", next_state1, next_state2)),
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.add_link(¤t_ref, &new_ref, action_operator(action1, action2), 1 << ch)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_state = if let Some(s) = states_matrix.get(&(fsm1.start, fsm2.start)) {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
return Err("Start state pair not found in states_matrix [internal]".to_string());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(builder.finish_without_default(start_state)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union_fsm(fsm1: &FiniteStateMachine, fsm2: &FiniteStateMachine) -> Result<FiniteStateMachine, String> {
|
||||||
|
fsm_cartesian_product(fsm1, fsm2,
|
||||||
|
|t1, t2| {
|
||||||
|
use StateType::{Terminal, Error, None};
|
||||||
|
match (t1, t2) {
|
||||||
|
(Error, _) => Error,
|
||||||
|
(_, Error) => Error,
|
||||||
|
|
||||||
|
(Terminal, Terminal) => Terminal,
|
||||||
|
(Terminal, None) => None,
|
||||||
|
|
||||||
|
(None, Terminal) => None,
|
||||||
|
(None, None) => None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|a1, a2| {
|
||||||
|
use Action::{Add, Pop, Submit, None};
|
||||||
|
match (a1, a2) {
|
||||||
|
(None, _) => None,
|
||||||
|
(_, None) => None,
|
||||||
|
|
||||||
|
(Add, Add) => Add,
|
||||||
|
(Pop, Pop) => Pop,
|
||||||
|
(Submit, Submit) => Submit,
|
||||||
|
|
||||||
|
(_, _) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_fsm_in_bound(min: u32, max: u32) -> Result<FiniteStateMachine, String> {
|
||||||
|
if min > max {
|
||||||
|
return Err(String::from("Lower bound must be <= Upper bound"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lower_fsm = build_fsm_lower_bound(min)?;
|
||||||
|
let upper_fsm = build_fsm_upper_bound(max)?;
|
||||||
|
|
||||||
|
Ok(union_fsm(&lower_fsm, &upper_fsm)?)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::configotron::fsm::{component_builder::build_fsm_upper_bound, display::{debug_dump_fsm_graph}, run_fsm, StateType};
|
use crate::configotron::fsm::{component_builder::{build_fsm_in_bound, build_fsm_upper_bound, union_fsm}, display::debug_dump_fsm_graph, masks::mask_char, run_fsm, StateType};
|
||||||
use crate::configotron::fsm::component_builder::build_fsm_lower_bound;
|
use crate::configotron::fsm::component_builder::build_fsm_lower_bound;
|
||||||
|
use crate::configotron::fsm::{FSMBuilder, Action};
|
||||||
|
|
||||||
|
static TEST_NUMBERS: &[u32] = &[1256, 1, 9999, 1000, 0, 10, 42, 123, 500, 2024, 751394];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn upper_bound() {
|
fn upper_bound() {
|
||||||
|
for &max in TEST_NUMBERS {
|
||||||
let test_vec: Vec<u32> = vec![1256, 1, 9999, 1000, 0, 10, 42, 123, 500, 2024, 751394];
|
|
||||||
|
|
||||||
for max in test_vec {
|
|
||||||
let fsm = build_fsm_upper_bound(max);
|
let fsm = build_fsm_upper_bound(max);
|
||||||
assert!(fsm.is_ok(), "{:?}", if let Err(e) = fsm { e } else { String::new() });
|
assert!(fsm.is_ok(), "{:?}", if let Err(e) = fsm { e } else { String::new() });
|
||||||
|
|
||||||
if let Ok(machine) = fsm {
|
if let Ok(machine) = fsm {
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&machine, &"".to_string()), "Empty String");
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&machine, &"this-is-not-a-number".to_string()), "Text");
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&machine, &"13373 is a nice number".to_string()), "Number + Text");
|
||||||
|
|
||||||
debug_dump_fsm_graph(&machine, format!(".debug/upper_bound{}.svg", max));
|
debug_dump_fsm_graph(&machine, format!(".debug/upper_bound{}.svg", max));
|
||||||
|
|
||||||
for i in 0..=max*10 {
|
for i in 0..=max*10 {
|
||||||
@@ -125,14 +221,15 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lower_bound() {
|
fn lower_bound() {
|
||||||
let test_vec: Vec<u32> = vec![1256, 1, 9999, 1000, 0, 10, 42, 123, 500, 2024, 751394];
|
for &max in TEST_NUMBERS {
|
||||||
|
|
||||||
for max in test_vec {
|
|
||||||
let fsm = build_fsm_lower_bound(max);
|
let fsm = build_fsm_lower_bound(max);
|
||||||
assert!(fsm.is_ok(), "{:?}", if let Err(e) = fsm { e } else { String::new() });
|
assert!(fsm.is_ok(), "{:?}", if let Err(e) = fsm { e } else { String::new() });
|
||||||
|
|
||||||
if let Ok(machine) = fsm {
|
if let Ok(machine) = fsm {
|
||||||
debug_dump_fsm_graph(&machine, format!(".debug/lower_bound{}.svg", max));
|
debug_dump_fsm_graph(&machine, format!(".debug/lower_bound{}.svg", max));
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&machine, &"".to_string()), "Empty String");
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&machine, &"this-is-not-a-number".to_string()), "Text");
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&machine, &"13373 is a nice number".to_string()), "Number + Text");
|
||||||
|
|
||||||
for i in 0..=max*10 {
|
for i in 0..=max*10 {
|
||||||
let input_string = i.to_string();
|
let input_string = i.to_string();
|
||||||
@@ -147,4 +244,183 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn union_fsm_test() {
|
||||||
|
// FSM A:
|
||||||
|
// States: z0, z1, z2, z3
|
||||||
|
// Start: z0
|
||||||
|
//
|
||||||
|
// Types:
|
||||||
|
// - Terminal: z2
|
||||||
|
// - Error: z0, z1, z3
|
||||||
|
//
|
||||||
|
// Transitions:
|
||||||
|
// z0 -a-> z0
|
||||||
|
// z0 -b-> z1
|
||||||
|
// z1 -a-> z2
|
||||||
|
// default: z3
|
||||||
|
//
|
||||||
|
// Language: /a+ba/
|
||||||
|
|
||||||
|
let mut builder_a = FSMBuilder::new();
|
||||||
|
let z0 = builder_a.add_named_state("z0".to_string(), StateType::Error);
|
||||||
|
let z1 = builder_a.add_named_state("z1".to_string(), StateType::Error);
|
||||||
|
let z2 = builder_a.add_named_state("z2".to_string(), StateType::Terminal);
|
||||||
|
let z3 = builder_a.add_named_state("z3".to_string(), StateType::Error);
|
||||||
|
|
||||||
|
builder_a.add_link(&z0, &z0, Action::None, mask_char('a')).unwrap();
|
||||||
|
builder_a.add_link(&z0, &z1, Action::None, mask_char('b')).unwrap();
|
||||||
|
builder_a.add_link(&z1, &z2, Action::None, mask_char('a')).unwrap();
|
||||||
|
let fsm_a_res = builder_a.finish(&z3, Action::None, &z0);
|
||||||
|
let fsm_a_res_clone = fsm_a_res.clone();
|
||||||
|
|
||||||
|
let accepted_a = ["ba", "aba", "aaba", "aaaba", "aaaaba", "aaaaaba"];
|
||||||
|
let rejected_a = ["a", "b", "ab", "aab", "abb", "baba", "", "bb", "aaa", "bab"];
|
||||||
|
|
||||||
|
// Test FSM A with different inputs
|
||||||
|
if let Ok(fsm_a) = fsm_a_res {
|
||||||
|
debug_dump_fsm_graph(&fsm_a, ".debug/union_a.svg".to_string());
|
||||||
|
|
||||||
|
// Accepts "ba", "aba", "aaba", "aaaba", etc.
|
||||||
|
for input in &accepted_a {
|
||||||
|
let res = run_fsm(&fsm_a, &input.to_string());
|
||||||
|
assert_eq!(res, StateType::Terminal, "FSM A should accept '{}'", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rejects "a", "b", "ab", "aab", "abb", "baba", "", "bb", "aaa", "bab"
|
||||||
|
for input in &rejected_a {
|
||||||
|
let res = run_fsm(&fsm_a, &input.to_string());
|
||||||
|
assert_eq!(res, StateType::Error, "FSM A should reject '{}'", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FSM B:
|
||||||
|
// States: z0, z1, z2, z3
|
||||||
|
// Start: z0
|
||||||
|
//
|
||||||
|
// Types:
|
||||||
|
// - Terminal: z2
|
||||||
|
// - Error: z0, z1, z2, z3
|
||||||
|
//
|
||||||
|
// Transitions:
|
||||||
|
// z0 -a-> z1
|
||||||
|
// z1 -b-> z2
|
||||||
|
// z2 -a-> z2
|
||||||
|
// default: z3
|
||||||
|
//
|
||||||
|
// Language: /aba*/
|
||||||
|
|
||||||
|
let mut builder_a = FSMBuilder::new();
|
||||||
|
let z0 = builder_a.add_named_state("z0".to_string(), StateType::Error);
|
||||||
|
let z1 = builder_a.add_named_state("z1".to_string(), StateType::Error);
|
||||||
|
let z2 = builder_a.add_named_state("z2".to_string(), StateType::Terminal);
|
||||||
|
let z3 = builder_a.add_named_state("z3".to_string(), StateType::Terminal);
|
||||||
|
let z4 = builder_a.add_named_state("z4".to_string(), StateType::Error);
|
||||||
|
|
||||||
|
builder_a.add_link(&z0, &z1, Action::None, mask_char('a')).unwrap();
|
||||||
|
builder_a.add_link(&z1, &z2, Action::None, mask_char('b')).unwrap();
|
||||||
|
builder_a.add_link(&z2, &z3, Action::None, mask_char('a')).unwrap();
|
||||||
|
builder_a.add_link(&z3, &z3, Action::None, mask_char('a')).unwrap();
|
||||||
|
let fsm_b_res = builder_a.finish(&z4, Action::None, &z0);
|
||||||
|
let fsm_b_res_clone = fsm_b_res.clone();
|
||||||
|
|
||||||
|
let accepted_b = ["ab", "aba", "abaa", "abaaa", "abaaaa", "abaaaaa"];
|
||||||
|
let rejected_b = ["a", "b", "ba", "aab", "abb", "baba", "", "bb", "aaa"];
|
||||||
|
|
||||||
|
// Test FSM B with different inputs
|
||||||
|
if let Ok(fsm_b) = fsm_b_res {
|
||||||
|
debug_dump_fsm_graph(&fsm_b, ".debug/union_b.svg".to_string());
|
||||||
|
// Accepts "ab", "aba", "abaa", "abaaa", etc.
|
||||||
|
for input in &accepted_b {
|
||||||
|
let res = run_fsm(&fsm_b, &input.to_string());
|
||||||
|
assert_eq!(res, StateType::Terminal, "FSM B should accept '{}'", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rejects "a", "b", "ba", "aab", "abb", "baba", "", "bb", "aaa"
|
||||||
|
for input in &rejected_b {
|
||||||
|
let res = run_fsm(&fsm_b, &input.to_string());
|
||||||
|
assert_eq!(res, StateType::Error, "FSM B should reject '{}'", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fsm_a= fsm_a_res_clone.unwrap();
|
||||||
|
let fsm_b = fsm_b_res_clone.unwrap();
|
||||||
|
|
||||||
|
let fsm_res = union_fsm(&fsm_a, &fsm_b);
|
||||||
|
assert!(fsm_res.is_ok());
|
||||||
|
|
||||||
|
if let Ok(fsm) = fsm_res {
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&fsm, &"".to_string()), "Empty String");
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&fsm, &"this-is-not-a-number".to_string()), "Text");
|
||||||
|
assert_eq!(StateType::Error, run_fsm(&fsm, &"13373 is a nice number".to_string()), "Number + Text");
|
||||||
|
|
||||||
|
debug_dump_fsm_graph(&fsm, ".debug/union_res.svg".to_string());
|
||||||
|
let all_rejected = rejected_a.iter()
|
||||||
|
.chain(rejected_b.iter())
|
||||||
|
.chain(accepted_a.iter())
|
||||||
|
.chain(accepted_b.iter());
|
||||||
|
for i in all_rejected {
|
||||||
|
let input = i.to_string();
|
||||||
|
|
||||||
|
let res_a = run_fsm(&fsm_a, &input);
|
||||||
|
let res_b = run_fsm(&fsm_b, &input);
|
||||||
|
let res = run_fsm(&fsm, &input);
|
||||||
|
|
||||||
|
let expected = match (res_a, res_b) {
|
||||||
|
(StateType::Terminal, StateType::Terminal) => StateType::Terminal,
|
||||||
|
(_, _) => StateType::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(res, expected, "For input {} Union FSM output '{:?}', got '{:?}' (A: {:?}, B: {:?})", input, expected, res, res_a, res_b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_bound() {
|
||||||
|
|
||||||
|
assert!(build_fsm_in_bound(2, 1).is_err());
|
||||||
|
|
||||||
|
let test_number_pairs = vec![
|
||||||
|
(0, 0),
|
||||||
|
(0, 1),
|
||||||
|
(1, 10),
|
||||||
|
(5, 15),
|
||||||
|
(10, 100),
|
||||||
|
(42, 123),
|
||||||
|
(100, 1000),
|
||||||
|
(500, 2024),
|
||||||
|
(751394, 751400),
|
||||||
|
(1, 9999),
|
||||||
|
(0, 9999),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (min, max) in test_number_pairs {
|
||||||
|
if min > max {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fsm_result = build_fsm_in_bound(min, max);
|
||||||
|
assert!(fsm_result.is_ok());
|
||||||
|
println!("Testing {}..{}", min, max);
|
||||||
|
|
||||||
|
if let Ok(fsm) = fsm_result {
|
||||||
|
debug_dump_fsm_graph(&fsm, format!(".debug/in_bound/{}_{}.svg", min, max));
|
||||||
|
for i in 0..=max*10 {
|
||||||
|
let input_string = i.to_string();
|
||||||
|
let expected = if i >= min && i <= max {
|
||||||
|
StateType::Terminal
|
||||||
|
} else {
|
||||||
|
StateType::Error
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = run_fsm(&fsm, &input_string);
|
||||||
|
|
||||||
|
assert_eq!(res, expected, "Expecet '{:?}' (got '{:?}') for {} (Range: {}..{})", expected, res, i, min, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -131,6 +131,27 @@ pub fn create_graphviz_graph(fsm: &FiniteStateMachine) -> Graph {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stmts.push(
|
||||||
|
Stmt::Node(Node {
|
||||||
|
id: NodeId(Id::Plain("Start".to_string()), None),
|
||||||
|
attributes: vec![
|
||||||
|
Attribute(Id::Plain("shape".to_string()), Id::Plain("plain".to_string())),
|
||||||
|
Attribute(Id::Plain("label".to_string()), Id::Plain("\" \"".to_string())),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
stmts.push(
|
||||||
|
Stmt::Edge(Edge {
|
||||||
|
ty: EdgeTy::Pair(
|
||||||
|
Vertex::N(NodeId(Id::Plain("Start".to_string()), None)),
|
||||||
|
Vertex::N(NodeId(format_id(&fsm.start), None))
|
||||||
|
),
|
||||||
|
attributes: vec![]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
Graph::DiGraph { id: Id::Plain("FSM".to_string()), strict: true, stmts: stmts }
|
Graph::DiGraph { id: Id::Plain("FSM".to_string()), strict: true, stmts: stmts }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,8 +178,6 @@ mod test {
|
|||||||
use graphviz_rust::{printer::{DotPrinter, PrinterContext}};
|
use graphviz_rust::{printer::{DotPrinter, PrinterContext}};
|
||||||
|
|
||||||
use crate::configotron::fsm::{display::{create_graphviz_graph, debug_dump_fsm_graph, mask_to_string}, masks::{mask_all, mask_char, mask_char_range}, Action, FSMBuilder, StateType};
|
use crate::configotron::fsm::{display::{create_graphviz_graph, debug_dump_fsm_graph, mask_to_string}, masks::{mask_all, mask_char, mask_char_range}, Action, FSMBuilder, StateType};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mask_to_string_test() {
|
fn mask_to_string_test() {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ impl fmt::Display for Action {
|
|||||||
|
|
||||||
pub type TransitionArray<T> = [T; 128];
|
pub type TransitionArray<T> = [T; 128];
|
||||||
pub type UnfinishedTransitionArray<T> = [Option<T>; 128];
|
pub type UnfinishedTransitionArray<T> = [Option<T>; 128];
|
||||||
|
pub type StateTransition = TransitionArray<(StateRef, Action)>;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Represents a State in consolidate State machine State
|
/// Represents a State in consolidate State machine State
|
||||||
@@ -44,9 +45,10 @@ pub type UnfinishedTransitionArray<T> = [Option<T>; 128];
|
|||||||
/// The `transition` field contains the outgoing transitions for this state,
|
/// The `transition` field contains the outgoing transitions for this state,
|
||||||
/// where each entry corresponds to a possible input symbol.
|
/// where each entry corresponds to a possible input symbol.
|
||||||
/// Its expected to have ONLY valid StateRefs
|
/// Its expected to have ONLY valid StateRefs
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
name: String,
|
name: String,
|
||||||
transition: TransitionArray<(StateRef, Action)>,
|
transition: StateTransition,
|
||||||
state_type: StateType
|
state_type: StateType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +56,7 @@ pub struct State {
|
|||||||
/// Represents a finished compleate consolidate State machine
|
/// Represents a finished compleate consolidate State machine
|
||||||
///
|
///
|
||||||
/// `start` is expected to be a valid refernces in the `states` Hashmap
|
/// `start` is expected to be a valid refernces in the `states` Hashmap
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct FiniteStateMachine {
|
pub struct FiniteStateMachine {
|
||||||
states: HashMap<StateRef, State>,
|
states: HashMap<StateRef, State>,
|
||||||
start: StateRef
|
start: StateRef
|
||||||
@@ -77,7 +80,7 @@ impl FSMBuilder {
|
|||||||
FSMBuilder { states: HashMap::new() }
|
FSMBuilder { states: HashMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish (&self, default_state: &StateRef, default_action: Action, start_state: &StateRef) -> Result<FiniteStateMachine, &str> {
|
pub fn finish(&self, default_state: &StateRef, default_action: Action, start_state: &StateRef) -> Result<FiniteStateMachine, &str> {
|
||||||
if self.states.len() < 1 {
|
if self.states.len() < 1 {
|
||||||
return Err("State Machine must have at least one state!");
|
return Err("State Machine must have at least one state!");
|
||||||
}
|
}
|
||||||
@@ -105,7 +108,41 @@ impl FSMBuilder {
|
|||||||
|
|
||||||
// 2. Elimate all unsed states
|
// 2. Elimate all unsed states
|
||||||
let used_states = Self::get_used_states(&filled_states, start_state);
|
let used_states = Self::get_used_states(&filled_states, start_state);
|
||||||
|
filled_states.retain(|id, _| used_states.contains(id));
|
||||||
|
|
||||||
|
Ok(FiniteStateMachine { states: filled_states, start: *start_state })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish_without_default(&self, start_state: &StateRef) -> Result<FiniteStateMachine, &str> {
|
||||||
|
if self.states.len() < 1 {
|
||||||
|
return Err("State Machine must have at least one state!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.states.contains_key(start_state) {
|
||||||
|
return Err("Start state is not in the State Set!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Check for any vacant transitions
|
||||||
|
for (_, state) in self.states.iter() {
|
||||||
|
for trans in state.transition.iter() {
|
||||||
|
if trans.is_none() {
|
||||||
|
return Err("Not all transitions are filled!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. All transitions are filled, so build the FSM
|
||||||
|
let mut filled_states: HashMap<Uuid, State> = HashMap::new();
|
||||||
|
for (id, state) in self.states.iter() {
|
||||||
|
filled_states.insert(*id, State {
|
||||||
|
name: state.name.clone(),
|
||||||
|
state_type: state.state_type,
|
||||||
|
transition: std::array::from_fn(|i| state.transition[i].clone().unwrap()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Eliminate all unused states
|
||||||
|
let used_states = Self::get_used_states(&filled_states, start_state);
|
||||||
filled_states.retain(|id, _| used_states.contains(id));
|
filled_states.retain(|id, _| used_states.contains(id));
|
||||||
|
|
||||||
Ok(FiniteStateMachine { states: filled_states, start: *start_state })
|
Ok(FiniteStateMachine { states: filled_states, start: *start_state })
|
||||||
|
|||||||
Reference in New Issue
Block a user