Create fsm builder fo lower bound and upper bound numbers
All checks were successful
Test Rust / test (push) Successful in 1m7s

This commit is contained in:
AlexanderHD27
2025-06-10 01:38:14 +02:00
parent 8a4b14f44d
commit 85b7fcd8ab
4 changed files with 208 additions and 20 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target
.debug/

View File

@@ -0,0 +1,150 @@
use crate::configotron::fsm::{masks::{mask_any_number, mask_number, mask_number_range}, Action, FSMBuilder, FiniteStateMachine, StateRef, StateType};
fn decimal_digits(x: u32) -> std::vec::IntoIter<u8> {
x.to_string()
.chars()
.map(|c| c.to_digit(10).unwrap() as u8)
.collect::<Vec<u8>>()
.into_iter()
}
struct BoundFSMScaffold {
builder: FSMBuilder,
error_state: StateRef,
goal_state: StateRef,
last_main: StateRef,
last_side: StateRef,
start_state: StateRef
}
fn build_fsm_bound_selecton(n: u32, lines_state_type: StateType) -> Result<BoundFSMScaffold, String> {
let mut builder = FSMBuilder::new();
let error_state = builder.add_named_state("e".to_string(), StateType::Error);
let goal_state = builder.add_named_state("g".to_string(),StateType::Terminal);
let mut last_main_ref: StateRef = builder.add_named_state("m0".to_string(), lines_state_type);
let start_state = last_main_ref.clone();
let mut last_side_ref: StateRef = builder.add_named_state("s0".to_string(), lines_state_type);
// Link lines
let digits: Vec<u8> = decimal_digits(n).collect();
let mut digits = digits;
digits.pop();
for (i, &digit) in digits.iter().enumerate() {
let new_main_ref = builder.add_named_state(format!("m{}", i+1), lines_state_type);
let new_side_ref = builder.add_named_state(format!("s{}", i+1), lines_state_type);
if digit > 0 {
builder.add_link(&last_main_ref, &last_side_ref, Action::None, mask_number_range(0, digit - 1))?;
}
if digit < 9 {
builder.add_link(&last_main_ref, &new_side_ref, Action::None, mask_number_range(digit + 1, 9))?;
}
builder.add_link(&last_main_ref, &new_main_ref, Action::None, mask_number(digit))?;
builder.add_link(&last_side_ref, &new_side_ref, Action::None, mask_any_number())?;
last_main_ref = new_main_ref;
last_side_ref = new_side_ref;
}
let last_digit = (n % 10) as u8;
if last_digit > 0 {
builder.add_link(&last_main_ref, &last_side_ref, Action::None, mask_number_range(0, last_digit - 1))?;
}
Ok(BoundFSMScaffold {
builder: builder,
error_state: error_state,
goal_state: goal_state,
last_main: last_main_ref,
last_side: last_side_ref,
start_state: start_state
})
}
pub fn build_fsm_upper_bound(max: u32) -> Result<FiniteStateMachine, String> {
let mut scaffold = build_fsm_bound_selecton(max, StateType::Terminal)?;
let last_digit = (max % 10) as u8;
scaffold.builder.add_link(&scaffold.last_main, &scaffold.goal_state, Action::None, mask_number(last_digit))?;
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())?;
let fsm = scaffold.builder.finish(&scaffold.error_state, Action::None, &scaffold.start_state)?;
Ok(fsm)
}
pub fn build_fsm_lower_bound(max: u32) -> Result<FiniteStateMachine, String> {
let mut scaffold = build_fsm_bound_selecton(max, StateType::Error)?;
let last_digit = (max % 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_side, &scaffold.goal_state, Action::None, mask_any_number())?;
scaffold.builder.add_link(&scaffold.goal_state, &scaffold.goal_state, Action::None, mask_any_number())?;
let fsm = scaffold.builder.finish(&scaffold.error_state, Action::None, &scaffold.start_state)?;
Ok(fsm)
}
#[cfg(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_lower_bound;
#[test]
fn upper_bound() {
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);
assert!(fsm.is_ok(), "{:?}", if let Err(e) = fsm { e } else { String::new() });
if let Ok(machine) = fsm {
debug_dump_fsm_graph(&machine, format!(".debug/upper_bound{}.svg", max));
for i in 0..=max*10 {
let input_string = i.to_string();
let res = run_fsm(&machine, &input_string);
assert_eq!(res, if i <= max {
StateType::Terminal
} else {
StateType::Error
}, "Wrong '{:?}' output for {} (Max: {})", res, i, max);
}
}
}
}
#[test]
fn lower_bound() {
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_lower_bound(max);
assert!(fsm.is_ok(), "{:?}", if let Err(e) = fsm { e } else { String::new() });
if let Ok(machine) = fsm {
debug_dump_fsm_graph(&machine, format!(".debug/lower_bound{}.svg", max));
for i in 0..=max*10 {
let input_string = i.to_string();
let res = run_fsm(&machine, &input_string);
assert_eq!(res, if i >= max {
StateType::Terminal
} else {
StateType::Error
}, "Wrong '{:?}' output for {} (Max: {})", res, i, max);
}
}
}
}
}

View File

