Improved State Machine Representation using Hashmaps
This commit is contained in:
98
Cargo.lock
generated
98
Cargo.lock
generated
@@ -23,6 +23,12 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -40,6 +46,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_yml",
|
"serde_yml",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -216,6 +223,16 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.172"
|
version = "0.2.172"
|
||||||
@@ -238,6 +255,12 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
@@ -380,6 +403,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
@@ -531,6 +560,17 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@@ -552,6 +592,64 @@ dependencies = [
|
|||||||
"wit-bindgen-rt",
|
"wit-bindgen-rt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.101",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.101",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
|||||||
@@ -11,3 +11,10 @@ thiserror = "1"
|
|||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
graphviz-rust = "0.9.4"
|
graphviz-rust = "0.9.4"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
|
||||||
|
[dependencies.uuid]
|
||||||
|
version = "1.17.0"
|
||||||
|
# Lets you generate random UUIDs
|
||||||
|
features = [
|
||||||
|
"v4",
|
||||||
|
]
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
use std::{cell::RefCell, collections::HashSet, rc::{Rc, Weak}};
|
|
||||||
|
|
||||||
use crate::configotron::fsm::{FSMState, FSMStateRef, FSM};
|
|
||||||
|
|
||||||
pub mod numbers;
|
|
||||||
|
|
||||||
pub fn elimate_unused_states(fsm: &mut FSM) {
|
|
||||||
let start = fsm.start.as_ref().unwrap();
|
|
||||||
let mut used: HashSet<String> = HashSet::new();
|
|
||||||
|
|
||||||
pub fn explore_states(s: Weak<RefCell<FSMState>>, used: &mut HashSet<String>) -> Option<()> {
|
|
||||||
|
|
||||||
let s_rc = s.upgrade()?;
|
|
||||||
let s_borrow = s_rc.borrow();
|
|
||||||
let t = s_borrow.transition.as_ref()?;
|
|
||||||
let name = s_borrow.name.clone();
|
|
||||||
|
|
||||||
if !used.contains(&name) {
|
|
||||||
used.insert(name);
|
|
||||||
|
|
||||||
for i in 0..(128 as u8) {
|
|
||||||
explore_states(t.get_next(i), used);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
explore_states(Rc::downgrade(start), &mut used);
|
|
||||||
|
|
||||||
let mut new_state_array: Vec<FSMStateRef> = Vec::new();
|
|
||||||
|
|
||||||
fsm.states.drain(..).for_each(|s| {
|
|
||||||
let s_name = s.borrow().name.clone();
|
|
||||||
if used.contains(&s_name) {
|
|
||||||
new_state_array.push(s);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fsm.states = new_state_array;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use graphviz_rust::cmd::Format;
|
|
||||||
|
|
||||||
use crate::configotron::fsm::{builder::elimate_unused_states, masking::{MaskPresets, TransitionMask}, new_state_ref, test::debug_file_dump, transition::FSMTranistion, FSMState, FSMStateAction, FSM};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn state_elimitnation() {
|
|
||||||
let z0 = new_state_ref(FSMState::new(
|
|
||||||
"z0".to_string(), FSMStateAction::Terminal,
|
|
||||||
));
|
|
||||||
let z1 = new_state_ref(FSMState::new(
|
|
||||||
"z1".to_string(), FSMStateAction::Terminal,
|
|
||||||
));
|
|
||||||
let z2 = new_state_ref(FSMState::new(
|
|
||||||
"z2".to_string(), FSMStateAction::Terminal,
|
|
||||||
));
|
|
||||||
let z3 = new_state_ref(FSMState::new(
|
|
||||||
"z3".to_string(), FSMStateAction::Terminal,
|
|
||||||
));
|
|
||||||
let z4 = new_state_ref(FSMState::new(
|
|
||||||
"z4".to_string(), FSMStateAction::Terminal,
|
|
||||||
));
|
|
||||||
let z5 = new_state_ref(FSMState::new(
|
|
||||||
"z5".to_string(), FSMStateAction::Terminal,
|
|
||||||
));
|
|
||||||
|
|
||||||
let start = z0.clone();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Links:
|
|
||||||
* (z0) -> (z1)
|
|
||||||
* (z1) -> (z2), (z3)
|
|
||||||
* (z2) -> (z2)
|
|
||||||
* (z3) -> (z1)
|
|
||||||
* X (z4) -> (z3)
|
|
||||||
* X (z5) -> (z5)
|
|
||||||
*/
|
|
||||||
|
|
||||||
z0.borrow_mut().set_transitions({
|
|
||||||
let t = FSMTranistion::new(Rc::downgrade(&z1));
|
|
||||||
t
|
|
||||||
});
|
|
||||||
z1.borrow_mut().set_transitions({
|
|
||||||
let mut t = FSMTranistion::new(Rc::downgrade(&z2));
|
|
||||||
t.apply(TransitionMask::from(MaskPresets::LowerCase), Rc::downgrade(&z3));
|
|
||||||
t
|
|
||||||
});
|
|
||||||
z2.borrow_mut().set_transitions({
|
|
||||||
let t = FSMTranistion::new(Rc::downgrade(&z2));
|
|
||||||
t
|
|
||||||
});
|
|
||||||
z3.borrow_mut().set_transitions({
|
|
||||||
let t = FSMTranistion::new(Rc::downgrade(&z1));
|
|
||||||
t
|
|
||||||
});
|
|
||||||
z4.borrow_mut().set_transitions({
|
|
||||||
let t = FSMTranistion::new(Rc::downgrade(&z3));
|
|
||||||
t
|
|
||||||
});
|
|
||||||
z5.borrow_mut().set_transitions({
|
|
||||||
let t = FSMTranistion::new(Rc::downgrade(&z5));
|
|
||||||
t
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
let state_prev = vec![z0.clone(), z1.clone(), z2.clone(), z3.clone(), z4.clone(), z5.clone()];
|
|
||||||
let state_after = vec![z0.clone(), z1.clone(), z2.clone(), z3.clone()];
|
|
||||||
|
|
||||||
let mut fsm = FSM {
|
|
||||||
states: state_prev,
|
|
||||||
name: "test_fsm".to_string(),
|
|
||||||
start: Some(start)
|
|
||||||
};
|
|
||||||
|
|
||||||
debug_file_dump(fsm.render_graph(Format::Svg).unwrap(), "/tmp/state_elimitiation_before.svg".to_string());
|
|
||||||
|
|
||||||
elimate_unused_states(&mut fsm);
|
|
||||||
|
|
||||||
debug_file_dump(fsm.render_graph(Format::Svg).unwrap(), "/tmp/state_elimitiation_after.svg".to_string());
|
|
||||||
|
|
||||||
|
|
||||||
assert_eq!(fsm.states.len(), state_after.len());
|
|
||||||
|
|
||||||
for (i, j) in fsm.states.iter().zip(state_after) {
|
|
||||||
assert_eq!(i.borrow().name, j.borrow().name)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
use std::{rc::Rc};
|
|
||||||
|
|
||||||
use crate::{configotron::fsm::{builder::elimate_unused_states, masking::{MaskPresets, TransitionMask}, new_state_ref, transition::FSMTranistion, FSMState, FSMStateAction, FSMStateRef}, main};
|
|
||||||
|
|
||||||
use super::super::FSM;
|
|
||||||
|
|
||||||
fn number_of_digits(i: i32) -> u32 {
|
|
||||||
if i == 0 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
let mut n = i.abs();
|
|
||||||
let mut digits = 0;
|
|
||||||
while n > 0 { // Yes, with floating point math it does not work, sorry ;(
|
|
||||||
n /= 10;
|
|
||||||
digits += 1;
|
|
||||||
}
|
|
||||||
digits
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nth_decimal_digit(num: u32, n: u32) -> u32 {
|
|
||||||
let num_digits = number_of_digits(num as i32);
|
|
||||||
if n >= num_digits {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let divisor = 10u32.pow(num_digits - n - 1);
|
|
||||||
(num / divisor) % 10
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_fsm_number_scafold(n: u32) -> (Vec<FSMStateRef>, Vec<FSMStateRef>, FSMStateRef, FSMStateRef) {
|
|
||||||
let length_of_number = number_of_digits(n as i32);
|
|
||||||
|
|
||||||
// Creating All states
|
|
||||||
let error_state = new_state_ref(FSMState::new("e".to_string(), FSMStateAction::Error));
|
|
||||||
let goal_state = new_state_ref(FSMState::new("g".to_string(), FSMStateAction::Terminal));
|
|
||||||
|
|
||||||
let mut main_line: Vec<FSMStateRef> = Vec::new();
|
|
||||||
let mut side_line: Vec<FSMStateRef> = Vec::new();
|
|
||||||
|
|
||||||
// Create and Link Main to side
|
|
||||||
for i in 0..length_of_number {
|
|
||||||
let digit = nth_decimal_digit(n, i);
|
|
||||||
|
|
||||||
let new_main_state = new_state_ref(FSMState::new(
|
|
||||||
format!("m{}", i), FSMStateAction::None));
|
|
||||||
let new_side_state = new_state_ref(FSMState::new(
|
|
||||||
format!("s{}", i), FSMStateAction::None));
|
|
||||||
|
|
||||||
// Main -> Error
|
|
||||||
let mut main_state_transition = FSMTranistion::new(Rc::downgrade(&error_state));
|
|
||||||
|
|
||||||
// Side -> Error
|
|
||||||
new_side_state.borrow_mut().set_transitions(
|
|
||||||
FSMTranistion::new(Rc::downgrade(&error_state))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Main -> Side (-) Transition [0..n-1]
|
|
||||||
if digit > 0 {
|
|
||||||
main_state_transition.apply(TransitionMask::from(MaskPresets::NumberRange(0..=(digit as u8 - 1))), Rc::downgrade(&new_side_state));
|
|
||||||
}
|
|
||||||
|
|
||||||
new_main_state.borrow_mut().set_transitions(
|
|
||||||
main_state_transition
|
|
||||||
);
|
|
||||||
|
|
||||||
main_line.push(new_main_state.clone());
|
|
||||||
side_line.push(new_side_state.clone());
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Link Main to main, side to side
|
|
||||||
for (i,
|
|
||||||
(
|
|
||||||
(prev_main, next_main),
|
|
||||||
(prev_side, next_side)
|
|
||||||
)
|
|
||||||
) in
|
|
||||||
main_line.iter().zip(main_line.iter().skip(1))
|
|
||||||
.zip(side_line.iter().zip(side_line.iter().skip(1)))
|
|
||||||
.enumerate() {
|
|
||||||
let digit = nth_decimal_digit(n, i as u32);
|
|
||||||
|
|
||||||
match &mut prev_main.borrow_mut().transition {
|
|
||||||
None => {},
|
|
||||||
Some(t) => {
|
|
||||||
|
|
||||||
// prev_main -> next_side (+) Transition
|
|
||||||
if digit < 9 {
|
|
||||||
t.apply(
|
|
||||||
TransitionMask::from(MaskPresets::NumberRange((digit as u8 + 1)..=9)),
|
|
||||||
Rc::downgrade(next_side)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// prev_main -> next_main (=) Transition
|
|
||||||
t.apply(
|
|
||||||
TransitionMask::from(MaskPresets::Char(std::char::from_digit(digit, 10).unwrap())),
|
|
||||||
Rc::downgrade(next_main)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prev_side -> next_side
|
|
||||||
match &mut prev_side.borrow_mut().transition {
|
|
||||||
None => {},
|
|
||||||
Some(t) => {
|
|
||||||
t.apply(
|
|
||||||
TransitionMask::from(MaskPresets::Numbers),
|
|
||||||
Rc::downgrade(next_side)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(main_line, side_line, goal_state, error_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_fsm_number_upper_bound(max: u32) -> FSM {
|
|
||||||
let (
|
|
||||||
mut main_line,
|
|
||||||
mut side_line,
|
|
||||||
goal_state,
|
|
||||||
error_state
|
|
||||||
) = create_fsm_number_scafold(max);
|
|
||||||
|
|
||||||
// Setting main/side line as Terminal states
|
|
||||||
for (side, main) in side_line.iter_mut().zip(&mut main_line) {
|
|
||||||
side.borrow_mut().action = FSMStateAction::Terminal;
|
|
||||||
main.borrow_mut().action = FSMStateAction::Terminal;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Link last main to goal
|
|
||||||
match &mut main_line.last() {
|
|
||||||
Some(main_last) => {
|
|
||||||
let last_digit = max % 10;
|
|
||||||
match &mut main_last.borrow_mut().transition {
|
|
||||||
Some(t) => {
|
|
||||||
// Main -> Goal (=) or (+)
|
|
||||||
t.apply(TransitionMask::from(MaskPresets::NumberRange(0..=(last_digit as u8))),
|
|
||||||
Rc::downgrade(&goal_state)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link goal to error
|
|
||||||
goal_state.borrow_mut().set_transitions(FSMTranistion::new(Rc::downgrade(&error_state)));
|
|
||||||
|
|
||||||
// Linking error to error
|
|
||||||
error_state.borrow_mut().set_transitions(FSMTranistion::new(Rc::downgrade(&error_state)));
|
|
||||||
|
|
||||||
// Combinding in one array
|
|
||||||
let mut states: Vec<Rc<std::cell::RefCell<FSMState>>> = vec![error_state, goal_state];
|
|
||||||
let first_state: Option<FSMStateRef> = match main_line.first() {
|
|
||||||
None => None,
|
|
||||||
Some(first) => {
|
|
||||||
Some(first.clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
states.append(&mut main_line);
|
|
||||||
states.append(&mut side_line);
|
|
||||||
|
|
||||||
let mut fsm = FSM {
|
|
||||||
name: format!("FSM_UpperBound_{}", max),
|
|
||||||
states: states,
|
|
||||||
start: first_state
|
|
||||||
};
|
|
||||||
|
|
||||||
elimate_unused_states(&mut fsm);
|
|
||||||
|
|
||||||
fsm
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_fsm_number_lower_bound(min: u32) -> FSM {
|
|
||||||
let (
|
|
||||||
mut main_line,
|
|
||||||
mut side_line,
|
|
||||||
goal_state,
|
|
||||||
error_state
|
|
||||||
) = create_fsm_number_scafold(min);
|
|
||||||
|
|
||||||
// Setting main/side line as Error states
|
|
||||||
for (side, main) in side_line.iter_mut().zip(&mut main_line) {
|
|
||||||
side.borrow_mut().action = FSMStateAction::Error;
|
|
||||||
main.borrow_mut().action = FSMStateAction::Error;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Link last main to goal,error,side
|
|
||||||
match &mut main_line.last() {
|
|
||||||
Some(main_last) => {
|
|
||||||
let last_digit = min % 10;
|
|
||||||
match &mut main_last.borrow_mut().transition {
|
|
||||||
Some(t) => {
|
|
||||||
// Main -> Goal (=)
|
|
||||||
t.apply(TransitionMask::from(MaskPresets::NumberRange(0..=(last_digit as u8))),
|
|
||||||
Rc::downgrade(&goal_state)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Main -> Error (+)
|
|
||||||
t.apply(TransitionMask::from(MaskPresets::NumberRange(0..=(last_digit as u8))),
|
|
||||||
Rc::downgrade(&goal_state)
|
|
||||||
);
|
|
||||||
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link goal to error
|
|
||||||
goal_state.borrow_mut().set_transitions(FSMTranistion::new(Rc::downgrade(&error_state)));
|
|
||||||
|
|
||||||
// Linking error to error
|
|
||||||
error_state.borrow_mut().set_transitions(FSMTranistion::new(Rc::downgrade(&error_state)));
|
|
||||||
|
|
||||||
// Combinding in one array
|
|
||||||
let mut states: Vec<Rc<std::cell::RefCell<FSMState>>> = vec![error_state, goal_state];
|
|
||||||
let first_state: Option<FSMStateRef> = match main_line.first() {
|
|
||||||
None => None,
|
|
||||||
Some(first) => {
|
|
||||||
Some(first.clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
states.append(&mut main_line);
|
|
||||||
states.append(&mut side_line);
|
|
||||||
|
|
||||||
let mut fsm = FSM {
|
|
||||||
name: format!("FSM_LowerBound_{}", min),
|
|
||||||
states: states,
|
|
||||||
start: first_state
|
|
||||||
};
|
|
||||||
|
|
||||||
elimate_unused_states(&mut fsm);
|
|
||||||
|
|
||||||
fsm
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use graphviz_rust::cmd::Format;
|
|
||||||
|
|
||||||
use crate::configotron::fsm::{builder::numbers::{create_fsm_number_upper_bound, number_of_digits}, run::run_fsm, test::debug_file_dump, FSMStateAction};
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn build_upper_bound() {
|
|
||||||
for max in vec![4026, 124, 9999, 1000, 0, 42398] {
|
|
||||||
let fsm = create_fsm_number_upper_bound(max);
|
|
||||||
let data = fsm.render_graph(Format::Svg).unwrap();
|
|
||||||
println!("Testing upper Bound FSM {}", max);
|
|
||||||
|
|
||||||
for i in 0..=(if max == 0 {10} else {max * 10} ) {
|
|
||||||
|
|
||||||
assert_eq!(run_fsm(&i.to_string(), &fsm), if i <= max {
|
|
||||||
FSMStateAction::Terminal
|
|
||||||
} else {
|
|
||||||
FSMStateAction::Error
|
|
||||||
}, "{} yield the wrong final state", i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Testing against refernce machine
|
|
||||||
let file_name = format!("build_number_upper_bound_{}.svg", max);
|
|
||||||
debug_file_dump(data.clone(), format!("/tmp/{}", file_name).to_string());
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn build_lower_bound() {
|
|
||||||
for max in vec![4026, 124, 9999, 1000, 0, 42398] {
|
|
||||||
let fsm = create_fsm_number_upper_bound(max);
|
|
||||||
let data = fsm.render_graph(Format::Svg).unwrap();
|
|
||||||
println!("Testing Lower Bound FSM {}", max);
|
|
||||||
|
|
||||||
for i in 0..=(if max == 0 {10} else {max * 10} ) {
|
|
||||||
|
|
||||||
assert_eq!(run_fsm(&i.to_string(), &fsm), if i >= max {
|
|
||||||
FSMStateAction::Terminal
|
|
||||||
} else {
|
|
||||||
FSMStateAction::Error
|
|
||||||
}, "{} yield the wrong final state", i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Testing against refernce machine
|
|
||||||
let file_name = format!("build_number_lower_bound_{}.svg", max);
|
|
||||||
debug_file_dump(data.clone(), format!("/tmp/{}", file_name).to_string());
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn number_of_digits_test() {
|
|
||||||
assert_eq!(number_of_digits(0), 1);
|
|
||||||
assert_eq!(number_of_digits(9), 1);
|
|
||||||
assert_eq!(number_of_digits(10), 2);
|
|
||||||
assert_eq!(number_of_digits(99), 2);
|
|
||||||
assert_eq!(number_of_digits(100), 3);
|
|
||||||
assert_eq!(number_of_digits(-1), 1);
|
|
||||||
assert_eq!(number_of_digits(-9), 1);
|
|
||||||
assert_eq!(number_of_digits(-10), 2);
|
|
||||||
assert_eq!(number_of_digits(-99), 2);
|
|
||||||
assert_eq!(number_of_digits(-100), 3);
|
|
||||||
assert_eq!(number_of_digits(123456), 6);
|
|
||||||
assert_eq!(number_of_digits(-123456), 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
src/configotron/fsm/display.rs
Normal file
0
src/configotron/fsm/display.rs
Normal file
@@ -1,224 +0,0 @@
|
|||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
use std::{ops::RangeInclusive, usize};
|
|
||||||
|
|
||||||
pub enum MaskPresets {
|
|
||||||
Char(char),
|
|
||||||
AnyCase,
|
|
||||||
LowerCase,
|
|
||||||
UpperCase,
|
|
||||||
AllHumanReadable,
|
|
||||||
Numbers,
|
|
||||||
NumberRange(RangeInclusive<u8>),
|
|
||||||
SpecialChars,
|
|
||||||
WhiteSpace,
|
|
||||||
LineTermination,
|
|
||||||
NoChar,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TransitionMask {
|
|
||||||
mask: u128,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MaskPresets> for TransitionMask {
|
|
||||||
fn from(value: MaskPresets) -> Self {
|
|
||||||
let mut n: u128 = 0;
|
|
||||||
|
|
||||||
let mut set_range = |r: RangeInclusive<i32>| {
|
|
||||||
r.for_each(|i| n |= 1 << i);
|
|
||||||
};
|
|
||||||
|
|
||||||
match value {
|
|
||||||
MaskPresets::NoChar => {}
|
|
||||||
MaskPresets::AnyCase => {
|
|
||||||
set_range(0x41..=0x5a); // UpperCase
|
|
||||||
set_range(0x61..=0x7a); // LowerCase
|
|
||||||
},
|
|
||||||
MaskPresets::NumberRange(r) => {
|
|
||||||
let start = (*r.start() as i32) + 0x30;
|
|
||||||
let end = (*r.end() as i32) + 0x30;
|
|
||||||
set_range(start..=end);
|
|
||||||
}
|
|
||||||
MaskPresets::LowerCase => {
|
|
||||||
set_range(0x61..=0x7a); // LowerCase
|
|
||||||
}
|
|
||||||
MaskPresets::UpperCase => {
|
|
||||||
set_range(0x41..=0x5a); // UpperCase
|
|
||||||
}
|
|
||||||
MaskPresets::AllHumanReadable => {
|
|
||||||
set_range(0x20..=0x7e); // All Ascii Chars + Space + Tab
|
|
||||||
n |= 1 << 0x9; // Tab
|
|
||||||
}
|
|
||||||
MaskPresets::Numbers => {
|
|
||||||
set_range(0x30..=0x39); // Numbers
|
|
||||||
}
|
|
||||||
MaskPresets::SpecialChars => {
|
|
||||||
set_range(0x21..=0x2F); // ! to /
|
|
||||||
set_range(0x3A..=0x40); // : to @
|
|
||||||
set_range(0x5B..=0x0D); // [ to `
|
|
||||||
set_range(0x7B..=0x7E); // { to ~
|
|
||||||
}
|
|
||||||
MaskPresets::WhiteSpace => {
|
|
||||||
n |= 1 << 0x9; // Tab
|
|
||||||
n |= 1 << 0x20; // Spaces
|
|
||||||
}
|
|
||||||
MaskPresets::LineTermination => {
|
|
||||||
n |= 1 << 0x0a; // Line Feed
|
|
||||||
n |= 1 << 0x0d; // Carriage Return
|
|
||||||
}
|
|
||||||
MaskPresets::Char(c) => {
|
|
||||||
if c.is_ascii() {
|
|
||||||
n |= 1 << (c as u32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TransitionMask { mask: n }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransitionMask {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
TransitionMask { mask: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn or(&self, other: Self) -> TransitionMask {
|
|
||||||
TransitionMask {
|
|
||||||
mask: other.mask | self.mask,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn and(&self, other: Self) -> TransitionMask {
|
|
||||||
TransitionMask {
|
|
||||||
mask: other.mask & self.mask,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inv(&self) -> TransitionMask {
|
|
||||||
TransitionMask { mask: !self.mask }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_bit(&self, i: usize) -> bool {
|
|
||||||
(self.mask & (1 << i)) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_bit(&mut self, i: usize) {
|
|
||||||
self.mask |= 1 << i;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
let mut res: Vec<String> = Vec::new();
|
|
||||||
let mut strike: Option<usize> = None;
|
|
||||||
|
|
||||||
fn num_to_str(i: usize) -> String {
|
|
||||||
if (0x21..=0x7E).contains(&i) {
|
|
||||||
format!("'{}'", ((i as u8) as char).to_string())
|
|
||||||
} else {
|
|
||||||
match i {
|
|
||||||
0x20 => "SP".to_string(),
|
|
||||||
0x0D => "LF".to_string(),
|
|
||||||
0x0A => "CR".to_string(),
|
|
||||||
0x09 => "TAB".to_string(),
|
|
||||||
_ => format!("{:02x}", i),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (p, n) in (0..128).tuple_windows() {
|
|
||||||
match (strike, self.get_bit(p), self.get_bit(n)) {
|
|
||||||
(None, true, true) => {
|
|
||||||
// First two
|
|
||||||
strike = Some(p);
|
|
||||||
}
|
|
||||||
(None, true, false) => {
|
|
||||||
// First, only one
|
|
||||||
res.push(num_to_str(p));
|
|
||||||
}
|
|
||||||
(None, false, true) => {
|
|
||||||
// Strik Start
|
|
||||||
strike = Some(n)
|
|
||||||
}
|
|
||||||
(None, false, false) => {
|
|
||||||
// No Strike
|
|
||||||
}
|
|
||||||
(Some(_), true, true) => {
|
|
||||||
// Stike ongoing
|
|
||||||
}
|
|
||||||
(Some(s), true, false) => {
|
|
||||||
// Strik end
|
|
||||||
if s == p {
|
|
||||||
// Single Number
|
|
||||||
res.push(num_to_str(p));
|
|
||||||
} else {
|
|
||||||
// Range
|
|
||||||
res.push(format!("{}-{}", num_to_str(s), num_to_str(p)));
|
|
||||||
}
|
|
||||||
|
|
||||||
strike = None;
|
|
||||||
}
|
|
||||||
(Some(_), false, true) => {
|
|
||||||
// This should no happend
|
|
||||||
strike = Some(n);
|
|
||||||
}
|
|
||||||
(Some(_), false, false) => {
|
|
||||||
// Should also no happend
|
|
||||||
strike = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match strike {
|
|
||||||
None => (),
|
|
||||||
Some(s) => {
|
|
||||||
if s == 127 {
|
|
||||||
res.push(num_to_str(s));
|
|
||||||
} else {
|
|
||||||
res.push(format!("{}-{}", num_to_str(s), num_to_str(127)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
res.join(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mod test {
|
|
||||||
use super::TransitionMask;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn transition_mask_to_string() {
|
|
||||||
let mut mask1 = TransitionMask::new();
|
|
||||||
|
|
||||||
mask1.set_bit(0);
|
|
||||||
|
|
||||||
(10..=16).for_each(|i| {
|
|
||||||
mask1.set_bit(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
(50..=51).for_each(|i| {
|
|
||||||
mask1.set_bit(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
mask1.set_bit(60);
|
|
||||||
mask1.set_bit(62);
|
|
||||||
mask1.set_bit(64);
|
|
||||||
|
|
||||||
(70..=73).for_each(|i| {
|
|
||||||
mask1.set_bit(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
(126..=127).for_each(|i| {
|
|
||||||
mask1.set_bit(i);
|
|
||||||
});
|
|
||||||
|
|
||||||
println!("{}", mask1.to_string());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
mask1.to_string(),
|
|
||||||
"00,CR-10,'2'-'3','<','>','@','F'-'I','~'-7f"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
127
src/configotron/fsm/masks.rs
Normal file
127
src/configotron/fsm/masks.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use std::{collections::{HashMap}, hash::Hash};
|
||||||
|
|
||||||
|
use crate::configotron::fsm::{TransitionArray, TransitionMask};
|
||||||
|
|
||||||
|
pub fn decompose_transition<T: Hash + Eq + PartialEq + Clone>(array: &TransitionArray<T>) -> Vec<(T, TransitionMask)> {
|
||||||
|
let mut results: HashMap<T, TransitionMask> = HashMap::new();
|
||||||
|
|
||||||
|
for (pos, i) in array.iter().enumerate() {
|
||||||
|
let m: TransitionMask = 1 << pos;
|
||||||
|
results.entry(i.clone())
|
||||||
|
.and_modify(|val| *val |= m)
|
||||||
|
.or_insert(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mask_number(i: u8) -> TransitionMask {
|
||||||
|
let i = i % 10;
|
||||||
|
1 << (i + b'0')
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mask_any_number() -> TransitionMask {
|
||||||
|
let mut mask: TransitionMask = 0;
|
||||||
|
for n in 0..=9 {
|
||||||
|
mask |= 1 << (n + b'0');
|
||||||
|
}
|
||||||
|
mask
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mask_number_range(start: u8, end: u8) -> TransitionMask {
|
||||||
|
let mut mask: TransitionMask = 0;
|
||||||
|
for n in (start % 10)..=(end % 10) {
|
||||||
|
mask |= 1 << (n + b'0');
|
||||||
|
}
|
||||||
|
mask
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mask_number_char(c: char) -> TransitionMask {
|
||||||
|
if c.is_ascii() && (c as u8) <= 0x7F {
|
||||||
|
1 << (c as u8)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mask_all() -> TransitionMask {
|
||||||
|
u128::MAX
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::configotron::fsm::masks::decompose_transition;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_decomposition() {
|
||||||
|
let mut arr: [String; 128] = std::array::from_fn(|_| " ".to_string());
|
||||||
|
|
||||||
|
for i in 10..=20 {
|
||||||
|
arr[i] = "A".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 30..=35 {
|
||||||
|
arr[i] = "A".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 100..=110 {
|
||||||
|
arr[i] = "B".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 111..=120 {
|
||||||
|
arr[i] = "C".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 121..=127 {
|
||||||
|
arr[i] = "A".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let masks = decompose_transition(&arr);
|
||||||
|
|
||||||
|
// Test that the number of unique masks is 3 ("A", "B", "C")
|
||||||
|
assert_eq!(masks.len(), 4);
|
||||||
|
|
||||||
|
// Collect the keys for easier assertions
|
||||||
|
let mut keys: Vec<_> = masks.iter().map(|(k, _)| k.as_str()).collect();
|
||||||
|
keys.sort();
|
||||||
|
assert_eq!(keys, vec![" ", "A", "B", "C"]);
|
||||||
|
// Print out the masks for "A", "B", "C", and " "
|
||||||
|
for (k, v) in &masks {
|
||||||
|
if k == "A" || k == "B" || k == "C" || k == " " {
|
||||||
|
println!("mask for {:?}: {:#0128b}", k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the mask for "A" covers the correct indices
|
||||||
|
let mask_a = masks.iter().find(|(k, _)| k == "A").unwrap().1;
|
||||||
|
let mut expected_mask_a = 0;
|
||||||
|
for i in 10..=20 {
|
||||||
|
expected_mask_a |= 1 << i;
|
||||||
|
}
|
||||||
|
for i in 30..=35 {
|
||||||
|
expected_mask_a |= 1 << i;
|
||||||
|
}
|
||||||
|
for i in 121..=127 {
|
||||||
|
expected_mask_a |= 1 << i;
|
||||||
|
}
|
||||||
|
// Note: arr is length 128, so 128..=130 are out of bounds
|
||||||
|
assert_eq!(mask_a, expected_mask_a);
|
||||||
|
|
||||||
|
// Check that the mask for "B" covers 100..=110
|
||||||
|
let mask_b = masks.iter().find(|(k, _)| k == "B").unwrap().1;
|
||||||
|
let mut expected_mask_b = 0;
|
||||||
|
for i in 100..=110 {
|
||||||
|
expected_mask_b |= 1 << i;
|
||||||
|
}
|
||||||
|
assert_eq!(mask_b, expected_mask_b);
|
||||||
|
|
||||||
|
// Check that the mask for "C" covers 111..=120
|
||||||
|
let mask_c = masks.iter().find(|(k, _)| k == "C").unwrap().1;
|
||||||
|
let mut expected_mask_c = 0;
|
||||||
|
for i in 111..=120 {
|
||||||
|
expected_mask_c |= 1 << i;
|
||||||
|
}
|
||||||
|
assert_eq!(mask_c, expected_mask_c);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,253 +1,223 @@
|
|||||||
pub mod masking;
|
pub mod display;
|
||||||
pub mod transition;
|
pub mod masks;
|
||||||
pub mod builder;
|
|
||||||
pub mod run;
|
|
||||||
|
|
||||||
use graphviz_rust::{
|
use std::{collections::{HashMap, HashSet}};
|
||||||
cmd::Layout, dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, NodeId, Stmt, Vertex}, exec, printer::PrinterContext
|
|
||||||
};
|
|
||||||
use transition::FSMTranistion;
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
|
||||||
|
|
||||||
pub type FSMStateRef = Rc<RefCell<FSMState>>;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub fn new_state_ref(s: FSMState) -> FSMStateRef {
|
pub type StateRef = Uuid;
|
||||||
Rc::new(RefCell::new(s))
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum StateType {
|
||||||
|
EndState,
|
||||||
|
ErrorState,
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Action {
|
||||||
|
None,
|
||||||
|
Push,
|
||||||
|
Pop,
|
||||||
|
Submit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type TransitionArray<T> = [T; 128];
|
||||||
|
pub type UnfinishedTransitionArray<T> = [Option<T>; 128];
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Represents a State in consolidate State machine State
|
||||||
|
///
|
||||||
|
/// The `transition` field contains the outgoing transitions for this state,
|
||||||
|
/// where each entry corresponds to a possible input symbol.
|
||||||
|
/// Its expected to have ONLY valid StateRefs
|
||||||
|
pub struct State {
|
||||||
|
name: String,
|
||||||
|
transition: TransitionArray<(StateRef, Action)>,
|
||||||
|
state_type: StateType
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Represents a finished compleate consolidate State machine
|
||||||
|
///
|
||||||
|
/// `start` is expected to be a valid refernces in the `states` Hashmap
|
||||||
|
pub struct FiniteStateMachine {
|
||||||
|
states: HashMap<StateRef, State>,
|
||||||
|
start: StateRef
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FSM {
|
struct UnfinishedState {
|
||||||
name: String,
|
name: String,
|
||||||
states: Vec<FSMStateRef>,
|
transition: UnfinishedTransitionArray<(StateRef, Action)>,
|
||||||
start: Option<FSMStateRef>
|
state_type: StateType
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FSM {
|
pub struct FSMBuilder {
|
||||||
pub fn new(state: Vec<FSMStateRef>, name: &str) -> Self {
|
states: HashMap<Uuid, UnfinishedState>
|
||||||
FSM {
|
}
|
||||||
states: state,
|
|
||||||
name: name.to_string(),
|
pub type TransitionMask = u128;
|
||||||
start: None
|
|
||||||
|
impl FSMBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
FSMBuilder { states: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish (&self, default_state: &StateRef, default_action: Action, 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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.states.contains_key(default_state) {
|
||||||
|
return Err("Start state is not in the State Set!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Fill vacent Tranisitons
|
||||||
|
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| match state.transition[i] {
|
||||||
|
Some(trans) => trans.clone(),
|
||||||
|
None => (*default_state, default_action.clone())
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Elimate all unsed states
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_used_states(states: &HashMap<StateRef, State>, start: &StateRef) -> HashSet<StateRef> {
|
||||||
|
let mut used: HashSet<StateRef> = HashSet::new();
|
||||||
|
|
||||||
|
let mut stack: Vec<StateRef> = Vec::new();
|
||||||
|
stack.push(*start);
|
||||||
|
|
||||||
|
while let Some(current) = stack.pop() {
|
||||||
|
if used.insert(current) {
|
||||||
|
if let Some(s) = states.get(¤t) {
|
||||||
|
for next in s.transition.iter() {
|
||||||
|
stack.push((*next).0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
used
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub fn add_state(&mut self, name: String, state_type: StateType) -> StateRef {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
self.states.insert(id, UnfinishedState {
|
||||||
|
name: name, transition: [None; 128], state_type: state_type
|
||||||
|
});
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_link(&mut self, from_ref: &StateRef, to_ref: &StateRef, action: Action, mask: TransitionMask) -> Result<(), &str> {
|
||||||
|
if !self.states.contains_key(to_ref) { return Err("to-State does not exist!"); }
|
||||||
|
|
||||||
|
let from_state = self.states.get_mut(from_ref).ok_or("from-State does not exist")?;
|
||||||
|
|
||||||
|
for i in 0..128 {
|
||||||
|
if (mask & (1 << i)) != 0 {
|
||||||
|
from_state.transition[i] = Some((*to_ref, action.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_stmts(&self) -> Vec<Stmt> {
|
Ok(())
|
||||||
let mut stmts: Vec<Stmt> = vec![Stmt::Attribute(Attribute(
|
|
||||||
Id::Plain("rankdir".to_string()),
|
|
||||||
Id::Plain("LR".to_string()),
|
|
||||||
))];
|
|
||||||
for s in &self.states {
|
|
||||||
let s_borrowed = s.borrow();
|
|
||||||
|
|
||||||
stmts.push(Stmt::Node(graphviz_rust::dot_structures::Node {
|
|
||||||
id: NodeId(Id::Plain(s_borrowed.to_graph_label()), None),
|
|
||||||
attributes: {
|
|
||||||
let mut res = vec![];
|
|
||||||
let label = s_borrowed.to_graph_label();
|
|
||||||
|
|
||||||
match s_borrowed.action {
|
|
||||||
FSMStateAction::None => {
|
|
||||||
res.push(Attribute(
|
|
||||||
Id::Plain("shape".to_string()),
|
|
||||||
Id::Plain("circle".to_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
FSMStateAction::Error => {
|
|
||||||
res.push(Attribute(
|
|
||||||
Id::Plain("shape".to_string()),
|
|
||||||
Id::Plain("circle".to_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
FSMStateAction::Reset => {
|
|
||||||
res.push(Attribute(
|
|
||||||
Id::Plain("shape".to_string()),
|
|
||||||
Id::Plain("circle".to_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
FSMStateAction::Terminal => {
|
|
||||||
res.push(Attribute(
|
|
||||||
Id::Plain("shape".to_string()),
|
|
||||||
Id::Plain("doublecircle".to_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.push(
|
|
||||||
Attribute(
|
|
||||||
Id::Plain("label".to_string()),
|
|
||||||
Id::Plain(label),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
res
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
match &s_borrowed.transition {
|
|
||||||
None => {}
|
|
||||||
Some(t) => {
|
|
||||||
for (t, range) in t.to_targets_vec() {
|
|
||||||
match t.upgrade() {
|
|
||||||
None => {}
|
|
||||||
Some(target) => {
|
|
||||||
stmts.push(Stmt::Edge(Edge {
|
|
||||||
ty: EdgeTy::Pair(
|
|
||||||
Vertex::N(NodeId(
|
|
||||||
Id::Plain(s_borrowed.to_graph_label()),
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
Vertex::N(NodeId(
|
|
||||||
Id::Plain(target.borrow().to_graph_label()),
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
attributes: vec![Attribute(
|
|
||||||
Id::Plain("label".to_string()),
|
|
||||||
Id::Plain(format!("\"{}\"", range.to_string())),
|
|
||||||
)],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stmts
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_graph(&self, format: graphviz_rust::cmd::Format) -> Result<Vec<u8>, std::io::Error> {
|
|
||||||
let g = Graph::DiGraph {
|
|
||||||
id: Id::Plain("root".to_string()),
|
|
||||||
strict: false,
|
|
||||||
stmts: self.render_stmts(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ctx: graphviz_rust::printer::PrinterContext = PrinterContext::default();
|
|
||||||
|
|
||||||
println!("{}", graphviz_rust::print(g.clone(), &mut ctx));
|
|
||||||
|
|
||||||
let data = exec(g, &mut ctx, vec![
|
|
||||||
format.into(),
|
|
||||||
graphviz_rust::cmd::CommandArg::Layout(Layout::Dot)
|
|
||||||
])?;
|
|
||||||
|
|
||||||
Ok(data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
pub fn run_fsm(fsm: &FiniteStateMachine, input: &String) -> StateType {
|
||||||
pub enum FSMStateAction {
|
let mut current = fsm.start;
|
||||||
None,
|
|
||||||
Terminal,
|
for c in input.chars() {
|
||||||
Error,
|
if c.is_ascii() && (c as u32) <= 0x7F {
|
||||||
Reset
|
current = fsm.states.get(¤t).unwrap().transition[c as usize].0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fsm.states.get(¤t).unwrap().state_type
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FSMState {
|
|
||||||
name: String,
|
|
||||||
action: FSMStateAction,
|
|
||||||
transition: Option<FSMTranistion>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FSMState {
|
|
||||||
pub fn new(name: String, action: FSMStateAction) -> Self {
|
|
||||||
FSMState {
|
|
||||||
name: name,
|
|
||||||
action: action,
|
|
||||||
transition: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_transitions(&mut self, t: FSMTranistion) {
|
|
||||||
self.transition = Some(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_graph_label(&self) -> String {
|
|
||||||
return format!("\"{}\"", self.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_graph_hash(&self) -> String {
|
|
||||||
self.to_graph_label()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use graphviz_rust::cmd::Format;
|
use crate::configotron::fsm::{masks::{mask_all, mask_number_char}, run_fsm, Action, FSMBuilder, StateType};
|
||||||
use std::{cell::RefCell, io::Write, rc::Rc};
|
|
||||||
use super::{FSMState, FSMStateAction, FSM};
|
|
||||||
use super::transition::FSMTranistion;
|
|
||||||
use super::masking::{MaskPresets, TransitionMask};
|
|
||||||
|
|
||||||
pub fn debug_file_dump(d: Vec<u8>, filepath: String) {
|
|
||||||
let mut output_file = std::fs::File::create(filepath).unwrap();
|
|
||||||
output_file.write_all(&d).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_fsm_render() {
|
pub fn basic_fsm_build() {
|
||||||
let z0 = Rc::new(RefCell::new(FSMState::new(
|
let mut fsm_builder = FSMBuilder::new();
|
||||||
"z0".to_string(),
|
let z0 = fsm_builder.add_state("z0".to_string(), StateType::None);
|
||||||
FSMStateAction::None,
|
let z1 = fsm_builder.add_state("z1".to_string(), StateType::None);
|
||||||
)));
|
let z2 = fsm_builder.add_state("z2".to_string(), StateType::EndState);
|
||||||
let z1 = Rc::new(RefCell::new(FSMState::new(
|
let z3 = fsm_builder.add_state("z3".to_string(), StateType::ErrorState);
|
||||||
"z1".to_string(),
|
|
||||||
FSMStateAction::None,
|
|
||||||
)));
|
|
||||||
let z2 = Rc::new(RefCell::new(FSMState::new(
|
|
||||||
"z2".to_string(),
|
|
||||||
FSMStateAction::Terminal,
|
|
||||||
)));
|
|
||||||
let z3 = Rc::new(RefCell::new(FSMState::new(
|
|
||||||
"z3".to_string(),
|
|
||||||
FSMStateAction::None,
|
|
||||||
)));
|
|
||||||
|
|
||||||
let mut z0_transisions = FSMTranistion::new_from_mask(
|
// These are unsed states and should be eliminated
|
||||||
TransitionMask::from(MaskPresets::Char('a')),
|
fsm_builder.add_state("u4".to_string(), StateType::ErrorState);
|
||||||
Rc::downgrade(&z1),
|
let u5 =fsm_builder.add_state("u5".to_string(), StateType::ErrorState);
|
||||||
Rc::downgrade(&z3),
|
|
||||||
);
|
|
||||||
z0_transisions.apply(
|
|
||||||
TransitionMask::from(MaskPresets::Char('b')),
|
|
||||||
Rc::downgrade(&z2),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut z1_transisions = FSMTranistion::new_from_mask(
|
fsm_builder.add_link(&u5, &u5, Action::None, mask_all()).unwrap();
|
||||||
TransitionMask::from(MaskPresets::Char('a')),
|
|
||||||
Rc::downgrade(&z1),
|
|
||||||
Rc::downgrade(&z3),
|
|
||||||
);
|
|
||||||
z1_transisions.apply(
|
|
||||||
TransitionMask::from(MaskPresets::Char('b')),
|
|
||||||
Rc::downgrade(&z2),
|
|
||||||
);
|
|
||||||
|
|
||||||
let z2_transisions = FSMTranistion::new_from_mask(
|
// Transitions:
|
||||||
TransitionMask::from(MaskPresets::Char('a')),
|
// z0: 'a' -> z1, 'b' -> z2
|
||||||
Rc::downgrade(&z2),
|
// z1: 'a' -> z1, 'b' -> z2
|
||||||
Rc::downgrade(&z3),
|
// z2: 'a' -> z2
|
||||||
);
|
// z3: * -> z3
|
||||||
|
//
|
||||||
|
// u4: * -> z3
|
||||||
|
// u5: * -> u5
|
||||||
|
// Error/Default: z3
|
||||||
|
|
||||||
let z3_transisions = FSMTranistion::new(Rc::downgrade(&z3));
|
fsm_builder.add_link(&z0, &z1, Action::None, mask_number_char('a')).unwrap();
|
||||||
|
fsm_builder.add_link(&z0, &z2, Action::None, mask_number_char('b')).unwrap();
|
||||||
|
|
||||||
z0.borrow_mut().set_transitions(z0_transisions);
|
fsm_builder.add_link(&z1, &z1, Action::None, mask_number_char('a')).unwrap();
|
||||||
z1.borrow_mut().set_transitions(z1_transisions);
|
fsm_builder.add_link(&z1, &z2, Action::None, mask_number_char('b')).unwrap();
|
||||||
z2.borrow_mut().set_transitions(z2_transisions);
|
|
||||||
z3.borrow_mut().set_transitions(z3_transisions);
|
|
||||||
|
|
||||||
let fsm = FSM::new(vec![z0, z1, z2, z3], "Test FSM #1");
|
fsm_builder.add_link(&z2, &z2, Action::None, mask_number_char('a')).unwrap();
|
||||||
|
|
||||||
let data = fsm.render_graph(Format::Svg).unwrap();
|
let res_fsm = fsm_builder.finish(&z3, Action::None, &z0);
|
||||||
|
assert!(res_fsm.is_ok());
|
||||||
let mut output_file = std::fs::File::create("/tmp/debug-simle_fsm_building.svg").unwrap();
|
|
||||||
output_file.write_all(&data).unwrap();
|
|
||||||
|
|
||||||
|
|
||||||
let svg_content = std::fs::read_to_string("/tmp/debug-simle_fsm_building.svg").unwrap();
|
if let Ok(fsm) = res_fsm {
|
||||||
assert_eq!(svg_content, String::from_utf8(data).unwrap());
|
// Unused state elimation worked?
|
||||||
|
assert_eq!(fsm.states.len(), 4);
|
||||||
|
|
||||||
|
assert_eq!(run_fsm(&fsm, &"".to_string()), StateType::None);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"a".to_string()), StateType::None);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"b".to_string()), StateType::EndState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"aa".to_string()), StateType::None);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"ab".to_string()), StateType::EndState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"ba".to_string()), StateType::EndState); // 'b' leads to z2, then 'a' stays in z2
|
||||||
|
assert_eq!(run_fsm(&fsm, &"bb".to_string()), StateType::ErrorState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"aaa".to_string()), StateType::None);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"aab".to_string()), StateType::EndState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"abb".to_string()), StateType::ErrorState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"abc".to_string()), StateType::ErrorState); // 'c' is not defined, should go to default z3
|
||||||
|
assert_eq!(run_fsm(&fsm, &"c".to_string()), StateType::ErrorState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"cab".to_string()), StateType::ErrorState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"aabc".to_string()), StateType::ErrorState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"bca".to_string()), StateType::ErrorState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"aaaaab".to_string()), StateType::EndState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"aaaaac".to_string()), StateType::ErrorState);
|
||||||
|
assert_eq!(run_fsm(&fsm, &"bbbb".to_string()), StateType::ErrorState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
use std::{cell::RefCell, rc::{Rc, Weak}};
|
|
||||||
|
|
||||||
use crate::configotron::fsm::{FSMState, FSMStateAction, FSM};
|
|
||||||
|
|
||||||
pub fn run_fsm(input: &str, fsm: &FSM) -> FSMStateAction {
|
|
||||||
let mut state: Weak<RefCell<FSMState>> = Rc::downgrade(fsm.start.as_ref().unwrap());
|
|
||||||
|
|
||||||
for c in input.chars() {
|
|
||||||
let n = c as u8;
|
|
||||||
if c.is_ascii() && n <= 0x7F {
|
|
||||||
let rc_state = state.upgrade().unwrap();
|
|
||||||
let s = rc_state.borrow();
|
|
||||||
state = s.transition.as_ref().unwrap().get_next(n);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
state.upgrade().unwrap().borrow().action.clone()
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
|
|
||||||
use super::masking::TransitionMask;
|
|
||||||
use super::FSMState;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::{cell::RefCell, rc::Weak};
|
|
||||||
|
|
||||||
type StateRef = Weak<RefCell<FSMState>>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FSMTranistion {
|
|
||||||
array: [StateRef; 128],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FSMTranistion {
|
|
||||||
pub fn new_from_mask(mask: TransitionMask, pos: StateRef, default: StateRef) -> Self {
|
|
||||||
let mut array: [StateRef; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
|
||||||
for i in 0..128 {
|
|
||||||
if mask.get_bit(i) {
|
|
||||||
array[i] = Weak::clone(&pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FSMTranistion { array }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_next(&self, c: u8) -> StateRef {
|
|
||||||
self.array[c as usize].clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(default: StateRef) -> Self {
|
|
||||||
let array: [StateRef; 128] = std::array::from_fn(|_| Weak::clone(&default));
|
|
||||||
|
|
||||||
FSMTranistion { array }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&mut self, mask: TransitionMask, state: StateRef) {
|
|
||||||
for i in 0..128 {
|
|
||||||
if mask.get_bit(i) {
|
|
||||||
self.array[i] = Weak::clone(&state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_targets_vec(&self) -> Vec<(StateRef, TransitionMask)> {
|
|
||||||
let mut target_maps: HashMap<String, (StateRef, TransitionMask)> = HashMap::new();
|
|
||||||
|
|
||||||
self.array.iter().enumerate().for_each(|(index, target)| {
|
|
||||||
match target.upgrade() {
|
|
||||||
None => {}
|
|
||||||
Some(t) => {
|
|
||||||
let s_borrowd = t.borrow();
|
|
||||||
let key = s_borrowd.to_graph_label();
|
|
||||||
match target_maps.get_mut(&key) {
|
|
||||||
Some((_, mask)) => {
|
|
||||||
mask.set_bit(index);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let mut mask = TransitionMask::new();
|
|
||||||
mask.set_bit(index);
|
|
||||||
target_maps.insert(key, (Rc::downgrade(&t), mask));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut res: Vec<(StateRef, TransitionMask)> = Vec::new();
|
|
||||||
|
|
||||||
target_maps
|
|
||||||
.iter()
|
|
||||||
.for_each(|(_, (target, mask))| match target.upgrade() {
|
|
||||||
Some(t) => {
|
|
||||||
res.push((Rc::downgrade(&t), mask.clone()));
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
});
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user