Build Statemachine Creationg for lower bound number
This commit is contained in:
135
src/configotron/fsm/builder/mod.rs
Normal file
135
src/configotron/fsm/builder/mod.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
203
src/configotron/fsm/builder/numbers.rs
Normal file
203
src/configotron/fsm/builder/numbers.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use std::{rc::Rc};
|
||||
|
||||
use crate::configotron::fsm::{builder::elimate_unused_states, masking::{MaskPresets, TransitionMask}, new_state_ref, transition::FSMTranistion, FSMState, FSMStateAction, FSMStateRef};
|
||||
|
||||
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_upperbound(max: u32) -> FSM {
|
||||
let length_of_number = number_of_digits(max 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(max, i);
|
||||
|
||||
let new_main_state = new_state_ref(FSMState::new(
|
||||
format!("m{}", i), FSMStateAction::Terminal));
|
||||
let new_side_state = new_state_ref(FSMState::new(
|
||||
format!("s{}", i), FSMStateAction::Terminal));
|
||||
|
||||
let mut main_state_transition = FSMTranistion::new(Rc::downgrade(&error_state));
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
new_side_state.borrow_mut().set_transitions(
|
||||
FSMTranistion::new(Rc::downgrade(&error_state))
|
||||
);
|
||||
|
||||
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(max, i as u32);
|
||||
|
||||
match &mut prev_main.borrow_mut().transition {
|
||||
None => {},
|
||||
Some(t) => {
|
||||
|
||||
if digit < 9 {
|
||||
t.apply(
|
||||
TransitionMask::from(MaskPresets::NumberRange((digit as u8 + 1)..=9)),
|
||||
Rc::downgrade(next_side)
|
||||
);
|
||||
}
|
||||
|
||||
t.apply(
|
||||
TransitionMask::from(MaskPresets::Char(std::char::from_digit(digit, 10).unwrap())),
|
||||
Rc::downgrade(next_main)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
match &mut prev_side.borrow_mut().transition {
|
||||
None => {},
|
||||
Some(t) => {
|
||||
t.apply(
|
||||
TransitionMask::from(MaskPresets::Numbers),
|
||||
Rc::downgrade(next_side)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Link last main to side, goal
|
||||
match &mut main_line.last() {
|
||||
Some(main_last) => {
|
||||
let last_digit = max % 10;
|
||||
match &mut main_last.borrow_mut().transition {
|
||||
Some(t) => {
|
||||
t.apply(TransitionMask::from(MaskPresets::Char(
|
||||
std::char::from_digit(last_digit, 10).unwrap()
|
||||
)), Rc::downgrade(&goal_state));
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use graphviz_rust::cmd::Format;
|
||||
|
||||
use crate::configotron::fsm::{builder::numbers::{create_fsm_number_upperbound, 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_upperbound(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
|
||||
});
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub enum MaskPresets {
|
||||
UpperCase,
|
||||
AllHumanReadable,
|
||||
Numbers,
|
||||
NumberRange(RangeInclusive<u8>),
|
||||
SpecialChars,
|
||||
WhiteSpace,
|
||||
LineTermination,
|
||||
@@ -33,6 +34,11 @@ impl From<MaskPresets> for TransitionMask {
|
||||
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
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
pub mod masking;
|
||||
pub mod transition;
|
||||
pub mod builder;
|
||||
pub mod run;
|
||||
|
||||
use graphviz_rust::{
|
||||
dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, NodeId, Stmt, Vertex},
|
||||
exec,
|
||||
printer::PrinterContext,
|
||||
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>>;
|
||||
|
||||
pub fn new_state_ref(s: FSMState) -> FSMStateRef {
|
||||
Rc::new(RefCell::new(s))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FSM {
|
||||
name: String,
|
||||
states: Vec<Rc<RefCell<FSMState>>>,
|
||||
states: Vec<FSMStateRef>,
|
||||
start: Option<FSMStateRef>
|
||||
}
|
||||
|
||||
impl FSM {
|
||||
pub fn new(state: Vec<Rc<RefCell<FSMState>>>, name: &str) -> Self {
|
||||
pub fn new(state: Vec<FSMStateRef>, name: &str) -> Self {
|
||||
FSM {
|
||||
states: state,
|
||||
name: name.to_string(),
|
||||
start: None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,24 +42,43 @@ impl FSM {
|
||||
stmts.push(Stmt::Node(graphviz_rust::dot_structures::Node {
|
||||
id: NodeId(Id::Plain(s_borrowed.to_graph_label()), None),
|
||||
attributes: {
|
||||
let mut res = vec![Attribute(
|
||||
Id::Plain("label".to_string()),
|
||||
Id::Plain(s_borrowed.to_graph_label()),
|
||||
)];
|
||||
let mut res = vec![];
|
||||
let label = s_borrowed.to_graph_label();
|
||||
|
||||
match s_borrowed.action {
|
||||
FSMStateAction::NONE => {
|
||||
FSMStateAction::None => {
|
||||
res.push(Attribute(
|
||||
Id::Plain("shape".to_string()),
|
||||
Id::Plain("circle".to_string()),
|
||||
));
|
||||
}
|
||||
FSMStateAction::TERMINAL => {
|
||||
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
|
||||
},
|
||||
}));
|
||||
@@ -100,16 +127,21 @@ impl FSM {
|
||||
|
||||
println!("{}", graphviz_rust::print(g.clone(), &mut ctx));
|
||||
|
||||
let data = exec(g, &mut ctx, vec![format.into()])?;
|
||||
let data = exec(g, &mut ctx, vec![
|
||||
format.into(),
|
||||
graphviz_rust::cmd::CommandArg::Layout(Layout::Dot)
|
||||
])?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum FSMStateAction {
|
||||
NONE,
|
||||
TERMINAL,
|
||||
None,
|
||||
Terminal,
|
||||
Error,
|
||||
Reset
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -150,23 +182,28 @@ mod test {
|
||||
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]
|
||||
fn simple_fsm_render() {
|
||||
let z0 = Rc::new(RefCell::new(FSMState::new(
|
||||
"z0".to_string(),
|
||||
FSMStateAction::NONE,
|
||||
FSMStateAction::None,
|
||||
)));
|
||||
let z1 = Rc::new(RefCell::new(FSMState::new(
|
||||
"z1".to_string(),
|
||||
FSMStateAction::NONE,
|
||||
FSMStateAction::None,
|
||||
)));
|
||||
let z2 = Rc::new(RefCell::new(FSMState::new(
|
||||
"z2".to_string(),
|
||||
FSMStateAction::TERMINAL,
|
||||
FSMStateAction::Terminal,
|
||||
)));
|
||||
let z3 = Rc::new(RefCell::new(FSMState::new(
|
||||
"z3".to_string(),
|
||||
FSMStateAction::NONE,
|
||||
FSMStateAction::None,
|
||||
)));
|
||||
|
||||
let mut z0_transisions = FSMTranistion::new_from_mask(
|
||||
|
||||
18
src/configotron/fsm/run.rs
Normal file
18
src/configotron/fsm/run.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
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()
|
||||
}
|
||||
@@ -24,6 +24,10 @@ impl FSMTranistion {
|
||||
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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user