@@ -1,5 +1,7 @@
use std::fs::File;
use crate::configotron::fsm::{masks::decompose_transition, FiniteStateMachine, TransitionMask};
use graphviz_rust::dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, Node, NodeId, Stmt, Vertex};
use graphviz_rust::{cmd::{CommandArg, Format}, dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, Node, NodeId, Stmt, Vertex}, exec, printer::PrinterContext};
use uuid::Uuid;
@@ -132,11 +134,31 @@ pub fn create_graphviz_graph(fsm: &FiniteStateMachine) -> Graph {
Graph::DiGraph { id: Id::Plain("FSM".to_string()), strict: true, stmts: stmts }
}
pub fn debug_dump_fsm_graph(fsm: &FiniteStateMachine, file: String) {
let graph = create_graphviz_graph(fsm);
let data = exec(graph, &mut PrinterContext::default(), vec![
CommandArg::Format(Format::Svg)
]);
match File::create(&file) {
Ok(mut file) => {
// You probably want to write the SVG data to the file
if let Ok(svg_data) = data {
use std::io::Write;
file.write_all(&svg_data).expect("Unable to write DOT data");
}
}
_ => {}
}
}
#[cfg(test)]
mod test {
use graphviz_rust::{cmd::{CommandArg, Format}, exec, printer::{DotPrinter, PrinterContext}};
use graphviz_rust::{printer::{DotPrinter, PrinterContext}};
use crate::configotron::fsm::{display::{create_graphviz_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]
fn mask_to_string_test() {
@@ -176,12 +198,12 @@ mod test {
}
#[test]
fn create_graph_dot_lang() {
fn basic_fsm() {
let mut fsm_builder = FSMBuilder::new();
let z0 = fsm_builder.add_state("z0".to_string(), StateType::None);
let z1 = fsm_builder.add_state("z1".to_string(), StateType::None);
let z2 = fsm_builder.add_state("z2".to_string(), StateType::Terminal);
let z3 = fsm_builder.add_state("z3".to_string(), StateType::Error);
let z0 = fsm_builder.add_named_state("z0".to_string(), StateType::None);
let z1 = fsm_builder.add_named_state("z1".to_string(), StateType::None);
let z2 = fsm_builder.add_named_state("z2".to_string(), StateType::Terminal);
let z3 = fsm_builder.add_named_state("z3".to_string(), StateType::Error);
// Transitions:
// z0: 'a' -> z1, 'b' -> z2
@@ -206,6 +228,8 @@ mod test {
let string = graph.print(&mut PrinterContext::default());
println!("Original: {}", string);
debug_dump_fsm_graph(&fsm, ".debug/basic_fsm.svg".to_string());
// Test that the DOT output contains the expected structure, ignoring node ids (UUIDs)
let string = graph.print(&mut PrinterContext::default());
// Remove all UUIDs from the output for comparison

View File

@@ -1,5 +1,6 @@
pub mod display;
pub mod masks;
pub mod component_builder;
use std::{collections::{HashMap, HashSet}, fmt};
@@ -18,7 +19,7 @@ pub enum StateType {
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum Action {
None,
Push,
Add,
Pop,
Submit
}
@@ -27,7 +28,7 @@ impl fmt::Display for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Action::None => write!(f, "None"),
Action::Push => write!(f, "Push"),
Action::Add => write!(f, "Push"),
Action::Pop => write!(f, "Pop"),
Action::Submit => write!(f, "Submit"),
}
@@ -128,9 +129,7 @@ impl FSMBuilder {
used
}
pub fn add_state(&mut self, name: String, state_type: StateType) -> StateRef {
pub fn add_named_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
@@ -138,7 +137,21 @@ impl FSMBuilder {
id
}
pub fn add_link(&mut self, from_ref: &StateRef, to_ref: &StateRef, action: Action, mask: TransitionMask) -> Result<(), &str> {
pub fn add_state(&mut self, state_type: StateType) -> StateRef {
let id = Uuid::new_v4();
self.states.insert(id, UnfinishedState {
name: "".to_string(), transition: [None; 128], state_type: state_type
});
id
}
pub fn set_state_type(&mut self, state_ref: &StateRef, state_type: StateType) {
if let Some(state) = self.states.get_mut(state_ref) {
state.state_type = state_type;
}
}
pub fn add_link(&mut self, from_ref: &StateRef, to_ref: &StateRef, action: Action, mask: TransitionMask) -> Result<(), &'static 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")?;
@@ -172,14 +185,14 @@ mod test {
#[test]
pub fn basic_fsm_build() {
let mut fsm_builder = FSMBuilder::new();
let z0 = fsm_builder.add_state("z0".to_string(), StateType::None);
let z1 = fsm_builder.add_state("z1".to_string(), StateType::None);
let z2 = fsm_builder.add_state("z2".to_string(), StateType::Terminal);
let z3 = fsm_builder.add_state("z3".to_string(), StateType::Error);
let z0 = fsm_builder.add_named_state("z0".to_string(), StateType::None);
let z1 = fsm_builder.add_named_state("z1".to_string(), StateType::None);
let z2 = fsm_builder.add_named_state("z2".to_string(), StateType::Terminal);
let z3 = fsm_builder.add_named_state("z3".to_string(), StateType::Error);
// These are unsed states and should be eliminated
fsm_builder.add_state("u4".to_string(), StateType::Error);
let u5 =fsm_builder.add_state("u5".to_string(), StateType::Error);
fsm_builder.add_named_state("u4".to_string(), StateType::Error);
let u5 =fsm_builder.add_named_state("u5".to_string(), StateType::Error);
fsm_builder.add_link(&u5, &u5, Action::None, mask_all()).unwrap();