Build Statemachine Creationg for lower bound number

This commit is contained in:
AlexanderHD27
2025-06-07 03:01:00 +02:00
parent 1a74f6a735
commit 1f399905a6
18 changed files with 2278 additions and 19 deletions

View 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)
}
}
}

View 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);
}
}

View File

@@ -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

View File

@@ -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(

View 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()
}

View File

@@ -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));